@nitida/asset-client 0.16.4 → 0.18.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/dist/index.d.ts CHANGED
@@ -1,3 +1,91 @@
1
+ /**
2
+ * Signed URLs for PRIVATE assets — the `/a/{tenant}/…?exp&sig` tree.
3
+ *
4
+ * ## Who calls this, and who must not
5
+ *
6
+ * The tenant's BACKEND, which knows who the viewer is and holds the signing
7
+ * key. Never a browser: shipping the signing key to the client would let any
8
+ * visitor mint URLs for any private asset of that tenant, which is the whole
9
+ * property the tree exists to provide. This module is deliberately importable
10
+ * from anywhere — it runs on WebCrypto, so browsers, Node, Bun and Workers all
11
+ * work — and that convenience is exactly why the warning is here rather than
12
+ * in a doc nobody reads at the call site.
13
+ *
14
+ * ## Not the same signature as `signTransformUrl`
15
+ *
16
+ * `signTransformUrl` vouches for a WIDTH; this vouches for a VIEWER, until
17
+ * `exp`. They come from the same `signing_key` but not the same key material:
18
+ * the access key is derived (`HMAC(signing_key, "nitida/access/v1")`) so that
19
+ * no crafted transform path can be replayed as an access signature. The full
20
+ * argument lives beside the server implementation in `access-signing.ts`; the
21
+ * short version is that a shared payload with a prefix separator IS
22
+ * collidable, because both fields of the transform message are
23
+ * attacker-influenced path segments.
24
+ *
25
+ * ## `exp` is mandatory
26
+ *
27
+ * A signed URL that never expires is a public URL as soon as someone forwards
28
+ * it. There is no "no expiry" option here, and there will not be one.
29
+ */
30
+ /**
31
+ * The per-tenant access key, derived from `signing_key`. Same value the origin
32
+ * computes and hands the edge — nothing needs to be stored or synchronised.
33
+ */
34
+ declare function deriveAccessKey(signingKey: string): Promise<Uint8Array>;
35
+ /**
36
+ * The signed message. Byte-identical to the server's `accessMessage` and the
37
+ * worker's — three implementations of one string, which is why all three pin
38
+ * the exact bytes in a test.
39
+ *
40
+ * `tenantPrefix` is the base36 segment as it appears in the URL, not the
41
+ * decimal id: tenant 10 lives at `/a/a/`, and the thing being vouched for is a
42
+ * path.
43
+ */
44
+ declare function accessMessage(tenantPrefix: string, exp: number, resourcePath: string): string;
45
+ type SignAccessOptions = {
46
+ /** Lifetime in seconds. Required — see the header. */
47
+ expiresInSeconds: number;
48
+ /** Injectable clock, for tests that need a URL already dead on arrival. */
49
+ nowSeconds?: number;
50
+ };
51
+ /**
52
+ * Turn a PUBLIC-tree URL into a signed PRIVATE-tree URL.
53
+ *
54
+ * https://8ok.uk/5/v/<sha16>-lg.webp
55
+ * → https://8ok.uk/a/5/v/<sha16>-lg.webp?exp=…&sig=…
56
+ *
57
+ * Accepts a URL that is already under `/a/` and re-signs it, so calling twice
58
+ * is not an error and does not produce `/a/a/`.
59
+ */
60
+ declare function signAccessUrl(publicUrl: string, signingKey: string, opts: SignAccessOptions): Promise<string>;
61
+ /** What every URL builder accepts so it can refuse a doomed URL. */
62
+ type VisibilityHint = {
63
+ visibility?: "public" | "private";
64
+ };
65
+ /**
66
+ * Refuse to build a public URL for a private asset.
67
+ *
68
+ * Doctrine of the house: **a silence reads as "you can't"**. Returning
69
+ * `https://8ok.uk/5/v/<sha>-lg.webp` for a private asset is not a smaller
70
+ * failure than throwing — it is a URL that answers 404, in a platform where a
71
+ * 404 has always meant "that file does not exist". The caller then debugs the
72
+ * wrong thing.
73
+ *
74
+ * Only refuses when it was actually TOLD. A caller passing `{ sha }` carries no
75
+ * visibility, and guessing would break every existing call site to protect
76
+ * assets that are not there.
77
+ */
78
+ declare function assertPublic(asset: VisibilityHint, fn: string,
79
+ /**
80
+ * The call to make instead — declared per call site, not guessed.
81
+ *
82
+ * It matters which one: `getPrivateAssetUrl` signs a STORED preset, and
83
+ * pointing a transform caller at it sends them to a function that cannot do
84
+ * what they asked for. The first version of this message named
85
+ * `getPrivateAssetUrl` for all seven builders; a test caught it.
86
+ */
87
+ escape: string): void;
88
+
1
89
  /**
2
90
  * Color palette helpers — render harmonious ambient backgrounds behind
3
91
  * product images, inspired by Spotify Now Playing / Apple Music / Pico.
@@ -92,83 +180,6 @@ declare function iteratePaletteSwatches(palette: AssetPalette | null | undefined
92
180
  */
93
181
  declare function getPaletteBlurBackground(palette: AssetPalette | null | undefined): string | undefined;
94
182
 
95
- /**
96
- * @nitida/asset-client/slots — slot resolver for tenant-named assets.
97
- *
98
- * Slots give tenants a way to attach stable, human-readable names
99
- * ("webapp.wizard.pool-type.icon-1", "storefront.cr.hero-video.landscape_hd_16x9.mp4")
100
- * to assets they uploaded. Consumers resolve names → AssetDTOs at
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
- *
105
- * Two layers in this package:
106
- * - `resolveSlot` / `resolveSlots` — universal (server, edge,
107
- * workers) fetch helpers. Cache 60s by default.
108
- * - React hooks live in `@nitida/asset-client/react/use-slot`
109
- * (kept out of this module so the SSR-safe core stays
110
- * dependency-free of react).
111
- */
112
-
113
- type SlotDTO = {
114
- slotKey: string;
115
- /** Preset hint set when the slot was bound (e.g. `thumb` for icon slots). */
116
- preset: VariantPreset | null;
117
- description: string | null;
118
- updatedAt: string;
119
- asset: AssetDTO;
120
- };
121
- type SlotResolution = {
122
- /** The resolved DTO (`null` when the slot is unbound or asset missing). */
123
- slot: SlotDTO | null;
124
- /**
125
- * Effective preset — what `url` below was built with. Resolution order:
126
- * 1. caller's `preset` override
127
- * 2. slot's `preset` hint
128
- * 3. `lg` for images, `video` for video kind
129
- */
130
- preset: VariantPreset;
131
- /** The CDN URL the consumer should use. */
132
- url: string | null;
133
- };
134
- /**
135
- * Configure the resolver process-wide. Call once at boot from your
136
- * storefront layout / server entry / worker init.
137
- *
138
- * configureSlotResolver({
139
- * endpoint: process.env.AQUIENPZ_URL,
140
- * apiKey: process.env.AQUIENPZ_API_KEY, // amk_rt_* — server-only
141
- * tenantCode: "acme-co",
142
- * });
143
- */
144
- declare function configureSlotResolver(opts: {
145
- endpoint?: string;
146
- apiKey?: string;
147
- tenantCode?: string;
148
- }): void;
149
- /** Wipe the in-process cache (test helper or forced refresh). */
150
- declare function invalidateSlotCache(slotKey?: string): void;
151
- type ResolveSlotOptions = {
152
- /** Override preset (caller knows the use case better than the slot binding). */
153
- preset?: VariantPreset;
154
- /** TTL for the in-process cache. Default 60s. Set 0 to bypass. */
155
- ttlMs?: number;
156
- };
157
- /**
158
- * Resolve a single slot to a CDN URL. Returns `{slot: null, url: null}`
159
- * when the slot is unbound — callers fall back to a placeholder.
160
- *
161
- * Cached for `ttlMs` (default 60s). Slot rebindings propagate within the
162
- * TTL window without an app restart.
163
- */
164
- declare function resolveSlot(slotKey: string, opts?: ResolveSlotOptions): Promise<SlotResolution>;
165
- /**
166
- * Bulk-resolve N slot keys in one round-trip. The SDK's `useSlots`
167
- * React hook calls this so every storefront header (logo + tagline +
168
- * nav cover + …) loads as one request.
169
- */
170
- declare function resolveSlots(slotKeys: string[], opts?: ResolveSlotOptions): Promise<Record<string, SlotResolution>>;
171
-
172
183
  /**
173
184
  * On-the-fly transform URL builder.
174
185
  *
@@ -309,7 +320,7 @@ declare function serializeTransform(opts: SignedTransformOptions): string;
309
320
  * <video src={aq.transformVideo(asset, { width: 1080, height: 1920 })}
310
321
  * autoPlay muted loop playsInline />
311
322
  */
312
- declare function getVideoTransformUrl(asset: Pick<AssetDTO, "sha">, opts: TransformOptions): string | null;
323
+ declare function getVideoTransformUrl(asset: Pick<AssetDTO, "sha"> & VisibilityHint, opts: TransformOptions): string | null;
313
324
  /**
314
325
  * Build an HLS streaming URL for a VIDEO asset (Phase 5). Returns the
315
326
  * master.m3u8 entry point — HLS-aware players (Video.js's
@@ -368,8 +379,9 @@ declare function getVideoTransformUrl(asset: Pick<AssetDTO, "sha">, opts: Transf
368
379
  * on demand after the raw is unavailable it reads the `-v.mp4`, which is
369
380
  * capped at 1920 wide. `getAssetUrl(sha, "video")` is always <= 1080p.
370
381
  */
371
- declare function getHlsStreamingUrl(asset: Pick<AssetDTO, "sha">, opts?: Omit<TransformOptions, "format">): string;
372
- declare function getTransformUrl(asset: Pick<AssetDTO, "sha">, opts: TransformOptions): string | null;
382
+ declare function getHlsStreamingUrl(asset: Pick<AssetDTO, "sha"> & VisibilityHint, opts?: Omit<TransformOptions, "format">): string;
383
+
384
+ declare function getTransformUrl(asset: Pick<AssetDTO, "sha"> & VisibilityHint, opts: TransformOptions): string | null;
373
385
  /**
374
386
  * Build AND sign a transform URL, allowing an off-ladder custom `width`.
375
387
  *
@@ -381,7 +393,7 @@ declare function getTransformUrl(asset: Pick<AssetDTO, "sha">, opts: TransformOp
381
393
  * Returns `null` only when `opts` serialize to an empty DSL (no transform
382
394
  * requested) — same contract as {@link getTransformUrl}.
383
395
  */
384
- declare function getSignedTransformUrl(asset: Pick<AssetDTO, "sha">, opts: SignedTransformOptions, signingKey: string): Promise<string> | null;
396
+ declare function getSignedTransformUrl(asset: Pick<AssetDTO, "sha"> & VisibilityHint, opts: SignedTransformOptions, signingKey: string): Promise<string> | null;
385
397
  /**
386
398
  * Sign a transform URL with the tenant's HMAC signing key. Appends
387
399
  * `?sig=<hex>` where hex = HMAC-SHA256(signingKey, `<canonical-DSL>/<filename>`).
@@ -404,7 +416,84 @@ declare function signTransformUrl(unsignedUrl: string, signingKey: string): Prom
404
416
  * sizes="(max-width: 768px) 100vw, 50vw"
405
417
  * />
406
418
  */
407
- declare function getTransformSrcSet(asset: Pick<AssetDTO, "sha">, widths: number[], extraOpts?: Omit<TransformOptions, "width">): string;
419
+ declare function getTransformSrcSet(asset: Pick<AssetDTO, "sha"> & VisibilityHint, widths: number[], extraOpts?: Omit<TransformOptions, "width">): string;
420
+
421
+ /**
422
+ * @nitida/asset-client/slots — slot resolver for tenant-named assets.
423
+ *
424
+ * Slots give tenants a way to attach stable, human-readable names
425
+ * ("webapp.wizard.pool-type.icon-1", "storefront.cr.hero-video.landscape_hd_16x9.mp4")
426
+ * to assets they uploaded. Consumers resolve names → AssetDTOs at
427
+ * build / runtime so their source never hardcodes a CDN URL; an
428
+ * admin rebinds a slot in the platform console and every consumer
429
+ * picks up the swap on cache refresh.
430
+ *
431
+ * Two layers in this package:
432
+ * - `resolveSlot` / `resolveSlots` — universal (server, edge,
433
+ * workers) fetch helpers. Cache 60s by default.
434
+ * - React hooks live in `@nitida/asset-client/react/use-slot`
435
+ * (kept out of this module so the SSR-safe core stays
436
+ * dependency-free of react).
437
+ */
438
+
439
+ type SlotDTO = {
440
+ slotKey: string;
441
+ /** Preset hint set when the slot was bound (e.g. `thumb` for icon slots). */
442
+ preset: VariantPreset | null;
443
+ description: string | null;
444
+ updatedAt: string;
445
+ asset: AssetDTO;
446
+ };
447
+ type SlotResolution = {
448
+ /** The resolved DTO (`null` when the slot is unbound or asset missing). */
449
+ slot: SlotDTO | null;
450
+ /**
451
+ * Effective preset — what `url` below was built with. Resolution order:
452
+ * 1. caller's `preset` override
453
+ * 2. slot's `preset` hint
454
+ * 3. `lg` for images, `video` for video kind
455
+ */
456
+ preset: VariantPreset;
457
+ /** The CDN URL the consumer should use. */
458
+ url: string | null;
459
+ };
460
+ /**
461
+ * Configure the resolver process-wide. Call once at boot from your
462
+ * storefront layout / server entry / worker init.
463
+ *
464
+ * configureSlotResolver({
465
+ * endpoint: process.env.AQUIENPZ_URL,
466
+ * apiKey: process.env.AQUIENPZ_API_KEY, // amk_rt_* — server-only
467
+ * tenantCode: "acme-co",
468
+ * });
469
+ */
470
+ declare function configureSlotResolver(opts: {
471
+ endpoint?: string;
472
+ apiKey?: string;
473
+ tenantCode?: string;
474
+ }): void;
475
+ /** Wipe the in-process cache (test helper or forced refresh). */
476
+ declare function invalidateSlotCache(slotKey?: string): void;
477
+ type ResolveSlotOptions = {
478
+ /** Override preset (caller knows the use case better than the slot binding). */
479
+ preset?: VariantPreset;
480
+ /** TTL for the in-process cache. Default 60s. Set 0 to bypass. */
481
+ ttlMs?: number;
482
+ };
483
+ /**
484
+ * Resolve a single slot to a CDN URL. Returns `{slot: null, url: null}`
485
+ * when the slot is unbound — callers fall back to a placeholder.
486
+ *
487
+ * Cached for `ttlMs` (default 60s). Slot rebindings propagate within the
488
+ * TTL window without an app restart.
489
+ */
490
+ declare function resolveSlot(slotKey: string, opts?: ResolveSlotOptions): Promise<SlotResolution>;
491
+ /**
492
+ * Bulk-resolve N slot keys in one round-trip. The SDK's `useSlots`
493
+ * React hook calls this so every storefront header (logo + tagline +
494
+ * nav cover + …) loads as one request.
495
+ */
496
+ declare function resolveSlots(slotKeys: string[], opts?: ResolveSlotOptions): Promise<Record<string, SlotResolution>>;
408
497
 
409
498
  /**
410
499
  * @nitida/asset-client — read helpers for asset URLs.
@@ -432,6 +521,31 @@ declare function getTransformSrcSet(asset: Pick<AssetDTO, "sha">, widths: number
432
521
  * with vertical product photos.
433
522
  */
434
523
  type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "hls" | "mp3";
524
+ /**
525
+ * What you may ASK the server to produce.
526
+ *
527
+ * NOT the same set as {@link VariantPreset}, and conflating the two is the
528
+ * single most expensive type error this package has shipped. Three unknown
529
+ * agents evaluating the SDK all hit it, independently, in the same afternoon:
530
+ *
531
+ * regenerate(id, { presets: ["hls"] }) // compiled → HTTP 400
532
+ * upload(file, { presets: ["mp3"] }) // compiled → HTTP 400
533
+ *
534
+ * Both symbols are perfectly real — they are things a variant CAN BE. Neither
535
+ * is something you can ASK FOR. `hls` is built by the video pipeline when a
536
+ * video is transcoded; `mp3` is emitted automatically alongside any audio
537
+ * original so iOS Safari can play it. You do not order either one.
538
+ *
539
+ * And it was wrong in the other direction too, which nobody had noticed:
540
+ * **`probe` is requestable and was not on `VariantPreset` at all**, so the type
541
+ * forbade a request the server has always accepted.
542
+ *
543
+ * Verified 2026-08-21 against the Elysia schemas of all four write routes —
544
+ * `/assets/process`, the presign route, `/assets/:id/regenerate` and both
545
+ * multipart routes. All four accept exactly this list and nothing else, with
546
+ * no drift between them.
547
+ */
548
+ type RequestablePreset = Exclude<VariantPreset, "hls" | "mp3"> | "probe";
435
549
  /** 1-char alias used in storage keys / wire `presets` string. */
436
550
  declare const PRESET_SHORT: Record<VariantPreset, string>;
437
551
  declare const PRESET_LONG: Record<string, VariantPreset>;
@@ -443,9 +557,44 @@ declare const PRESET_MAX_DIM: Record<VariantPreset, number | null>;
443
557
  * One generated variant of an asset. Returned by the admin endpoints
444
558
  * (`GET /assets/:id`, `POST /assets/:id/regenerate`).
445
559
  */
560
+ /**
561
+ * What `AssetVariant.preset` can actually hold.
562
+ *
563
+ * ⚠️ NOT `VariantPreset`, and the difference is a real bug the type used to
564
+ * hide. Measured against the live API on 2026-08-21, one asset came back with
565
+ * **29 variants, 25 of them `transform-<hash>`** — 86 % of the array — while the
566
+ * type said every entry was one of eleven known presets. So this compiles:
567
+ *
568
+ * ```ts
569
+ * for (const v of asset.variants ?? []) getAssetUrl(asset, v.preset);
570
+ * ```
571
+ *
572
+ * `tsc` exits 0, and at runtime 25 of those 29 URLs come out as
573
+ * `<sha>-undefined.undefined` and answer 404, because `PRESET_SHORT[preset]`
574
+ * and `PRESET_EXT[preset]` are `undefined` for a hash that is not a preset.
575
+ *
576
+ * The `transform-*` entries are NOT junk and are not being removed: they are
577
+ * the materialised cache of past on-demand requests — still ready, still free
578
+ * to fetch — and the inventory model treats them as a first-class
579
+ * `transform-cache` family, which is exactly the question a composer asks
580
+ * ("what can I fetch cheaply right now?"). Deleting them would destroy that.
581
+ *
582
+ * So the type tells the truth instead. `(string & {})` keeps autocomplete on
583
+ * the known presets while admitting the rest, and a caller that wants to build
584
+ * a URL now has to narrow first — which is the whole point.
585
+ */
586
+ type VariantEntryPreset = VariantPreset
587
+ /** Indexed stills (`-pr0.jpg`, …). Requestable, never on the compact string. */
588
+ | "probe"
589
+ /** `transform-<dslHash>` and `upscale_*` — materialised cache, not a rung. */
590
+ | (string & {});
446
591
  type AssetVariant = {
447
- /** Long name (`thumb` / `sm` / … / `original`) — see {@link VariantPreset}. */
448
- preset: VariantPreset;
592
+ /**
593
+ * What this entry IS. Usually a named preset; can also be a
594
+ * `transform-<hash>` cache artifact — see {@link VariantEntryPreset} before
595
+ * passing it to {@link getAssetUrl}.
596
+ */
597
+ preset: VariantEntryPreset;
449
598
  /** Public CDN URL of this variant. */
450
599
  url: string;
451
600
  /** Pixel width. Absent for `original`-only assets where image processing was skipped, or for video presets. */
@@ -566,6 +715,23 @@ type AssetDTO = {
566
715
  */
567
716
  presets: string;
568
717
  status: "processing" | "ready" | "failed";
718
+ /**
719
+ * Who may fetch the bytes.
720
+ *
721
+ * - `"public"` — the CDN serves it to anyone with the URL. The default,
722
+ * and what all 27 484 assets were until this field existed.
723
+ * - `"private"` — every public door answers **404**: the stored variants,
724
+ * the raw original, the HLS ladder and `/t/`. The bytes are reachable
725
+ * only through a signed URL under `/a/{tenant}/…?exp&sig`, which your
726
+ * BACKEND mints with {@link getPrivateAssetUrl}.
727
+ *
728
+ * Optional so an older server that does not send it is read as `"public"` —
729
+ * which is what such a server means.
730
+ *
731
+ * ⚠️ A 404 on a private asset is not a missing file. It is the feature
732
+ * working. See {@link getPrivateAssetUrl}.
733
+ */
734
+ visibility?: "public" | "private";
569
735
  /** Soft-delete timestamp (ISO). Hidden from catalog when set. */
570
736
  deletedAt?: string | null;
571
737
  /**
@@ -635,7 +801,59 @@ type OriginalHints = {
635
801
  * const url = hasPreset(asset, "thumb") ? getAssetUrl(asset, "thumb") : null;
636
802
  * ```
637
803
  */
638
- declare function getAssetUrl(asset: Pick<AssetDTO, "sha"> & OriginalHints, preset: VariantPreset): string;
804
+ declare function getAssetUrl(asset: Pick<AssetDTO, "sha"> & OriginalHints & VisibilityHint, preset: VariantPreset): string;
805
+ /**
806
+ * The signed URL for one preset of a PRIVATE asset — what every refusal above
807
+ * points at.
808
+ *
809
+ * ```ts
810
+ * // On your BACKEND, once you have decided this viewer may see it:
811
+ * const url = await getPrivateAssetUrl(asset, "lg", tenantSigningKey, {
812
+ * expiresInSeconds: 300,
813
+ * });
814
+ * ```
815
+ *
816
+ * It works on a public asset too — `/a/` is a different door onto the same
817
+ * object — but there is no reason to pay for it: a public URL is cacheable at
818
+ * the edge and costs nothing, a signed one is neither.
819
+ *
820
+ * ⚠️ **Backend only.** Handing the signing key to a browser lets any visitor
821
+ * mint URLs for every private asset the tenant owns, which is the whole
822
+ * property the private tree exists to provide.
823
+ *
824
+ * ⚠️ Needs {@link setTenantId} (or a `NitidaClient` with `tenantId`), like
825
+ * every variant URL builder: the tenant segment is base36 and part of what the
826
+ * signature covers, so a missing tenant does not produce a wrong URL — it
827
+ * produces an unsignable one.
828
+ */
829
+ declare function getPrivateAssetUrl(asset: Pick<AssetDTO, "sha"> & OriginalHints, preset: VariantPreset, signingKey: string, opts: SignAccessOptions): Promise<string>;
830
+ /**
831
+ * The signed URL for a TRANSFORM of a private asset — an arbitrary width, crop
832
+ * or format, not just the sizes that happen to be materialised.
833
+ *
834
+ * ```ts
835
+ * const url = await getPrivateTransformUrl(
836
+ * asset,
837
+ * { width: 1280, format: "webp" },
838
+ * tenantSigningKey,
839
+ * { expiresInSeconds: 300 },
840
+ * );
841
+ * // → https://8ok.uk/a/5/t/format=webp,width=1280/<sha>.webp?exp=…&sig=…
842
+ * ```
843
+ *
844
+ * Why this exists at all: a private asset that can only be served at the sizes
845
+ * someone already generated is barely a product. The signed tree mirrors the
846
+ * public one, transforms included.
847
+ *
848
+ * Returns `null` when `opts` serialize to an empty DSL — same contract as
849
+ * {@link getTransformUrl}, because "no transform requested" is not an error,
850
+ * it just means you wanted {@link getPrivateAssetUrl}.
851
+ *
852
+ * ⚠️ **Backend only**, like every signer here. And note the width is a plain
853
+ * `number`: a signed URL is a trusted caller, so the edge ladder does not
854
+ * apply — the same rule `getSignedTransformUrl` already follows.
855
+ */
856
+ declare function getPrivateTransformUrl(asset: Pick<AssetDTO, "sha">, opts: SignedTransformOptions, signingKey: string, signOpts: SignAccessOptions): Promise<string | null>;
639
857
  /**
640
858
  * Did the processor actually generate this preset?
641
859
  *
@@ -654,7 +872,7 @@ declare function getAssetUrl(asset: Pick<AssetDTO, "sha"> & OriginalHints, prese
654
872
  * ```
655
873
  */
656
874
  declare function hasPreset(asset: Pick<AssetDTO, "presets">, preset: VariantPreset): boolean;
657
- declare function getAssetSrcSet(asset: Pick<AssetDTO, "sha" | "presets">): string;
875
+ declare function getAssetSrcSet(asset: Pick<AssetDTO, "sha" | "presets"> & VisibilityHint): string;
658
876
  /**
659
877
  * Compute the dimensions a variant would have given the source asset's
660
878
  * width/height and the variant's bounding box. For thumbnails (square
@@ -671,4 +889,4 @@ declare function getAssetDimensions(asset: Pick<AssetDTO, "w" | "h">): {
671
889
  height: number;
672
890
  } | null;
673
891
 
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 };
892
+ export { type AssetDTO, type AssetPalette, type AssetVariant, type HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type RequestablePreset, type ResolveSlotOptions, type SignAccessOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantEntryPreset, type VariantPreset, type VisibilityHint, accessMessage, assertPublic, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, deriveAccessKey, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsLadder, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getPrivateAssetUrl, getPrivateTransformUrl, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, hlsLadderAlignment, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signAccessUrl, signTransformUrl };