@nitida/sdk 0.31.4 → 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.
Files changed (2) hide show
  1. package/README.md +34 -3
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -883,7 +883,20 @@ const asset = await aq.assets.byHash(sha256);
883
883
  > outside `TransformWidth`. Need an arbitrary one? **Sign it** —
884
884
  > `aq.transform(asset, { width: 1490 }, { sign: true })` and `getSignedTransformUrl` take
885
885
  > `SignedTransformOptions` (where `width` widens to `number`); a valid `?sig=` earns the
886
- > edge-whitelist bypass. `transformSrcSet` / `getTransformSrcSet` deliberately keep
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
887
900
  > `number[]` because a responsive ladder may legitimately include DPR-row widths (e.g.
888
901
  > `2400`). `height` stays `number` — for the responsive path it's derived from `width`
889
902
  > by aspect ratio; only fixed-canvas crops / `genfill` set it explicitly.
@@ -897,8 +910,14 @@ point of asking for `auto` instead of naming a format.
897
910
  `gravity=face` detects the highest-confidence face and crops to it with
898
911
  sensible padding (50 % of face dimensions on each side, clamped to source
899
912
  bounds, keeping the requested aspect ratio). When no face is detected it
900
- falls back to saliency-based cropping and returns the response with
901
- `X-Transform-Face: fallback`, so you can tell the two apart. Cost to you:
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:
902
921
  ~10 ms warm; ~150 ms on the first request after a cold start.
903
922
 
904
923
  `quality=auto` adapts the per-format quality to source complexity
@@ -1186,6 +1205,18 @@ landscape source ✓). For bigger crops, prefer `gravity=auto` smart-crop,
1186
1205
  which is deterministic and free — no generation, no invention.
1187
1206
 
1188
1207
  **What it costs you: ~$0.05 for the first request** per (sha, dsl, format)
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
+
1189
1220
  tuple. Subsequent identical requests are 302 redirects to the cached WebP —
1190
1221
  zero generative cost, forever. The model is server-side and may be swapped
1191
1222
  for a better one without any change on your side; the DSL, the output and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitida/sdk",
3
- "version": "0.31.4",
3
+ "version": "0.31.5",
4
4
  "description": "nitida — the media SDK: browser and mobile upload with resume, client-side compression, on-the-fly transforms behind a CDN, video transcode, HLS ladders and AI proxies. Multi-tenant.",
5
5
  "private": false,
6
6
  "publishConfig": {