@nitida/sdk 0.31.1 → 0.31.5
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 +131 -34
- package/dist/index.js +20 -7
- package/dist/index.js.map +1 -1
- package/dist/server.js +20 -7
- package/dist/server.js.map +1 -1
- package/dist/web.js +20 -7
- package/dist/web.js.map +1 -1
- package/package.json +2 -2
- package/skills/nitida-sdk/SKILL.md +46 -15
- package/src/index.ts +45 -7
package/README.md
CHANGED
|
@@ -246,14 +246,14 @@ const aq = new NitidaClient({
|
|
|
246
246
|
endpoint: "https://api.nitida.gofuture.space",
|
|
247
247
|
apiKey: process.env.AQUIENPZ_API_KEY!, // amk_rt_* runtime API key
|
|
248
248
|
tenantCode: "your-tenant",
|
|
249
|
-
tenantId: 42, //
|
|
249
|
+
tenantId: 42, // ⚠️ va en la URL en BASE36: 42 → "16". Ni decimal, ni hex.
|
|
250
250
|
cdnBase: "https://8ok.uk", // optional, defaults to https://8ok.uk
|
|
251
251
|
});
|
|
252
252
|
|
|
253
253
|
// Slots — the recommended way to reference brand assets in code.
|
|
254
254
|
// Source never hardcodes a CDN URL; an admin rebinds it from the console.
|
|
255
255
|
const hero = await aq.slots.resolve("storefront.home.hero");
|
|
256
|
-
// → { slot: { asset, preset, … }, preset: "lg", url: "https://8ok.uk/
|
|
256
|
+
// → { slot: { asset, preset, … }, preset: "lg", url: "https://8ok.uk/16/v/<sha>-l.webp" }
|
|
257
257
|
|
|
258
258
|
// Bulk resolution in one round-trip.
|
|
259
259
|
const heroes = await aq.slots.resolveMany([
|
|
@@ -271,7 +271,12 @@ const { assets, nextCursor } = await aq.assets.list({ limit: 50 });
|
|
|
271
271
|
|
|
272
272
|
// Uploads — hash-deduped; returns the canonical v2 URL immediately.
|
|
273
273
|
const result = await aq.upload(file, { fileName: "cover.jpg" });
|
|
274
|
-
// → { assetId, sha256, cdnUrl }
|
|
274
|
+
// → { assetId, sha256, sha, mime, oext, cdnUrl }
|
|
275
|
+
// `sha` (16 hex), `mime` y `oext` existen desde 0.30.0 para que el resultado
|
|
276
|
+
// se pueda pasar DIRECTO a cualquier builder, sin volver a buscar el DTO:
|
|
277
|
+
// getAssetUrl(result, "original") // → -o.jpg, no -o.bin
|
|
278
|
+
// Un objeto armado a mano con sólo { assetId, sha256, cdnUrl } NO sirve:
|
|
279
|
+
// los builders leen `sha`, no `sha256`, y `assertSha` lo rechaza.
|
|
275
280
|
|
|
276
281
|
// Uploading raw bytes (Node/Bun, e.g. re-hosting a remote image)? A Uint8Array has no
|
|
277
282
|
// inherent MIME, so give it one — otherwise it stores as kind:"other" (NO image variants):
|
|
@@ -437,10 +442,10 @@ export function UploadHeroScreen() {
|
|
|
437
442
|
}
|
|
438
443
|
```
|
|
439
444
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
445
|
+
ℹ️ `@nitida/asset-uploader-expo` **is on npm** (`0.2.0`, MIT) and installs
|
|
446
|
+
normally. `aq.upload()` works on Expo without it; you want the uploader when you
|
|
447
|
+
need a **resumable, background** upload — the only way a transfer survives iOS
|
|
448
|
+
suspending the app. See the note above on when an uploader is worth adding.
|
|
444
449
|
|
|
445
450
|
## Next.js App Router (Server Components)
|
|
446
451
|
|
|
@@ -634,7 +639,7 @@ Both symbols are real. Neither is orderable.
|
|
|
634
639
|
|---|---|---|
|
|
635
640
|
| `thumb` `sm` `md` `lg` `xl` `original` `poster` `video` `aiproxy` | ✅ | ✅ |
|
|
636
641
|
| `hls` — the adaptive ladder, built when a video transcodes | ❌ | ✅ |
|
|
637
|
-
| `mp3` — emitted
|
|
642
|
+
| `mp3` — emitted alongside an audio original **only when the original is not already universally playable** (i.e. NOT `audio/mpeg`, `audio/mp4`, `audio/aac`) | ❌ | ✅ |
|
|
638
643
|
| `probe` — indexed stills (`-pr0.jpg`), never on the compact `presets` string | ✅ | ❌ |
|
|
639
644
|
|
|
640
645
|
The write methods (`upload`, `presignUploadUrl`, `regenerate`) take
|
|
@@ -803,7 +808,29 @@ context.
|
|
|
803
808
|
|
|
804
809
|
| you have | use | why |
|
|
805
810
|
|---|---|---|
|
|
806
|
-
| a client (`aq`) | **`aq.transform(asset, opts)`** | it already knows your CDN base and tenant, and it is the only one that can **sign** — `aq.transform(asset, opts, { sign: true })` returns a `?sig=` URL,
|
|
811
|
+
| a client (`aq`) | **`aq.transform(asset, opts)`** | it already knows your CDN base and tenant (⚠️ see the multi-tenant warning below), and it is the only one that can **sign** — `aq.transform(asset, opts, { sign: true })` returns a `?sig=` URL, the only way to an **arbitrary** width |
|
|
812
|
+
|
|
813
|
+
> ⚠️ **THE TENANT IS ONE MODULE-WIDE GLOBAL, NOT PER-CLIENT STATE.**
|
|
814
|
+
>
|
|
815
|
+
> Constructing a `NitidaClient` calls `setTenantId()` — the same global the standalone
|
|
816
|
+
> setter writes. **The last client constructed wins**, and every client made before it
|
|
817
|
+
> silently starts emitting the other tenant's URLs. Measured:
|
|
818
|
+
>
|
|
819
|
+
> ```ts
|
|
820
|
+
> const a = new NitidaClient({ …, tenantId: 15 });
|
|
821
|
+
> a.urlFor(asset, "md"); // → https://8ok.uk/f/v/<sha>-m.webp ✅
|
|
822
|
+
>
|
|
823
|
+
> const b = new NitidaClient({ …, tenantId: 4 });
|
|
824
|
+
> a.urlFor(asset, "md"); // → https://8ok.uk/4/v/<sha>-m.webp ❌ 404, no error
|
|
825
|
+
> ```
|
|
826
|
+
>
|
|
827
|
+
> It affects everything that carries a tenant segment — `urlFor`, `srcSetFor`,
|
|
828
|
+
> `getAssetUrl`, and the private `/a/{tenant}/…` tree. Public `transform()` has no tenant
|
|
829
|
+
> segment, so it is unaffected.
|
|
830
|
+
>
|
|
831
|
+
> In a multi-tenant process (a BFF, a cron, a migration) use **one process per tenant**,
|
|
832
|
+
> or call `setTenantId(n)` immediately before each block of URL building. Holding two
|
|
833
|
+
> clients and trusting each to remember its own tenant does not work.
|
|
807
834
|
| only a DTO — a component, a Server Component, a worker | **`getTransformUrl(asset, opts)`** | no client needed. Call `setCdnBase()` / `setTenantId()` once at module load first |
|
|
808
835
|
|
|
809
836
|
Same builder underneath, same URL out. If you are holding a client, use its
|
|
@@ -846,12 +873,30 @@ const asset = await aq.assets.byHash(sha256);
|
|
|
846
873
|
> **`width` is strongly typed.** `TransformOptions.width` is a **`TransformWidth`** — the
|
|
847
874
|
> predefined CDN ladder (`96, 128, 160, 240, 256, 320, 400, 480, 600, 640, 800, 960,
|
|
848
875
|
> 1080, 1200, 1280, 1440, 1600, 1920, 2560, 3840` — 20 widths, exported as
|
|
849
|
-
> `TRANSFORM_WIDTHS`). An off-ladder
|
|
850
|
-
>
|
|
851
|
-
>
|
|
876
|
+
> `TRANSFORM_WIDTHS`). An off-ladder width is a **compile error**.
|
|
877
|
+
>
|
|
878
|
+
> ⚠️ **The type is narrower than the edge, on purpose.** The edge whitelists a
|
|
879
|
+
> longer list (28 today — its own 400 response enumerates them, and `512`, `768`,
|
|
880
|
+
> `1800`, `2160`, `2400`, `2700`, `2880` and `3600` all serve 200 unsigned). So a
|
|
881
|
+
> width outside the TYPE is not automatically a 400; a width outside the EDGE's
|
|
882
|
+
> list is. Signing is the only way to an **arbitrary** width — not to every width
|
|
883
|
+
> outside `TransformWidth`. Need an arbitrary one? **Sign it** —
|
|
852
884
|
> `aq.transform(asset, { width: 1490 }, { sign: true })` and `getSignedTransformUrl` take
|
|
853
885
|
> `SignedTransformOptions` (where `width` widens to `number`); a valid `?sig=` earns the
|
|
854
|
-
> edge-whitelist bypass.
|
|
886
|
+
> edge-whitelist bypass.
|
|
887
|
+
>
|
|
888
|
+
> ⚠️ **Signed widths have two ceilings the edge does not announce** (measured
|
|
889
|
+
> 2026-08-24):
|
|
890
|
+
>
|
|
891
|
+
> 1. **7680 is the hard cap.** `7680` → 200, `7681` → 400. Unsigned, the real
|
|
892
|
+
> cap is 3840 — so `3841…4320` is a dead band the old 400 message called
|
|
893
|
+
> "in range", and `4321…7680` works signed while that message called it
|
|
894
|
+
> impossible.
|
|
895
|
+
> 2. **Above the source width it CLAMPS and still answers 200.** `/t/` never
|
|
896
|
+
> upscales. Ask for 5000 on an 800 px master and you get **800 px of bytes
|
|
897
|
+
> with HTTP 200** — no error, no warning. Read `x-transform-dsl` on the
|
|
898
|
+
> response (it names the width actually used) or size against the source, if
|
|
899
|
+
> the exact width matters. `transformSrcSet` / `getTransformSrcSet` deliberately keep
|
|
855
900
|
> `number[]` because a responsive ladder may legitimately include DPR-row widths (e.g.
|
|
856
901
|
> `2400`). `height` stays `number` — for the responsive path it's derived from `width`
|
|
857
902
|
> by aspect ratio; only fixed-canvas crops / `genfill` set it explicitly.
|
|
@@ -865,8 +910,14 @@ point of asking for `auto` instead of naming a format.
|
|
|
865
910
|
`gravity=face` detects the highest-confidence face and crops to it with
|
|
866
911
|
sensible padding (50 % of face dimensions on each side, clamped to source
|
|
867
912
|
bounds, keeping the requested aspect ratio). When no face is detected it
|
|
868
|
-
falls back to saliency-based cropping
|
|
869
|
-
`X-Transform-Face
|
|
913
|
+
falls back to saliency-based cropping. The response **always** carries
|
|
914
|
+
`X-Transform-Face`, with one of two values: **`matched`** when a face was found,
|
|
915
|
+
**`fallback`** when it saliency-cropped.
|
|
916
|
+
|
|
917
|
+
⚠️ Check the VALUE, not the presence. The header is emitted **only for
|
|
918
|
+
`gravity=face`** — with `center` or `auto` it is absent, and that absence does
|
|
919
|
+
not mean "no face". Code that tests for presence is right 0 % of the time on
|
|
920
|
+
photos that do contain a face. Cost to you:
|
|
870
921
|
~10 ms warm; ~150 ms on the first request after a cold start.
|
|
871
922
|
|
|
872
923
|
`quality=auto` adapts the per-format quality to source complexity
|
|
@@ -1098,6 +1149,14 @@ the cache semantics are the contract.
|
|
|
1098
1149
|
|
|
1099
1150
|
### Generative fill / aspect outpaint (`effect=genfill`)
|
|
1100
1151
|
|
|
1152
|
+
⚠️ **`genfill` only works on SIGNED URLs.** It is a cost guard: the effect runs
|
|
1153
|
+
a generative model per unique tuple. Without `{ sign: true }` the edge answers
|
|
1154
|
+
`401 {"error":"signature_required"}` — whether or not your tenant has
|
|
1155
|
+
`strict_transforms`, and unlike `width=` or `effect=removebg`, which serve 200
|
|
1156
|
+
unsigned. You need the project's **signing key** (see *Signed URLs*); for a
|
|
1157
|
+
project created before 2026-08-23, ask your operator — it cannot be recovered
|
|
1158
|
+
afterwards. Every example below therefore passes `{ sign: true }`.
|
|
1159
|
+
|
|
1101
1160
|
Extend a source image into a different aspect ratio without the
|
|
1102
1161
|
awkward edge mirroring that classic content-aware fill produces.
|
|
1103
1162
|
Primary use case: building OG cards (1200×630) from portrait listing
|
|
@@ -1106,24 +1165,26 @@ photos, or 1:1 social tiles from 16:9 originals.
|
|
|
1106
1165
|
```ts
|
|
1107
1166
|
// 1200×630 OG card from a portrait listing cover — the gutters are
|
|
1108
1167
|
// generated, the source is pasted centered.
|
|
1109
|
-
const ogUrl = aq.transform(
|
|
1110
|
-
|
|
1111
|
-
width: 1200,
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
// → https://8ok.uk/t/effect=genfill,height=630,width=1200/<sha>.
|
|
1168
|
+
const ogUrl = await aq.transform(
|
|
1169
|
+
asset,
|
|
1170
|
+
{ effect: "genfill", width: 1200, height: 630 },
|
|
1171
|
+
{ sign: true }, // ⚠️ sin esto: 401 signature_required
|
|
1172
|
+
);
|
|
1173
|
+
// → https://8ok.uk/t/effect=genfill,height=630,width=1200/<sha>.webp
|
|
1115
1174
|
|
|
1116
1175
|
// 1:1 social tile from a landscape original
|
|
1117
|
-
const tileUrl = aq.transform(
|
|
1118
|
-
|
|
1119
|
-
width: 1080,
|
|
1120
|
-
|
|
1121
|
-
|
|
1176
|
+
const tileUrl = await aq.transform(
|
|
1177
|
+
asset,
|
|
1178
|
+
{ effect: "genfill", width: 1080, height: 1080 },
|
|
1179
|
+
{ sign: true },
|
|
1180
|
+
);
|
|
1122
1181
|
```
|
|
1123
1182
|
|
|
1124
1183
|
**Requires both `width` and `height`.** Without them the route returns
|
|
1125
1184
|
422 — the effect needs an explicit target canvas to know what to
|
|
1126
|
-
outpaint.
|
|
1185
|
+
outpaint. ⚠️ You only ever see that 422 **after** signing: unsigned, the 401
|
|
1186
|
+
comes first, so a missing `{ sign: true }` looks like a different bug than it
|
|
1187
|
+
is.
|
|
1127
1188
|
|
|
1128
1189
|
**Output defaults to WebP** at q=85 (~150KB for a 1200×630 OG card —
|
|
1129
1190
|
12× lighter than the raw generated PNG). Honors `format=` for
|
|
@@ -1144,7 +1205,19 @@ landscape source ✓). For bigger crops, prefer `gravity=auto` smart-crop,
|
|
|
1144
1205
|
which is deterministic and free — no generation, no invention.
|
|
1145
1206
|
|
|
1146
1207
|
**What it costs you: ~$0.05 for the first request** per (sha, dsl, format)
|
|
1147
|
-
|
|
1208
|
+
⚠️ **The signature guards GENERATION, not DELIVERY.** Once a signed transform
|
|
1209
|
+
runs, its result is written to `/{tenant}/v/<sha>-t<dslHash>.<ext>` and served
|
|
1210
|
+
there **with HTTP 200 and no `?sig=`**. `<dslHash>` is `sha256(canonical DSL)`
|
|
1211
|
+
truncated to 16 — **no secret in it**, so anyone who guesses the DSL derives the
|
|
1212
|
+
URL. Measured: a signed `width=641` (unreachable unsigned) went from 404 to 200
|
|
1213
|
+
at the derived path after a single signed GET.
|
|
1214
|
+
|
|
1215
|
+
Two consequences worth planning around: turning on `strict_transforms` does not
|
|
1216
|
+
un-publish anything already generated, and for `genfill` — whose entire cost
|
|
1217
|
+
guard is the signature — the result you paid for stays publicly readable. Treat
|
|
1218
|
+
a signed transform as "pay once, publish forever", not as an access control.
|
|
1219
|
+
|
|
1220
|
+
tuple. Subsequent identical requests are 302 redirects to the cached WebP —
|
|
1148
1221
|
zero generative cost, forever. The model is server-side and may be swapped
|
|
1149
1222
|
for a better one without any change on your side; the DSL, the output and
|
|
1150
1223
|
the cache semantics are the contract.
|
|
@@ -1229,7 +1302,15 @@ First request to a new HLS URL returns **202 Accepted** with
|
|
|
1229
1302
|
|
|
1230
1303
|
The ladder shrinks to fit the source: a 480p source produces three
|
|
1231
1304
|
rungs (240p / 360p / 480p), a 1080p source produces five, and a 4K
|
|
1232
|
-
source goes up to 2160p.
|
|
1305
|
+
source goes up to 2160p.
|
|
1306
|
+
|
|
1307
|
+
> ⚠️ **Clips under 18 seconds are the exception, and they are common.**
|
|
1308
|
+
> A short source is restricted to the 720p–1080p band on purpose, so an 8 s
|
|
1309
|
+
> 720p clip gets **one rung**, not four — the rung-switching a ladder exists
|
|
1310
|
+
> for cannot happen inside a clip that short, and building five of them just
|
|
1311
|
+
> burns transcode budget. Read `switchable` on the ladder rather than
|
|
1312
|
+
> `rungs.length`: it is `false` exactly when there is nothing to switch
|
|
1313
|
+
> between. A test asserting `rungs.length > 1` will fail on every short clip. The player picks the right rung on the fly
|
|
1233
1314
|
based on the current connection — a user on 3G starts at 240p and
|
|
1234
1315
|
climbs to 1080p as bandwidth improves, vs the monolithic MP4 that
|
|
1235
1316
|
either loaded or timed out.
|
|
@@ -1268,7 +1349,10 @@ const bg = getAmbientGradient(asset.palette);
|
|
|
1268
1349
|
// bg = "linear-gradient(135deg, oklch(...), oklch(...))"
|
|
1269
1350
|
|
|
1270
1351
|
// Auto-pick text color that contrasts with the chosen ambient:
|
|
1271
|
-
|
|
1352
|
+
// ⚠️ `getTextColorForBackground` toma UN SWATCH, no la paleta entera.
|
|
1353
|
+
// Pasarle `asset.palette` tira `TypeError: … evaluating 'hex.replace'`.
|
|
1354
|
+
const bg = pickAmbientBackground(asset.palette); // PaletteSwatch | null
|
|
1355
|
+
const fg = getTextColorForBackground(bg);
|
|
1272
1356
|
// fg = "#fff" | "#000" | similar
|
|
1273
1357
|
|
|
1274
1358
|
// Or just the blurry LQIP for a CSS background placeholder:
|
|
@@ -1277,8 +1361,16 @@ const placeholder = getPaletteBlurBackground(asset.palette);
|
|
|
1277
1361
|
|
|
1278
1362
|
The wire format is intentionally tight: `{d, v, m, dv, lv, dm, lm}`
|
|
1279
1363
|
(dominant, vibrant, muted, dark-vibrant, light-vibrant, dark-muted,
|
|
1280
|
-
light-muted)
|
|
1281
|
-
base64 LQIP
|
|
1364
|
+
light-muted) — **up to** 7 hex strings per asset, much smaller than a full
|
|
1365
|
+
base64 LQIP but composing into nicer ambient UX.
|
|
1366
|
+
|
|
1367
|
+
⚠️ **Only `d` (dominant) is guaranteed. Every other key is optional**, because
|
|
1368
|
+
only the swatches the source actually had get extracted — measured across 30
|
|
1369
|
+
assets, palettes carry anywhere from 2 to 7 keys. Never index one directly:
|
|
1370
|
+
`palette.dv` on a 2-swatch palette emits
|
|
1371
|
+
`linear-gradient(135deg, #1a1a1a, undefined)`. Use the helpers
|
|
1372
|
+
(`pickAmbientBackground`, `getAmbientGradient`, `getPaletteCssVars`), which skip
|
|
1373
|
+
the missing ones.
|
|
1282
1374
|
|
|
1283
1375
|
When the image can't be decoded (SVG sources, exotic formats,
|
|
1284
1376
|
deliberately corrupted bytes), palette + blur silently come back
|
|
@@ -1315,8 +1407,13 @@ server-side pipeline (a platform-wide addition, not a per-tenant one).
|
|
|
1315
1407
|
|
|
1316
1408
|
`<cdnBase>/<tenantId base36>/v/<sha16>-<presetCode>.<ext>`
|
|
1317
1409
|
|
|
1318
|
-
Example: `https://8ok.uk/f/v
|
|
1319
|
-
sha
|
|
1410
|
+
Example: `https://8ok.uk/f/v/<sha16>-q.webp` — the `thumb` preset of a 16-hex
|
|
1411
|
+
sha as WebP, for **tenant 15** (`15` in base36 is `f`).
|
|
1412
|
+
|
|
1413
|
+
> `<sha16>` is a placeholder on purpose. A concrete sha pinned here rots: the
|
|
1414
|
+
> previous example pasted a **real tenant-4 URL with the prefix swapped to
|
|
1415
|
+
> `f`**, so it 404'd on every preset while the identical path under `/4/` served
|
|
1416
|
+
> 200. Substitute a sha from your own tenant — `upload()` returns it as `sha`.
|
|
1320
1417
|
|
|
1321
1418
|
⚠️ **The tenant segment is not optional, and it is base36.** `/15/…` 404s;
|
|
1322
1419
|
so does a bare `/<sha16>-q.webp` with no tenant at all. Nothing serves that
|
package/dist/index.js
CHANGED
|
@@ -794,12 +794,15 @@ var NitidaClient = class {
|
|
|
794
794
|
});
|
|
795
795
|
if (presign.deduped) {
|
|
796
796
|
const short = sha.slice(0, 16);
|
|
797
|
+
const settled = presign.asset.status === "ready" ? presign.asset : await this.assets.waitReady(presign.asset.id, opts.timeoutMs);
|
|
798
|
+
if (settled.status !== "ready")
|
|
799
|
+
throw new Error(`upload: asset ended status=${settled.status}`);
|
|
797
800
|
return {
|
|
798
|
-
assetId:
|
|
801
|
+
assetId: settled.id,
|
|
799
802
|
sha256: sha,
|
|
800
803
|
sha: short,
|
|
801
|
-
mime:
|
|
802
|
-
oext:
|
|
804
|
+
mime: settled.mime ?? mime,
|
|
805
|
+
oext: settled.oext ?? null,
|
|
803
806
|
// ⭐ `{ ...asset, sha: short }`, never `asset` alone.
|
|
804
807
|
//
|
|
805
808
|
// The presign route answers a dedup hit with `sha256` and NO `sha` —
|
|
@@ -816,14 +819,24 @@ var NitidaClient = class {
|
|
|
816
819
|
// latent defect loud.
|
|
817
820
|
//
|
|
818
821
|
// This branch is only reached when the asset EXISTS but is not
|
|
819
|
-
// `ready` — the `byHash` branch above returns first otherwise
|
|
820
|
-
//
|
|
822
|
+
// `ready` — the `byHash` branch above returns first otherwise.
|
|
823
|
+
//
|
|
824
|
+
// That sentence used to end with "so ordinary uploads never touched it
|
|
825
|
+
// and no test did either", and that was the whole problem: it read as
|
|
826
|
+
// reassurance when it was the risk. Two callers uploading the same
|
|
827
|
+
// bytes at once land here, and a retry after a timeout is exactly that.
|
|
828
|
+
// Covered now by `every-upload-branch-waits-for-ready.test.ts`.
|
|
821
829
|
//
|
|
822
830
|
// The sha is not the DTO's to supply: we hashed the bytes ourselves at
|
|
823
831
|
// the top of this method. Build from what we KNOW.
|
|
832
|
+
//
|
|
833
|
+
// And the preset comes from `bestPresetForAsset` — what this asset
|
|
834
|
+
// ACTUALLY has, now that we waited for it — not from
|
|
835
|
+
// `defaultPresetForMime`, which is a guess made before anything exists.
|
|
836
|
+
// Guessing was safe only while this branch never ran.
|
|
824
837
|
cdnUrl: this.urlFor(
|
|
825
|
-
{ ...
|
|
826
|
-
this.
|
|
838
|
+
{ ...settled, sha: short },
|
|
839
|
+
this.bestPresetForAsset(settled, mime)
|
|
827
840
|
)
|
|
828
841
|
};
|
|
829
842
|
}
|