@nitida/asset-client 0.16.1 → 0.16.3
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/AGENTS.md +7 -6
- package/README.md +5 -3
- package/dist/index.cjs +45 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -37
- package/dist/index.d.ts +120 -37
- package/dist/index.js +43 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +154 -24
- package/src/palette.ts +20 -19
- package/src/slots.ts +11 -14
- package/src/transform.ts +35 -22
package/dist/index.d.cts
CHANGED
|
@@ -51,14 +51,14 @@ declare function getTextColorForBackground(swatch: PaletteSwatch | string | null
|
|
|
51
51
|
/** Luminancia relativa WCAG de un hex. 0 = negro, 1 = blanco. */
|
|
52
52
|
declare function relativeLuminance(hex: string): number;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* WCAG contrast ratio between two luminances: `(L1 + 0.05) / (L2 + 0.05)`,
|
|
55
|
+
* lighter on top. 1 = identical, 21 = black against white.
|
|
56
56
|
*/
|
|
57
57
|
declare function contrastRatio(l1: number, l2: number): number;
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* Same choice as the recommended text colour, but it also returns **the ratio
|
|
60
|
+
* it achieved** and whether that passes AA — so a caller can decide with the
|
|
61
|
+
* number in front of them instead of assuming it was enough.
|
|
62
62
|
*/
|
|
63
63
|
declare function bestTextContrast(hex: string): {
|
|
64
64
|
color: "#000000" | "#FFFFFF";
|
|
@@ -98,9 +98,9 @@ declare function getPaletteBlurBackground(palette: AssetPalette | null | undefin
|
|
|
98
98
|
* Slots give tenants a way to attach stable, human-readable names
|
|
99
99
|
* ("webapp.wizard.pool-type.icon-1", "storefront.cr.hero-video.landscape_hd_16x9.mp4")
|
|
100
100
|
* to assets they uploaded. Consumers resolve names → AssetDTOs at
|
|
101
|
-
* build / runtime so their source never hardcodes a CDN URL;
|
|
102
|
-
* admin rebinds a slot
|
|
103
|
-
* up the swap on cache refresh.
|
|
101
|
+
* build / runtime so their source never hardcodes a CDN URL; an
|
|
102
|
+
* admin rebinds a slot in the platform console and every consumer
|
|
103
|
+
* picks up the swap on cache refresh.
|
|
104
104
|
*
|
|
105
105
|
* Two layers in this package:
|
|
106
106
|
* - `resolveSlot` / `resolveSlots` — universal (server, edge,
|
|
@@ -137,8 +137,8 @@ type SlotResolution = {
|
|
|
137
137
|
*
|
|
138
138
|
* configureSlotResolver({
|
|
139
139
|
* endpoint: process.env.AQUIENPZ_URL,
|
|
140
|
-
* apiKey: process.env.
|
|
141
|
-
* tenantCode: "
|
|
140
|
+
* apiKey: process.env.AQUIENPZ_API_KEY, // amk_rt_* — server-only
|
|
141
|
+
* tenantCode: "acme-co",
|
|
142
142
|
* });
|
|
143
143
|
*/
|
|
144
144
|
declare function configureSlotResolver(opts: {
|
|
@@ -173,10 +173,9 @@ declare function resolveSlots(slotKeys: string[], opts?: ResolveSlotOptions): Pr
|
|
|
173
173
|
* On-the-fly transform URL builder.
|
|
174
174
|
*
|
|
175
175
|
* Mirrors the server's DSL canonicalizer byte-for-byte so a URL generated
|
|
176
|
-
* here hashes to the same
|
|
176
|
+
* here hashes to the same cache key as the server's canonical form.
|
|
177
177
|
*
|
|
178
|
-
* Canonicalization rules (
|
|
179
|
-
* `apps/asset-manager/src/features/assets/transform.dsl.ts`):
|
|
178
|
+
* Canonicalization rules (kept in sync with the server):
|
|
180
179
|
* - Drop entries whose value is `undefined`
|
|
181
180
|
* - Sort keys alphabetically
|
|
182
181
|
* - Numbers rendered without leading zeros or trailing dots
|
|
@@ -191,7 +190,7 @@ declare function resolveSlots(slotKeys: string[], opts?: ResolveSlotOptions): Pr
|
|
|
191
190
|
*/
|
|
192
191
|
|
|
193
192
|
/**
|
|
194
|
-
* Widths the CDN edge whitelists (DoS guard
|
|
193
|
+
* Widths the CDN edge whitelists (DoS guard).
|
|
195
194
|
* Requesting any OTHER width returns HTTP 400 at the edge (unsigned URLs). These are the
|
|
196
195
|
* 1× base ladder values; DPR ×2/×3 multiples are applied + whitelisted server-side. Import
|
|
197
196
|
* this instead of hardcoding magic widths so an unsupported size is caught in review/IDE.
|
|
@@ -221,9 +220,9 @@ type TransformOptions = {
|
|
|
221
220
|
height?: number;
|
|
222
221
|
/** Resize fit mode. Default `cover` server-side. */
|
|
223
222
|
fit?: TransformFit;
|
|
224
|
-
/** Crop gravity. `auto`
|
|
223
|
+
/** Crop gravity. `auto` picks the region with the most visual salience. */
|
|
225
224
|
gravity?: TransformGravity;
|
|
226
|
-
/** Output format. `auto` → policy decides
|
|
225
|
+
/** Output format. `auto` → the platform's policy decides. */
|
|
227
226
|
format?: TransformFormat;
|
|
228
227
|
/** Output quality. `auto` → format-specific default. */
|
|
229
228
|
quality?: "auto" | number;
|
|
@@ -234,16 +233,15 @@ type TransformOptions = {
|
|
|
234
233
|
*
|
|
235
234
|
* - `removebg`: remove the background; output is a transparent PNG
|
|
236
235
|
* of the foreground subject. Forces `format=png` regardless of
|
|
237
|
-
* other format hints.
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* from R2 — no inference, no per-image cost.
|
|
236
|
+
* other format hints. A single cache miss per (sha, dsl) tuple;
|
|
237
|
+
* subsequent identical DSLs serve from cache — no inference, no
|
|
238
|
+
* per-image cost.
|
|
241
239
|
*
|
|
242
|
-
* - `genfill`: aspect-extension outpaint
|
|
243
|
-
*
|
|
240
|
+
* - `genfill`: aspect-extension outpaint. Requires BOTH `width`
|
|
241
|
+
* and `height` — the server
|
|
244
242
|
* fits the source centered into the target canvas and outpaints
|
|
245
243
|
* the gutters. Output is PNG (forced) at exactly target dims.
|
|
246
|
-
* ~$0.05/image first time; same
|
|
244
|
+
* ~$0.05/image first time; same cache as removebg after.
|
|
247
245
|
* Primary use case: building OG cards (1200×630) from portrait
|
|
248
246
|
* listing photos without awkward edge mirroring.
|
|
249
247
|
*/
|
|
@@ -264,7 +262,7 @@ type TransformOptions = {
|
|
|
264
262
|
* the escape hatch for SIGNED URLs that need an off-ladder custom width.
|
|
265
263
|
*
|
|
266
264
|
* The edge whitelist only rejects off-ladder widths on UNSIGNED URLs; a valid
|
|
267
|
-
* `?sig=` earns the whitelist bypass at the
|
|
265
|
+
* `?sig=` earns the whitelist bypass at the edge (the server still
|
|
268
266
|
* does the real HMAC check). So a custom width is ONLY safe when the URL is
|
|
269
267
|
* signed — hence this type is accepted exclusively by the signing helpers
|
|
270
268
|
* ({@link getSignedTransformUrl} / `aq.transform(asset, opts, { sign: true })`),
|
|
@@ -305,8 +303,8 @@ declare function serializeTransform(opts: SignedTransformOptions): string;
|
|
|
305
303
|
* Build a transform URL for a VIDEO asset. Same DSL shape as image
|
|
306
304
|
* transforms; the server branches on the asset's `kind` column. Video
|
|
307
305
|
* URLs use `.mp4` (default) or `.webm` extension and on cache miss the
|
|
308
|
-
* server returns 202 Accepted while a
|
|
309
|
-
* subsequent GETs return 302 to the cached
|
|
306
|
+
* server returns 202 Accepted while a background job encodes the clip;
|
|
307
|
+
* subsequent GETs return 302 to the cached object.
|
|
310
308
|
*
|
|
311
309
|
* <video src={aq.transformVideo(asset, { width: 1080, height: 1920 })}
|
|
312
310
|
* autoPlay muted loop playsInline />
|
|
@@ -338,15 +336,30 @@ declare function getVideoTransformUrl(asset: Pick<AssetDTO, "sha">, opts: Transf
|
|
|
338
336
|
* if (prefersNativeHls(video)) {
|
|
339
337
|
* video.src = src;
|
|
340
338
|
* } else {
|
|
341
|
-
*
|
|
342
|
-
* const hls = new Hls({ startLevel: -1, testBandwidth: true, abrEwmaDefaultEstimate: 1_000_000 });
|
|
339
|
+
* const hls = new Hls({ capLevelToPlayerSize: false, abrEwmaDefaultEstimate: 5_000_000 });
|
|
343
340
|
* hls.loadSource(src);
|
|
344
341
|
* hls.attachMedia(video);
|
|
345
342
|
* }
|
|
346
343
|
* ```
|
|
347
344
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
345
|
+
* ⚠️ **Do NOT pass `startLevel: -1` with `testBandwidth: true`.** That pair is
|
|
346
|
+
* documented by hls.js as *"forces the player to download a fragment from the
|
|
347
|
+
* lowest level to establish a bandwidth estimate"* — on a clip short enough to
|
|
348
|
+
* be one segment, the probe IS the whole video, and it plays at the bottom
|
|
349
|
+
* rung from first frame to last. (This doc-comment recommended exactly that
|
|
350
|
+
* until 2026-08-18; a 5.042 s 4K asset was measured being delivered at
|
|
351
|
+
* 426x240 because of it.) Leave `startLevel` unset: hls.js then opens on the
|
|
352
|
+
* FIRST level in the manifest, and the server puts the right one there —
|
|
353
|
+
* a mid rung for long video, the top rung for a clip under 18 s, which is the
|
|
354
|
+
* same rung native HLS opens on per RFC 8216 §6.3.4. The ladder decides; the
|
|
355
|
+
* player should not second-guess it.
|
|
356
|
+
*
|
|
357
|
+
* `capLevelToPlayerSize` is worth disabling explicitly: `@videojs/core`
|
|
358
|
+
* defaults it to `true`, which caps quality to the player's rendered pixel box,
|
|
359
|
+
* so a small inline player is pinned to 240p/360p on any connection.
|
|
360
|
+
*
|
|
361
|
+
* On first request the server returns 202 Accepted while a background
|
|
362
|
+
* job transcodes the ladder (typically 1-3 min for a 90 s source);
|
|
350
363
|
* subsequent requests get 302 to the cached master.m3u8. Keep the
|
|
351
364
|
* progressive MP4 as a fallback source for that window.
|
|
352
365
|
*
|
|
@@ -418,8 +431,8 @@ declare function getTransformSrcSet(asset: Pick<AssetDTO, "sha">, widths: number
|
|
|
418
431
|
* USED for, removing the implicit landscape-only assumption that bit us
|
|
419
432
|
* with vertical product photos.
|
|
420
433
|
*/
|
|
421
|
-
type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "mp3";
|
|
422
|
-
/** 1-char alias used in
|
|
434
|
+
type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "hls" | "mp3";
|
|
435
|
+
/** 1-char alias used in storage keys / wire `presets` string. */
|
|
423
436
|
declare const PRESET_SHORT: Record<VariantPreset, string>;
|
|
424
437
|
declare const PRESET_LONG: Record<string, VariantPreset>;
|
|
425
438
|
/** Variant extension by preset. Image variants are always WebP, video MP4. */
|
|
@@ -435,11 +448,11 @@ type AssetVariant = {
|
|
|
435
448
|
preset: VariantPreset;
|
|
436
449
|
/** Public CDN URL of this variant. */
|
|
437
450
|
url: string;
|
|
438
|
-
/** Pixel width. Absent for `original`-only assets where
|
|
451
|
+
/** Pixel width. Absent for `original`-only assets where image processing was skipped, or for video presets. */
|
|
439
452
|
width?: number;
|
|
440
453
|
/** Pixel height. Same caveat as `width`. */
|
|
441
454
|
height?: number;
|
|
442
|
-
/** Byte size of the variant file
|
|
455
|
+
/** Byte size of the stored variant file. */
|
|
443
456
|
bytes: number;
|
|
444
457
|
/**
|
|
445
458
|
* Where the bytes for this variant came from. Useful for quality
|
|
@@ -453,9 +466,79 @@ type AssetVariant = {
|
|
|
453
466
|
*
|
|
454
467
|
* Absent on variants written before the trace field existed.
|
|
455
468
|
*/
|
|
456
|
-
sourceFrom?: VariantPreset | "upload";
|
|
469
|
+
sourceFrom?: VariantPreset | "upload" | "transform-route" | "hls-transcode" | "cdn-probe";
|
|
457
470
|
/** ISO timestamp this variant was written. Absent on pre-trace variants. */
|
|
458
471
|
createdAt?: string;
|
|
472
|
+
/**
|
|
473
|
+
* **`preset: "hls"` only** — the ladder's rungs, in MASTER ORDER.
|
|
474
|
+
*
|
|
475
|
+
* The reason this exists: an entry that only says *there is a ladder* leaves
|
|
476
|
+
* a consumer that plans a composition exactly as blind as no entry at all,
|
|
477
|
+
* because the ceiling of a composition is its weakest ingredient and there is
|
|
478
|
+
* no upscale. Before this field the only way to learn a clip's real rungs was
|
|
479
|
+
* to fetch the master playlist — or worse, download the asset.
|
|
480
|
+
*
|
|
481
|
+
* `rungs[0]` is the rung every client OPENS on (RFC 8216 §6.3.4 for native
|
|
482
|
+
* HLS; hls.js with `startLevel` unset uses "the first level in the
|
|
483
|
+
* manifest"), so the order is a delivery fact — do not sort it in place.
|
|
484
|
+
*
|
|
485
|
+
* Use {@link hlsLadderAlignment} rather than eyeballing `segments`: a ladder
|
|
486
|
+
* can be complete and still unable to adapt.
|
|
487
|
+
*/
|
|
488
|
+
rungs?: HlsRung[];
|
|
489
|
+
};
|
|
490
|
+
/**
|
|
491
|
+
* One rung of an adaptive HLS ladder.
|
|
492
|
+
*
|
|
493
|
+
* `segments` / `durationSec` are what make a ladder JUDGEABLE rather than
|
|
494
|
+
* merely present. Measured on a prod ladder of an 87 s 4K source: 240p cut at
|
|
495
|
+
* 14.35 s in 12 segments, 720p at 5.63 s in 12, 2160p at 5.88 s in 14. Cuts
|
|
496
|
+
* that do not line up cannot be swapped, and a swap is what a rendition switch
|
|
497
|
+
* IS — so that ladder looked complete and adapted badly. One segment means zero
|
|
498
|
+
* switch points: whichever rung the player opens on is the rung it finishes on.
|
|
499
|
+
*
|
|
500
|
+
* Both are optional because a rung whose playlist could not be read is recorded
|
|
501
|
+
* WITHOUT them rather than with a zero — unmeasured and none are different
|
|
502
|
+
* facts, and a `0` there would read as the latter.
|
|
503
|
+
*/
|
|
504
|
+
type HlsRung = {
|
|
505
|
+
/** Rung directory / identity — `"720p"`. */
|
|
506
|
+
name: string;
|
|
507
|
+
/** `RESOLUTION` from the master playlist. */
|
|
508
|
+
width: number;
|
|
509
|
+
height: number;
|
|
510
|
+
/** `BANDWIDTH` in bits per second. */
|
|
511
|
+
bandwidth: number;
|
|
512
|
+
/** Absolute URL of this rung's media playlist. */
|
|
513
|
+
url: string;
|
|
514
|
+
/** `#EXTINF` count. */
|
|
515
|
+
segments?: number;
|
|
516
|
+
/** Sum of the `#EXTINF` values, seconds. */
|
|
517
|
+
durationSec?: number;
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* The ladder of an asset, or `null` when it has none / the DTO does not carry
|
|
521
|
+
* `variants` (the slim list shape never does — use `hasPreset(a, "hls")` there).
|
|
522
|
+
*/
|
|
523
|
+
declare function getHlsLadder(asset: Pick<AssetDTO, "variants">): AssetVariant | null;
|
|
524
|
+
/**
|
|
525
|
+
* What the recorded rungs actually support — computed here, never stored, so
|
|
526
|
+
* one rule serves every consumer and a change to it does not need a backfill.
|
|
527
|
+
*
|
|
528
|
+
* - `switchable`: more than one rung AND more than one segment. False means the
|
|
529
|
+
* player is pinned to whatever rung it opens on for the entire clip.
|
|
530
|
+
* - `aligned`: every measured rung reports the same segment count. `null` means
|
|
531
|
+
* fewer than two rungs were measured — **unknown, not false.** Treating that
|
|
532
|
+
* as `false` rejects ladders nobody looked at.
|
|
533
|
+
* - `ceilingHeight`: the tallest rung. The ceiling of any composition using it.
|
|
534
|
+
*/
|
|
535
|
+
declare function hlsLadderAlignment(rungs: HlsRung[]): {
|
|
536
|
+
rungCount: number;
|
|
537
|
+
ceilingHeight: number;
|
|
538
|
+
floorHeight: number;
|
|
539
|
+
switchable: boolean;
|
|
540
|
+
aligned: boolean | null;
|
|
541
|
+
minSegments: number | null;
|
|
459
542
|
};
|
|
460
543
|
/**
|
|
461
544
|
* Compact wire shape — what the server actually sends. Aliases (`w`, `h`,
|
|
@@ -559,7 +642,7 @@ declare function getAssetUrl(asset: Pick<AssetDTO, "sha"> & OriginalHints, prese
|
|
|
559
642
|
* Reads `dto.presets` — the compact 1-char code string the server sends on EVERY shape, including
|
|
560
643
|
* the slim list/resolver one that carries no `variants` at all. That is why this exists and why it
|
|
561
644
|
* stays the right existence check even now that `GET /assets/:id` really does send `variants`
|
|
562
|
-
* (it did not until 2026-08-17
|
|
645
|
+
* (it did not until the 2026-08-17 deploy).
|
|
563
646
|
*
|
|
564
647
|
* @example
|
|
565
648
|
* ```ts
|
|
@@ -588,4 +671,4 @@ declare function getAssetDimensions(asset: Pick<AssetDTO, "w" | "h">): {
|
|
|
588
671
|
height: number;
|
|
589
672
|
} | null;
|
|
590
673
|
|
|
591
|
-
export { type AssetDTO, type AssetPalette, type AssetVariant, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type ResolveSlotOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl };
|
|
674
|
+
export { type AssetDTO, type AssetPalette, type AssetVariant, type HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type ResolveSlotOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsLadder, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, hlsLadderAlignment, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -51,14 +51,14 @@ declare function getTextColorForBackground(swatch: PaletteSwatch | string | null
|
|
|
51
51
|
/** Luminancia relativa WCAG de un hex. 0 = negro, 1 = blanco. */
|
|
52
52
|
declare function relativeLuminance(hex: string): number;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* WCAG contrast ratio between two luminances: `(L1 + 0.05) / (L2 + 0.05)`,
|
|
55
|
+
* lighter on top. 1 = identical, 21 = black against white.
|
|
56
56
|
*/
|
|
57
57
|
declare function contrastRatio(l1: number, l2: number): number;
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* Same choice as the recommended text colour, but it also returns **the ratio
|
|
60
|
+
* it achieved** and whether that passes AA — so a caller can decide with the
|
|
61
|
+
* number in front of them instead of assuming it was enough.
|
|
62
62
|
*/
|
|
63
63
|
declare function bestTextContrast(hex: string): {
|
|
64
64
|
color: "#000000" | "#FFFFFF";
|
|
@@ -98,9 +98,9 @@ declare function getPaletteBlurBackground(palette: AssetPalette | null | undefin
|
|
|
98
98
|
* Slots give tenants a way to attach stable, human-readable names
|
|
99
99
|
* ("webapp.wizard.pool-type.icon-1", "storefront.cr.hero-video.landscape_hd_16x9.mp4")
|
|
100
100
|
* to assets they uploaded. Consumers resolve names → AssetDTOs at
|
|
101
|
-
* build / runtime so their source never hardcodes a CDN URL;
|
|
102
|
-
* admin rebinds a slot
|
|
103
|
-
* up the swap on cache refresh.
|
|
101
|
+
* build / runtime so their source never hardcodes a CDN URL; an
|
|
102
|
+
* admin rebinds a slot in the platform console and every consumer
|
|
103
|
+
* picks up the swap on cache refresh.
|
|
104
104
|
*
|
|
105
105
|
* Two layers in this package:
|
|
106
106
|
* - `resolveSlot` / `resolveSlots` — universal (server, edge,
|
|
@@ -137,8 +137,8 @@ type SlotResolution = {
|
|
|
137
137
|
*
|
|
138
138
|
* configureSlotResolver({
|
|
139
139
|
* endpoint: process.env.AQUIENPZ_URL,
|
|
140
|
-
* apiKey: process.env.
|
|
141
|
-
* tenantCode: "
|
|
140
|
+
* apiKey: process.env.AQUIENPZ_API_KEY, // amk_rt_* — server-only
|
|
141
|
+
* tenantCode: "acme-co",
|
|
142
142
|
* });
|
|
143
143
|
*/
|
|
144
144
|
declare function configureSlotResolver(opts: {
|
|
@@ -173,10 +173,9 @@ declare function resolveSlots(slotKeys: string[], opts?: ResolveSlotOptions): Pr
|
|
|
173
173
|
* On-the-fly transform URL builder.
|
|
174
174
|
*
|
|
175
175
|
* Mirrors the server's DSL canonicalizer byte-for-byte so a URL generated
|
|
176
|
-
* here hashes to the same
|
|
176
|
+
* here hashes to the same cache key as the server's canonical form.
|
|
177
177
|
*
|
|
178
|
-
* Canonicalization rules (
|
|
179
|
-
* `apps/asset-manager/src/features/assets/transform.dsl.ts`):
|
|
178
|
+
* Canonicalization rules (kept in sync with the server):
|
|
180
179
|
* - Drop entries whose value is `undefined`
|
|
181
180
|
* - Sort keys alphabetically
|
|
182
181
|
* - Numbers rendered without leading zeros or trailing dots
|
|
@@ -191,7 +190,7 @@ declare function resolveSlots(slotKeys: string[], opts?: ResolveSlotOptions): Pr
|
|
|
191
190
|
*/
|
|
192
191
|
|
|
193
192
|
/**
|
|
194
|
-
* Widths the CDN edge whitelists (DoS guard
|
|
193
|
+
* Widths the CDN edge whitelists (DoS guard).
|
|
195
194
|
* Requesting any OTHER width returns HTTP 400 at the edge (unsigned URLs). These are the
|
|
196
195
|
* 1× base ladder values; DPR ×2/×3 multiples are applied + whitelisted server-side. Import
|
|
197
196
|
* this instead of hardcoding magic widths so an unsupported size is caught in review/IDE.
|
|
@@ -221,9 +220,9 @@ type TransformOptions = {
|
|
|
221
220
|
height?: number;
|
|
222
221
|
/** Resize fit mode. Default `cover` server-side. */
|
|
223
222
|
fit?: TransformFit;
|
|
224
|
-
/** Crop gravity. `auto`
|
|
223
|
+
/** Crop gravity. `auto` picks the region with the most visual salience. */
|
|
225
224
|
gravity?: TransformGravity;
|
|
226
|
-
/** Output format. `auto` → policy decides
|
|
225
|
+
/** Output format. `auto` → the platform's policy decides. */
|
|
227
226
|
format?: TransformFormat;
|
|
228
227
|
/** Output quality. `auto` → format-specific default. */
|
|
229
228
|
quality?: "auto" | number;
|
|
@@ -234,16 +233,15 @@ type TransformOptions = {
|
|
|
234
233
|
*
|
|
235
234
|
* - `removebg`: remove the background; output is a transparent PNG
|
|
236
235
|
* of the foreground subject. Forces `format=png` regardless of
|
|
237
|
-
* other format hints.
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* from R2 — no inference, no per-image cost.
|
|
236
|
+
* other format hints. A single cache miss per (sha, dsl) tuple;
|
|
237
|
+
* subsequent identical DSLs serve from cache — no inference, no
|
|
238
|
+
* per-image cost.
|
|
241
239
|
*
|
|
242
|
-
* - `genfill`: aspect-extension outpaint
|
|
243
|
-
*
|
|
240
|
+
* - `genfill`: aspect-extension outpaint. Requires BOTH `width`
|
|
241
|
+
* and `height` — the server
|
|
244
242
|
* fits the source centered into the target canvas and outpaints
|
|
245
243
|
* the gutters. Output is PNG (forced) at exactly target dims.
|
|
246
|
-
* ~$0.05/image first time; same
|
|
244
|
+
* ~$0.05/image first time; same cache as removebg after.
|
|
247
245
|
* Primary use case: building OG cards (1200×630) from portrait
|
|
248
246
|
* listing photos without awkward edge mirroring.
|
|
249
247
|
*/
|
|
@@ -264,7 +262,7 @@ type TransformOptions = {
|
|
|
264
262
|
* the escape hatch for SIGNED URLs that need an off-ladder custom width.
|
|
265
263
|
*
|
|
266
264
|
* The edge whitelist only rejects off-ladder widths on UNSIGNED URLs; a valid
|
|
267
|
-
* `?sig=` earns the whitelist bypass at the
|
|
265
|
+
* `?sig=` earns the whitelist bypass at the edge (the server still
|
|
268
266
|
* does the real HMAC check). So a custom width is ONLY safe when the URL is
|
|
269
267
|
* signed — hence this type is accepted exclusively by the signing helpers
|
|
270
268
|
* ({@link getSignedTransformUrl} / `aq.transform(asset, opts, { sign: true })`),
|
|
@@ -305,8 +303,8 @@ declare function serializeTransform(opts: SignedTransformOptions): string;
|
|
|
305
303
|
* Build a transform URL for a VIDEO asset. Same DSL shape as image
|
|
306
304
|
* transforms; the server branches on the asset's `kind` column. Video
|
|
307
305
|
* URLs use `.mp4` (default) or `.webm` extension and on cache miss the
|
|
308
|
-
* server returns 202 Accepted while a
|
|
309
|
-
* subsequent GETs return 302 to the cached
|
|
306
|
+
* server returns 202 Accepted while a background job encodes the clip;
|
|
307
|
+
* subsequent GETs return 302 to the cached object.
|
|
310
308
|
*
|
|
311
309
|
* <video src={aq.transformVideo(asset, { width: 1080, height: 1920 })}
|
|
312
310
|
* autoPlay muted loop playsInline />
|
|
@@ -338,15 +336,30 @@ declare function getVideoTransformUrl(asset: Pick<AssetDTO, "sha">, opts: Transf
|
|
|
338
336
|
* if (prefersNativeHls(video)) {
|
|
339
337
|
* video.src = src;
|
|
340
338
|
* } else {
|
|
341
|
-
*
|
|
342
|
-
* const hls = new Hls({ startLevel: -1, testBandwidth: true, abrEwmaDefaultEstimate: 1_000_000 });
|
|
339
|
+
* const hls = new Hls({ capLevelToPlayerSize: false, abrEwmaDefaultEstimate: 5_000_000 });
|
|
343
340
|
* hls.loadSource(src);
|
|
344
341
|
* hls.attachMedia(video);
|
|
345
342
|
* }
|
|
346
343
|
* ```
|
|
347
344
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
345
|
+
* ⚠️ **Do NOT pass `startLevel: -1` with `testBandwidth: true`.** That pair is
|
|
346
|
+
* documented by hls.js as *"forces the player to download a fragment from the
|
|
347
|
+
* lowest level to establish a bandwidth estimate"* — on a clip short enough to
|
|
348
|
+
* be one segment, the probe IS the whole video, and it plays at the bottom
|
|
349
|
+
* rung from first frame to last. (This doc-comment recommended exactly that
|
|
350
|
+
* until 2026-08-18; a 5.042 s 4K asset was measured being delivered at
|
|
351
|
+
* 426x240 because of it.) Leave `startLevel` unset: hls.js then opens on the
|
|
352
|
+
* FIRST level in the manifest, and the server puts the right one there —
|
|
353
|
+
* a mid rung for long video, the top rung for a clip under 18 s, which is the
|
|
354
|
+
* same rung native HLS opens on per RFC 8216 §6.3.4. The ladder decides; the
|
|
355
|
+
* player should not second-guess it.
|
|
356
|
+
*
|
|
357
|
+
* `capLevelToPlayerSize` is worth disabling explicitly: `@videojs/core`
|
|
358
|
+
* defaults it to `true`, which caps quality to the player's rendered pixel box,
|
|
359
|
+
* so a small inline player is pinned to 240p/360p on any connection.
|
|
360
|
+
*
|
|
361
|
+
* On first request the server returns 202 Accepted while a background
|
|
362
|
+
* job transcodes the ladder (typically 1-3 min for a 90 s source);
|
|
350
363
|
* subsequent requests get 302 to the cached master.m3u8. Keep the
|
|
351
364
|
* progressive MP4 as a fallback source for that window.
|
|
352
365
|
*
|
|
@@ -418,8 +431,8 @@ declare function getTransformSrcSet(asset: Pick<AssetDTO, "sha">, widths: number
|
|
|
418
431
|
* USED for, removing the implicit landscape-only assumption that bit us
|
|
419
432
|
* with vertical product photos.
|
|
420
433
|
*/
|
|
421
|
-
type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "mp3";
|
|
422
|
-
/** 1-char alias used in
|
|
434
|
+
type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "hls" | "mp3";
|
|
435
|
+
/** 1-char alias used in storage keys / wire `presets` string. */
|
|
423
436
|
declare const PRESET_SHORT: Record<VariantPreset, string>;
|
|
424
437
|
declare const PRESET_LONG: Record<string, VariantPreset>;
|
|
425
438
|
/** Variant extension by preset. Image variants are always WebP, video MP4. */
|
|
@@ -435,11 +448,11 @@ type AssetVariant = {
|
|
|
435
448
|
preset: VariantPreset;
|
|
436
449
|
/** Public CDN URL of this variant. */
|
|
437
450
|
url: string;
|
|
438
|
-
/** Pixel width. Absent for `original`-only assets where
|
|
451
|
+
/** Pixel width. Absent for `original`-only assets where image processing was skipped, or for video presets. */
|
|
439
452
|
width?: number;
|
|
440
453
|
/** Pixel height. Same caveat as `width`. */
|
|
441
454
|
height?: number;
|
|
442
|
-
/** Byte size of the variant file
|
|
455
|
+
/** Byte size of the stored variant file. */
|
|
443
456
|
bytes: number;
|
|
444
457
|
/**
|
|
445
458
|
* Where the bytes for this variant came from. Useful for quality
|
|
@@ -453,9 +466,79 @@ type AssetVariant = {
|
|
|
453
466
|
*
|
|
454
467
|
* Absent on variants written before the trace field existed.
|
|
455
468
|
*/
|
|
456
|
-
sourceFrom?: VariantPreset | "upload";
|
|
469
|
+
sourceFrom?: VariantPreset | "upload" | "transform-route" | "hls-transcode" | "cdn-probe";
|
|
457
470
|
/** ISO timestamp this variant was written. Absent on pre-trace variants. */
|
|
458
471
|
createdAt?: string;
|
|
472
|
+
/**
|
|
473
|
+
* **`preset: "hls"` only** — the ladder's rungs, in MASTER ORDER.
|
|
474
|
+
*
|
|
475
|
+
* The reason this exists: an entry that only says *there is a ladder* leaves
|
|
476
|
+
* a consumer that plans a composition exactly as blind as no entry at all,
|
|
477
|
+
* because the ceiling of a composition is its weakest ingredient and there is
|
|
478
|
+
* no upscale. Before this field the only way to learn a clip's real rungs was
|
|
479
|
+
* to fetch the master playlist — or worse, download the asset.
|
|
480
|
+
*
|
|
481
|
+
* `rungs[0]` is the rung every client OPENS on (RFC 8216 §6.3.4 for native
|
|
482
|
+
* HLS; hls.js with `startLevel` unset uses "the first level in the
|
|
483
|
+
* manifest"), so the order is a delivery fact — do not sort it in place.
|
|
484
|
+
*
|
|
485
|
+
* Use {@link hlsLadderAlignment} rather than eyeballing `segments`: a ladder
|
|
486
|
+
* can be complete and still unable to adapt.
|
|
487
|
+
*/
|
|
488
|
+
rungs?: HlsRung[];
|
|
489
|
+
};
|
|
490
|
+
/**
|
|
491
|
+
* One rung of an adaptive HLS ladder.
|
|
492
|
+
*
|
|
493
|
+
* `segments` / `durationSec` are what make a ladder JUDGEABLE rather than
|
|
494
|
+
* merely present. Measured on a prod ladder of an 87 s 4K source: 240p cut at
|
|
495
|
+
* 14.35 s in 12 segments, 720p at 5.63 s in 12, 2160p at 5.88 s in 14. Cuts
|
|
496
|
+
* that do not line up cannot be swapped, and a swap is what a rendition switch
|
|
497
|
+
* IS — so that ladder looked complete and adapted badly. One segment means zero
|
|
498
|
+
* switch points: whichever rung the player opens on is the rung it finishes on.
|
|
499
|
+
*
|
|
500
|
+
* Both are optional because a rung whose playlist could not be read is recorded
|
|
501
|
+
* WITHOUT them rather than with a zero — unmeasured and none are different
|
|
502
|
+
* facts, and a `0` there would read as the latter.
|
|
503
|
+
*/
|
|
504
|
+
type HlsRung = {
|
|
505
|
+
/** Rung directory / identity — `"720p"`. */
|
|
506
|
+
name: string;
|
|
507
|
+
/** `RESOLUTION` from the master playlist. */
|
|
508
|
+
width: number;
|
|
509
|
+
height: number;
|
|
510
|
+
/** `BANDWIDTH` in bits per second. */
|
|
511
|
+
bandwidth: number;
|
|
512
|
+
/** Absolute URL of this rung's media playlist. */
|
|
513
|
+
url: string;
|
|
514
|
+
/** `#EXTINF` count. */
|
|
515
|
+
segments?: number;
|
|
516
|
+
/** Sum of the `#EXTINF` values, seconds. */
|
|
517
|
+
durationSec?: number;
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* The ladder of an asset, or `null` when it has none / the DTO does not carry
|
|
521
|
+
* `variants` (the slim list shape never does — use `hasPreset(a, "hls")` there).
|
|
522
|
+
*/
|
|
523
|
+
declare function getHlsLadder(asset: Pick<AssetDTO, "variants">): AssetVariant | null;
|
|
524
|
+
/**
|
|
525
|
+
* What the recorded rungs actually support — computed here, never stored, so
|
|
526
|
+
* one rule serves every consumer and a change to it does not need a backfill.
|
|
527
|
+
*
|
|
528
|
+
* - `switchable`: more than one rung AND more than one segment. False means the
|
|
529
|
+
* player is pinned to whatever rung it opens on for the entire clip.
|
|
530
|
+
* - `aligned`: every measured rung reports the same segment count. `null` means
|
|
531
|
+
* fewer than two rungs were measured — **unknown, not false.** Treating that
|
|
532
|
+
* as `false` rejects ladders nobody looked at.
|
|
533
|
+
* - `ceilingHeight`: the tallest rung. The ceiling of any composition using it.
|
|
534
|
+
*/
|
|
535
|
+
declare function hlsLadderAlignment(rungs: HlsRung[]): {
|
|
536
|
+
rungCount: number;
|
|
537
|
+
ceilingHeight: number;
|
|
538
|
+
floorHeight: number;
|
|
539
|
+
switchable: boolean;
|
|
540
|
+
aligned: boolean | null;
|
|
541
|
+
minSegments: number | null;
|
|
459
542
|
};
|
|
460
543
|
/**
|
|
461
544
|
* Compact wire shape — what the server actually sends. Aliases (`w`, `h`,
|
|
@@ -559,7 +642,7 @@ declare function getAssetUrl(asset: Pick<AssetDTO, "sha"> & OriginalHints, prese
|
|
|
559
642
|
* Reads `dto.presets` — the compact 1-char code string the server sends on EVERY shape, including
|
|
560
643
|
* the slim list/resolver one that carries no `variants` at all. That is why this exists and why it
|
|
561
644
|
* stays the right existence check even now that `GET /assets/:id` really does send `variants`
|
|
562
|
-
* (it did not until 2026-08-17
|
|
645
|
+
* (it did not until the 2026-08-17 deploy).
|
|
563
646
|
*
|
|
564
647
|
* @example
|
|
565
648
|
* ```ts
|
|
@@ -588,4 +671,4 @@ declare function getAssetDimensions(asset: Pick<AssetDTO, "w" | "h">): {
|
|
|
588
671
|
height: number;
|
|
589
672
|
} | null;
|
|
590
673
|
|
|
591
|
-
export { type AssetDTO, type AssetPalette, type AssetVariant, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type ResolveSlotOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl };
|
|
674
|
+
export { type AssetDTO, type AssetPalette, type AssetVariant, type HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type ResolveSlotOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsLadder, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, hlsLadderAlignment, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl };
|