@nitida/sdk 0.33.0 → 0.36.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitida/sdk",
3
- "version": "0.33.0",
3
+ "version": "0.36.0",
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": {
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "peerDependencies": {
75
75
  "react": ">=18",
76
- "@nitida/asset-client": "^0.21.0",
76
+ "@nitida/asset-client": "^0.24.0",
77
77
  "@nitida/asset-compressor-web": "^0.6.1",
78
78
  "@nitida/asset-compressor-native": "^0.2.1"
79
79
  },
@@ -94,7 +94,16 @@ const COVER_CROP = { fit: "cover", gravity: "auto" } as const;
94
94
 
95
95
  Output URL shape: `https://8ok.uk/t/format=webp,width=640/<sha16>.webp`.
96
96
 
97
- ⚠️ **Widths MUST be on the unsigned ladder** `TRANSFORM_WIDTHS` (`96,128,160,240,256,320,400,480,600,640,800,960,1080,1200,1280,1440,1600,1920,2560,3840` 20 widths). Any other width → **HTTP 400** at the edge (DoS guard). The `TransformWidth` type makes an off-ladder width a compile error — import the type, don't hardcode magic numbers. (For a one-off custom width you'd need signed URLs; not used here.)
97
+ ⚠️ **Widths MUST be on the unsigned ladder** `TRANSFORM_WIDTHS` — generated from the source, do not
98
+ edit by hand:
99
+
100
+ <!-- BEGIN GENERATED: transform-widths · bun run gen:docs -->
101
+ `96, 128, 160, 180, 240, 256, 320, 400, 480, 600, 640, 800, 960, 1080, 1200, 1280, 1440, 1600, 1920, 2560, 3840` — 21 widths.
102
+ <!-- END GENERATED: transform-widths -->
103
+
104
+ Any other width → **HTTP 400** at the edge (DoS guard). The `TransformWidth` type makes an off-ladder
105
+ width a compile error — import the type, don't hardcode magic numbers. (For a one-off custom width
106
+ you'd need signed URLs; not used here.)
98
107
 
99
108
  ⚠️ **`getTransformSrcSet` is NOT protected by that type.** Its parameter is `number[]` on purpose —
100
109
  a responsive ladder may legitimately carry DPR widths — so `[641, 999]` compiles clean and fails in
@@ -206,6 +215,52 @@ Smaller **and** worse is generation loss, not a saving.
206
215
  so there is no downscale in between to hide the first pass. At 640 and 1920
207
216
  the laddered output is *heavier* too.
208
217
  - **`["original"]`-only assets are unaffected** — one pass, always.
218
+ - 🔴 **Since `@nitida/asset-client` 0.24.0 you CANNOT pass it.** `quality` is gone from
219
+ `TransformOptions`, so `getTransformUrl(a, { width, quality: 75 })` is a **compile error**.
220
+ There is nothing to remember and nothing to get wrong. If you are reading older code or older
221
+ docs that pass it, that code no longer compiles — delete the key, do not look for a replacement.
222
+ - **The one narrow legitimate case has its own door, and it cannot be called blind:**
223
+ `getByteBudgetTransformUrl(asset, { …, quality })`. It requires a `presets` string on the asset
224
+ (a bare `{ sha }` does not compile) and **throws** if the asset has any size variant, or if
225
+ `presets` is missing — *unknown is not permission*. Use it only for a byte budget somebody
226
+ measured, on an asset uploaded `presets: ["original"]`.
227
+ - **`quality: "auto"` was a NO-OP** — byte-for-byte identical to omitting the key.
228
+ Measured 2026-08-31 on a production laddered asset at `width=1920`: both
229
+ answered **136 680 B, sha256 `3a9ba57d…`, `x-transform-source: original`**.
230
+ ⇒ The whole option is unnecessary, which is why `TransformOptions.quality`
231
+ carries an `@deprecated` tag since `@nitida/asset-client` 0.23.0. Your editor
232
+ strikes it through; that is on purpose.
233
+ - **A second corpus, where there is not even a byte saving.** 5056 px
234
+ architectural renders (tenant `suenos-del-mar`, 2026-08-31), referenced
235
+ against `/t/format=png,width=W/` — the lossless auto path at the same width:
236
+
237
+ | width | pinned source | bytes | PSNR |
238
+ |---|---|---|---|
239
+ | 3840 | `xl` | **+7.2 %** | **−0.50 dB** |
240
+ | 1920 | `lg` | **+8.1 %** | **−0.64 dB** |
241
+ | 1280 | `md` | **+2.0 %** | **−0.85 dB** |
242
+
243
+ Heavier **and** worse, 3 of 3. The photograph corpus above at least came back
244
+ smaller — that is exactly what hid this for four months.
245
+
246
+ ### ✅ The one-line check: `x-transform-source`
247
+
248
+ **Do not reason about which path you got — read it off the response.** The
249
+ header names the variant the edge actually decoded:
250
+
251
+ ```bash
252
+ curl -sI 'https://8ok.uk/t/format=webp,width=1920/<sha16>.webp' \
253
+ | grep -i x-transform-source
254
+ ```
255
+
256
+ | it says | you got |
257
+ |---|---|
258
+ | `original` | **one** lossy pass ✅ |
259
+ | `md` / `lg` / `xl` / `sm` | **two** — a numeric `quality` is in your URL ✖ |
260
+ | `poster` | you asked `/t/` for a video sha; it transformed the poster frame |
261
+
262
+ Run it once per surface after a change. It costs nothing and it is the only
263
+ statement about compression that is not an inference.
209
264
 
210
265
  ### What running this recipe caught
211
266
 
@@ -602,7 +657,13 @@ await getPrivateTransformUrl(asset, { width: 1280 }, signingKey, { expiresInSeco
602
657
  `"public"`, or a bare `{ sha }`, is never refused — and this is a DIFFERENT
603
658
  rule from the missing-preset fallback below, which is about presets, not
604
659
  privacy.
605
- - **`exp` is mandatory**; revocation is *"within a minute"* (60 s TTL at the edge).
660
+ - **`exp` is mandatory and CAPPED at 7 days.** `expiresInSeconds` over that
661
+ throws in the SDK and 401s (`exp_too_far`) at the platform — an expiry that
662
+ never arrives is not an expiry. Revocation is *"within a minute"* (60 s TTL
663
+ at the edge).
664
+ - **⚠️ A signature does NOT widen the width ladder.** Off-ladder widths are 400
665
+ whether or not the URL is signed. Signing buys IDENTITY (which tenant asked,
666
+ until `exp`), which is what `strict_transforms` and `effect=genfill` need.
606
667
  - **The signing key is a backend secret** — it mints URLs for every private
607
668
  asset the tenant owns.
608
669
  - **⭐ Where the signing key comes from: the response that CREATED your
@@ -610,10 +671,15 @@ await getPrivateTransformUrl(asset, { width: 1280 }, signingKey, { expiresInSeco
610
671
  three API keys, and the console shows it in the same panel. Nothing else
611
672
  hands it out — `GET /admin/projects/:code` does **not** include it. If it is
612
673
  lost, the only endpoint that returns a key is
613
- `POST /admin/projects/:code/rotate-signing-key`, which **invalidates every
614
- URL already signed** and needs the system-scope key the platform operator
615
- holds. A tenant with nothing signed yet can rotate for free; a live one
616
- cannot, which is why you save it at creation.
674
+ `POST /admin/projects/:code/rotate-signing-key`, and it needs the
675
+ system-scope key the platform operator holds.
676
+ - **⭐ Rotating no longer kills your `/t/` URLs.** Since `@nitida/sdk` 0.31.0 a
677
+ signed transform URL carries `kid` (which key signed it) and `exp`, and the
678
+ OUTGOING key keeps verifying for 14 days after a rotation. ⚠️ The exception
679
+ is `/a/…` ACCESS URLs: their key is derived from `signing_key` with no `kid`,
680
+ so those 401 immediately on rotation. They are minted per view with
681
+ minute-scale expiries, so the burst heals itself — but rotate at a quiet hour
682
+ if the tenant serves private assets.
617
683
  - **⭐ Already have a project and never saw a signing key?** Then you never got
618
684
  one — projects created before 2026-08-23 were not handed it, and no endpoint
619
685
  shows you the current one. **If you have not signed any URLs yet, ask us to
@@ -622,6 +688,69 @@ await getPrivateTransformUrl(asset, { width: 1280 }, signingKey, { expiresInSeco
622
688
  instead. This is the wall the T5 agent hit, and the sentence that was
623
689
  missing.
624
690
 
691
+ ## ⚠️ EXIF orientation — two dimension pairs, and mixing them stretches the photo
692
+
693
+ **A phone taking a portrait photo does not store portrait pixels.** It writes the
694
+ sensor buffer landscape and tags it `Orientation` 5–8 meaning "rotate before
695
+ showing". So every such file has **two** pairs, transposes of each other:
696
+
697
+ ```
698
+ 4032×3024 the STORED buffer (what a header parser reads)
699
+ 3024×4032 what it DISPLAYS as (what every decoder hands you)
700
+ ```
701
+
702
+ Use the **stored** pair only for orientation-invariant quantities — area, a
703
+ megapixel budget. Use the **displayed** pair for anything geometric: a resize
704
+ target, an aspect ratio, a layout box, a coordinate you denormalise.
705
+
706
+ **This exact confusion shipped twice, on both sides of the wire:**
707
+
708
+ | where | what happened | fixed |
709
+ |---|---|---|
710
+ | `@nitida/asset-compressor-web` 0.6.0–0.6.2 | resize target from the stored pair, fed to a decode that had already rotated ⇒ **1.78× stretch**, measured | **0.6.3** |
711
+ | `asset-manager` (server), until 2026-08-28 | recorded the stored pair as the asset's `w`/`h` while serving rotated variants ⇒ transposed aspect on the DTO | 2026-08-28 |
712
+
713
+ Needed all three, which is why it looked random: a parseable header
714
+ (**HEIC is immune** — the ISOBMFF box walk isn't implemented, so no dims, so no
715
+ forced resize), orientation **5–8**, and a long side **above** the box.
716
+
717
+ ⇒ **Damage is pre-upload and unrecoverable.** The raw bucket faithfully stores the
718
+ already-stretched bytes. Affected photos must be re-uploaded from the original;
719
+ no backfill exists.
720
+
721
+ ### The two traps, concretely
722
+
723
+ **Browser — `createImageBitmap` with BOTH resize axes does not preserve ratio.**
724
+
725
+ ```ts
726
+ // 🔴 any disagreement with the decoded bitmap becomes a stretch
727
+ createImageBitmap(blob, { imageOrientation: "from-image", resizeWidth: w, resizeHeight: h })
728
+ // ✅ one axis: the spec derives the other, ratio survives BY CONSTRUCTION
729
+ createImageBitmap(blob, { imageOrientation: "from-image", resizeWidth: w })
730
+ ```
731
+
732
+ **Node — `sharp(x).rotate().metadata()` does NOT apply the rotation.** `.rotate()`
733
+ queues an operation; `.metadata()` still reads the input. Measured, sharp 0.34.5:
734
+
735
+ ```
736
+ sharp(buf).metadata() → 4032×3024, orientation 6
737
+ sharp(buf).rotate().metadata() → 4032×3024 ← the false idiom
738
+ sharp(buf).rotate().toBuffer() → info → 3024×4032 ← the truth
739
+ ```
740
+
741
+ It read so plausibly it was written three times in one repo, once directly under
742
+ the comment *"Source dims (EXIF-rotated for correctness)"*. To get display dims:
743
+ transpose the raw pair yourself when `orientation` is 5–8, or measure the output
744
+ buffer.
745
+
746
+ **And the lesson that cost the most:** the stretch had been suspected once and
747
+ declared *"refuted with production data"* because storage held four distinct
748
+ aspect ratios — "a pipeline that forced a ratio would have produced ONE". It
749
+ never forced one ratio; it forced **each photo's own transpose**. Counting
750
+ distinct ratios could not detect it, so finding several was evidence of nothing.
751
+ **An invariant you cannot state is an invariant you cannot test** — compare
752
+ against the *displayed* ratio, or you are comparing stored ratios to each other.
753
+
625
754
  ## Two ways an image gets smaller, and only one of them is yours to call
626
755
 
627
756
  This is the question every programmatic caller gets wrong, so it is stated flat:
@@ -696,6 +825,8 @@ needed. Reproduce there before debugging any of these by hand.
696
825
 
697
826
  | Symptom | Cause |
698
827
  |---|---|
828
+ | **a portrait photo comes back stretched wide**, and only some do | `@nitida/asset-compressor-web` **0.6.0–0.6.2**: stored (pre-EXIF) dims forced onto an already-rotated decode ⇒ 1.78×. Needs a parseable header (**HEIC immune**) + orientation 5–8 + long side above the box. **Fixed in 0.6.3**; pre-upload damage, so re-upload, don't repair — see §EXIF orientation |
829
+ | **`w`/`h` on the DTO disagree with the image you receive** | `asset-manager` older than 2026-08-28 recorded the stored pair for EXIF-rotated sources. The **variant** dims were always right — trust those until the asset is re-processed |
699
830
  | **400** on an image URL | width not on `TRANSFORM_WIDTHS` ladder |
700
831
  | **410** on a video `/t/` URL | that video has no `poster`. With one, `/t/` returns the poster as an image, never the video — use `getAssetUrl(...,'video')` |
701
832
  | **404** on a video URL | decimal tenant prefix (`/10/v/`) instead of base36 (`/a/v/`) — call `setTenantId` + `getAssetUrl`. Tenant 12 → `/c/v/` |
package/src/index.ts CHANGED
@@ -49,17 +49,20 @@ import {
49
49
  hasPreset,
50
50
  invalidateSlotCache,
51
51
  isRequestablePreset,
52
+ type PrivateTransformOptions,
52
53
  REQUESTABLE_PRESETS,
53
54
  type RequestablePreset,
54
55
  type ResolveSlotOptions,
55
56
  resolveSlot,
56
57
  resolveSlots,
57
58
  type SignedTransformOptions,
59
+ type SignTransformOptions,
58
60
  type SlotDTO,
59
61
  type SlotResolution,
60
62
  setCdnBase,
61
63
  setTenantId,
62
64
  type TransformOptions,
65
+ type TransformWidth,
63
66
  toRequestablePresets,
64
67
  type VariantEntryPreset,
65
68
  type VariantPreset,
@@ -224,10 +227,12 @@ export type {
224
227
  AssetVariant,
225
228
  HlsRung,
226
229
  PaletteSwatch,
230
+ PrivateTransformOptions,
227
231
  RequestablePreset,
228
232
  ResolveSlotOptions,
229
233
  SignAccessOptions,
230
234
  SignedTransformOptions,
235
+ SignTransformOptions,
231
236
  SlotDTO,
232
237
  SlotResolution,
233
238
  TransformEffect,
@@ -249,6 +254,7 @@ export {
249
254
  configureSlotResolver,
250
255
  contrastRatio,
251
256
  deriveAccessKey,
257
+ deriveTransformKid,
252
258
  extractAssetSha,
253
259
  getAmbientGradient,
254
260
  getAssetDimensions,
@@ -259,6 +265,7 @@ export {
259
265
  getHlsStreamingUrl,
260
266
  getPaletteBlurBackground,
261
267
  getPaletteCssVars,
268
+ getByteBudgetTransformUrl,
262
269
  getPrivateAssetUrl,
263
270
  getPrivateTransformUrl,
264
271
  getSignedTransformUrl,
@@ -268,10 +275,14 @@ export {
268
275
  getTransformUrl,
269
276
  getVideoTransformUrl,
270
277
  hasPreset,
278
+ hasSizeLadder,
271
279
  hlsLadderAlignment,
272
280
  invalidateSlotCache,
273
281
  isRequestablePreset,
274
282
  iteratePaletteSwatches,
283
+ MAX_SIGNED_TRANSFORM_TTL_SECONDS,
284
+ MAX_SIGNED_URL_TTL_SECONDS,
285
+ MIN_SIGNED_TRANSFORM_TTL_SECONDS,
275
286
  PRESET_EXT,
276
287
  PRESET_LONG,
277
288
  PRESET_MAX_DIM,
@@ -288,6 +299,7 @@ export {
288
299
  signTransformUrl,
289
300
  TRANSFORM_WIDTHS,
290
301
  toRequestablePresets,
302
+ transformMessage,
291
303
  } from "@nitida/asset-client";
292
304
 
293
305
  // ---------------------------------------------------------------------------
@@ -1477,27 +1489,21 @@ export class NitidaClient {
1477
1489
  */
1478
1490
  // Overload 1: no signing — synchronous, on-ladder width only (strict).
1479
1491
  transform(asset: Pick<AssetDTO, "sha">, opts?: TransformOptions): string;
1480
- // Overload 2: with { sign: true } — async; returns `?sig=<hmac>` URL.
1481
- // Accepts SignedTransformOptions so a signed URL may carry an off-ladder
1482
- // custom width (the signature earns the edge whitelist bypass).
1492
+ // Overload 2: with { sign: true, expiresInSeconds } — async; returns a
1493
+ // `?kid&exp&sig` URL. The width ladder is the SAME as unsigned: a signature
1494
+ // names the caller, it does not widen the image (doc blindaje WS-6).
1483
1495
  transform(
1484
1496
  asset: Pick<AssetDTO, "sha">,
1485
1497
  opts: SignedTransformOptions,
1486
- signOpts: { sign: true },
1498
+ signOpts: { sign: true } & SignTransformOptions,
1487
1499
  ): Promise<string>;
1488
1500
  transform(
1489
1501
  asset: Pick<AssetDTO, "sha">,
1490
1502
  opts: SignedTransformOptions = {},
1491
- signOpts?: { sign: true },
1503
+ signOpts?: { sign: true } & SignTransformOptions,
1492
1504
  ): string | Promise<string> {
1493
1505
  if (!signOpts?.sign) {
1494
- // Unsigned path. Overload 1 constrains `width` to the ladder at every
1495
- // public call site, so an off-ladder width can't reach here through the
1496
- // typed API — narrow back to TransformOptions for the strict builder.
1497
- return (
1498
- getTransformUrl(asset, opts as TransformOptions) ??
1499
- this.urlFor(asset, "lg")
1500
- );
1506
+ return getTransformUrl(asset, opts) ?? this.urlFor(asset, "lg");
1501
1507
  }
1502
1508
  if (!this.opts.signingKey) {
1503
1509
  throw new Error(
@@ -1505,11 +1511,15 @@ export class NitidaClient {
1505
1511
  "No signingKey on this client. It is returned ONCE, in the response that creates your project (POST /admin/projects → `signingKey`, next to the three API keys; the console shows it in the same panel). If you never saw one — projects created before 2026-08-23 were not handed it — ask the platform operator to rotate: with no signed URLs in flight that invalidates nothing and is free. If you DO have signed URLs circulating, ask for the current key instead, because rotating would kill them. Then pass it to the SDK constructor on a SERVER-side instance only.",
1506
1512
  );
1507
1513
  }
1508
- // Signed path custom (off-ladder) widths allowed. Empty opts no
1509
- // transform DSL, fall back to the unsigned `lg` variant URL.
1514
+ // Empty opts no transform DSL, fall back to the unsigned `lg` variant
1515
+ // URL. `tenantId` defaults to the one this client was constructed with,
1516
+ // because the tenant is part of the signed message.
1510
1517
  return (
1511
- getSignedTransformUrl(asset, opts, this.opts.signingKey) ??
1512
- Promise.resolve(this.urlFor(asset, "lg"))
1518
+ getSignedTransformUrl(asset, opts, this.opts.signingKey, {
1519
+ expiresInSeconds: signOpts.expiresInSeconds,
1520
+ tenantId: signOpts.tenantId ?? this.opts.tenantId,
1521
+ nowSeconds: signOpts.nowSeconds,
1522
+ }) ?? Promise.resolve(this.urlFor(asset, "lg"))
1513
1523
  );
1514
1524
  }
1515
1525
 
@@ -1527,15 +1537,15 @@ export class NitidaClient {
1527
1537
  ): string;
1528
1538
  transformSrcSet(
1529
1539
  asset: Pick<AssetDTO, "sha">,
1530
- widths: number[],
1540
+ widths: TransformWidth[],
1531
1541
  extraOpts: Omit<TransformOptions, "width">,
1532
- signOpts: { sign: true },
1542
+ signOpts: { sign: true } & SignTransformOptions,
1533
1543
  ): Promise<string>;
1534
1544
  transformSrcSet(
1535
1545
  asset: Pick<AssetDTO, "sha">,
1536
1546
  widths: number[],
1537
1547
  extraOpts: Omit<TransformOptions, "width"> = {},
1538
- signOpts?: { sign: true },
1548
+ signOpts?: { sign: true } & SignTransformOptions,
1539
1549
  ): string | Promise<string> {
1540
1550
  if (!signOpts?.sign) return getTransformSrcSet(asset, widths, extraOpts);
1541
1551
  if (!this.opts.signingKey) {
@@ -1545,15 +1555,22 @@ export class NitidaClient {
1545
1555
  );
1546
1556
  }
1547
1557
  const key = this.opts.signingKey;
1558
+ const sign = {
1559
+ expiresInSeconds: signOpts.expiresInSeconds,
1560
+ tenantId: signOpts.tenantId ?? this.opts.tenantId,
1561
+ nowSeconds: signOpts.nowSeconds,
1562
+ };
1548
1563
  return Promise.all(
1549
1564
  widths.map(async (w) => {
1550
- // Signed path off-ladder widths allowed; build+sign via the
1551
- // custom-width helper (the strict `getTransformUrl` would reject a
1552
- // raw `number` width).
1565
+ // ⚠️ The overload types `widths` as `TransformWidth[]` on the signed
1566
+ // path. Off-ladder widths 400 at the edge whether or not they are
1567
+ // signed (doc blindaje WS-6), so a signed srcSet full of custom widths
1568
+ // is a srcSet of 400s — the type says so at compile time now.
1553
1569
  const signed = await getSignedTransformUrl(
1554
1570
  asset,
1555
- { ...extraOpts, width: w },
1571
+ { ...extraOpts, width: w as TransformWidth },
1556
1572
  key,
1573
+ sign,
1557
1574
  );
1558
1575
  return signed ? `${signed} ${w}w` : null;
1559
1576
  }),
@@ -107,6 +107,7 @@ export {
107
107
  configureSlotResolver,
108
108
  contrastRatio,
109
109
  deriveAccessKey,
110
+ deriveTransformKid,
110
111
  extractAssetSha,
111
112
  getAmbientGradient,
112
113
  getAssetDimensions,
@@ -119,6 +120,7 @@ export {
119
120
  getPaletteCssVars,
120
121
  getPrivateAssetUrl,
121
122
  getPrivateTransformUrl,
123
+ getByteBudgetTransformUrl,
122
124
  getSignedTransformUrl,
123
125
  getTenantId,
124
126
  getTextColorForBackground,
@@ -127,11 +129,15 @@ export {
127
129
  getVideoTransformUrl,
128
130
  type HlsRung,
129
131
  hasPreset,
132
+ hasSizeLadder,
130
133
  hlsLadderAlignment,
131
134
  invalidateSlotCache,
132
135
  isRequestablePreset,
133
136
  isUniversallyPlayableAudio,
134
137
  iteratePaletteSwatches,
138
+ MAX_SIGNED_TRANSFORM_TTL_SECONDS,
139
+ MAX_SIGNED_URL_TTL_SECONDS,
140
+ MIN_SIGNED_TRANSFORM_TTL_SECONDS,
135
141
  mimeFromFileName,
136
142
  type NitidaClientOptions,
137
143
  type PaletteSwatch,
@@ -140,6 +146,7 @@ export {
140
146
  PRESET_MAX_DIM,
141
147
  PRESET_SHORT,
142
148
  type PresignUploadUrlOptions,
149
+ type PrivateTransformOptions,
143
150
  pickAmbientBackground,
144
151
  REQUESTABLE_PRESETS,
145
152
  type RegenerateResult,
@@ -150,6 +157,7 @@ export {
150
157
  resolveSlots,
151
158
  type SignAccessOptions,
152
159
  type SignedTransformOptions,
160
+ type SignTransformOptions,
153
161
  type SlotDTO,
154
162
  type SlotHistoryEntry,
155
163
  type SlotResolution,
@@ -166,6 +174,7 @@ export {
166
174
  type TransformOptions,
167
175
  type TransformWidth,
168
176
  toRequestablePresets,
177
+ transformMessage,
169
178
  type UploadOptions,
170
179
  type UploadResult,
171
180
  type UploadUrlResult,
package/src/web/index.ts CHANGED
@@ -124,6 +124,7 @@ export {
124
124
  configureSlotResolver,
125
125
  contrastRatio,
126
126
  deriveAccessKey,
127
+ deriveTransformKid,
127
128
  extractAssetSha,
128
129
  getAmbientGradient,
129
130
  getAssetDimensions,
@@ -136,6 +137,7 @@ export {
136
137
  getPaletteCssVars,
137
138
  getPrivateAssetUrl,
138
139
  getPrivateTransformUrl,
140
+ getByteBudgetTransformUrl,
139
141
  getSignedTransformUrl,
140
142
  getTenantId,
141
143
  getTextColorForBackground,
@@ -144,11 +146,15 @@ export {
144
146
  getVideoTransformUrl,
145
147
  type HlsRung,
146
148
  hasPreset,
149
+ hasSizeLadder,
147
150
  hlsLadderAlignment,
148
151
  invalidateSlotCache,
149
152
  isRequestablePreset,
150
153
  isUniversallyPlayableAudio,
151
154
  iteratePaletteSwatches,
155
+ MAX_SIGNED_TRANSFORM_TTL_SECONDS,
156
+ MAX_SIGNED_URL_TTL_SECONDS,
157
+ MIN_SIGNED_TRANSFORM_TTL_SECONDS,
152
158
  mimeFromFileName,
153
159
  type PaletteSwatch,
154
160
  PRESET_EXT,
@@ -156,6 +162,7 @@ export {
156
162
  PRESET_MAX_DIM,
157
163
  PRESET_SHORT,
158
164
  type PresignUploadUrlOptions,
165
+ type PrivateTransformOptions,
159
166
  pickAmbientBackground,
160
167
  REQUESTABLE_PRESETS,
161
168
  type RegenerateResult,
@@ -166,6 +173,7 @@ export {
166
173
  resolveSlots,
167
174
  type SignAccessOptions,
168
175
  type SignedTransformOptions,
176
+ type SignTransformOptions,
169
177
  type SlotDTO,
170
178
  type SlotHistoryEntry,
171
179
  type SlotResolution,
@@ -182,6 +190,7 @@ export {
182
190
  type TransformOptions,
183
191
  type TransformWidth,
184
192
  toRequestablePresets,
193
+ transformMessage,
185
194
  type UploadOptions,
186
195
  type UploadResult,
187
196
  type UploadUrlResult,