@nitida/asset-client 0.18.0 → 0.19.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/AGENTS.md CHANGED
@@ -91,6 +91,22 @@ await getPrivateTransformUrl(asset, { width: 1280 }, signingKey, { expiresInSeco
91
91
  - **`exp` is mandatory**; revocation is *"within a minute"* (60 s TTL at the edge).
92
92
  - **The signing key is a backend secret** — it mints URLs for every private
93
93
  asset the tenant owns.
94
+ - **⭐ Where the signing key comes from: the response that CREATED your
95
+ project, once.** `POST /admin/projects` returns `signingKey` next to the
96
+ three API keys, and the console shows it in the same panel. Nothing else
97
+ hands it out — `GET /admin/projects/:code` does **not** include it. If it is
98
+ lost, the only endpoint that returns a key is
99
+ `POST /admin/projects/:code/rotate-signing-key`, which **invalidates every
100
+ URL already signed** and needs the system-scope key the platform operator
101
+ holds. A tenant with nothing signed yet can rotate for free; a live one
102
+ cannot, which is why you save it at creation.
103
+ - **⭐ Already have a project and never saw a signing key?** Then you never got
104
+ one — projects created before 2026-08-23 were not handed it, and no endpoint
105
+ shows you the current one. **If you have not signed any URLs yet, ask us to
106
+ rotate: with nothing in flight, rotation invalidates nothing and is free.**
107
+ If you already have signed URLs circulating, ask us for the current key
108
+ instead. This is the wall the T5 agent hit, and the sentence that was
109
+ missing.
94
110
 
95
111
  ## The endpoint
96
112
 
package/dist/index.cjs CHANGED
@@ -24,9 +24,11 @@ __export(index_exports, {
24
24
  PRESET_LONG: () => PRESET_LONG,
25
25
  PRESET_MAX_DIM: () => PRESET_MAX_DIM,
26
26
  PRESET_SHORT: () => PRESET_SHORT,
27
+ REQUESTABLE_PRESETS: () => REQUESTABLE_PRESETS,
27
28
  TRANSFORM_WIDTHS: () => TRANSFORM_WIDTHS,
28
29
  accessMessage: () => accessMessage,
29
30
  assertPublic: () => assertPublic,
31
+ assertSha: () => assertSha,
30
32
  bestTextContrast: () => bestTextContrast,
31
33
  computeVariantDimensions: () => computeVariantDimensions,
32
34
  configureSlotResolver: () => configureSlotResolver,
@@ -53,6 +55,7 @@ __export(index_exports, {
53
55
  hasPreset: () => hasPreset,
54
56
  hlsLadderAlignment: () => hlsLadderAlignment,
55
57
  invalidateSlotCache: () => invalidateSlotCache,
58
+ isRequestablePreset: () => isRequestablePreset,
56
59
  iteratePaletteSwatches: () => iteratePaletteSwatches,
57
60
  pickAmbientBackground: () => pickAmbientBackground,
58
61
  relativeLuminance: () => relativeLuminance,
@@ -62,7 +65,8 @@ __export(index_exports, {
62
65
  setCdnBase: () => setCdnBase,
63
66
  setTenantId: () => setTenantId,
64
67
  signAccessUrl: () => signAccessUrl,
65
- signTransformUrl: () => signTransformUrl
68
+ signTransformUrl: () => signTransformUrl,
69
+ toRequestablePresets: () => toRequestablePresets
66
70
  });
67
71
  module.exports = __toCommonJS(index_exports);
68
72
 
@@ -129,10 +133,18 @@ async function signAccessUrl(publicUrl, signingKey, opts) {
129
133
  u.searchParams.set("sig", sig);
130
134
  return u.toString();
131
135
  }
132
- function assertPublic(asset, fn, escape) {
136
+ function assertPublic(asset, fn, escapeHatch) {
133
137
  if (asset.visibility !== "private") return;
134
138
  throw new Error(
135
- `${fn}: this asset is private, so a public CDN URL for it will answer 404 \u2014 that is the feature, not a missing file. Mint a signed URL on your BACKEND instead: await ${escape}. Never ship the signing key to a browser.`
139
+ `${fn}: this asset is private, so a public CDN URL for it will answer 404 \u2014 that is the feature, not a missing file. Mint a signed URL on your BACKEND instead: await ${escapeHatch}. Never ship the signing key to a browser.`
140
+ );
141
+ }
142
+ function assertSha(asset, fn) {
143
+ const sha = asset?.sha;
144
+ if (typeof sha === "string" && /^[0-9a-f]{16,64}$/i.test(sha)) return;
145
+ const hint = asset && typeof asset === "object" && "sha256" in asset ? " The value you passed has `sha256` but not `sha` \u2014 that is the shape `upload()` returns. Use `{ sha: result.sha256.slice(0, 16) }`, or fetch the DTO with `assets.get(id)`." : ` Got ${JSON.stringify(sha)}.`;
146
+ throw new Error(
147
+ `${fn}: no usable \`sha\` on the value you passed, so the URL would contain "undefined" and 404 somewhere else.${hint}`
136
148
  );
137
149
  }
138
150
 
@@ -311,6 +323,7 @@ function extForOptions(opts) {
311
323
  }
312
324
  }
313
325
  function getVideoTransformUrl(asset, opts) {
326
+ assertSha(asset, "getVideoTransformUrl");
314
327
  assertPublic(
315
328
  asset,
316
329
  "getVideoTransformUrl",
@@ -322,6 +335,7 @@ function getVideoTransformUrl(asset, opts) {
322
335
  return `${getCdnBase()}/t/${dsl}/${asset.sha}.${ext}`;
323
336
  }
324
337
  function getHlsStreamingUrl(asset, opts = {}) {
338
+ assertSha(asset, "getHlsStreamingUrl");
325
339
  assertPublic(
326
340
  asset,
327
341
  "getHlsStreamingUrl",
@@ -338,6 +352,7 @@ function buildTransformUrl(asset, opts) {
338
352
  return `${getCdnBase()}/t/${dsl}/${asset.sha}.${ext}`;
339
353
  }
340
354
  function getTransformUrl(asset, opts) {
355
+ assertSha(asset, "getTransformUrl");
341
356
  assertPublic(
342
357
  asset,
343
358
  "getTransformUrl",
@@ -346,6 +361,7 @@ function getTransformUrl(asset, opts) {
346
361
  return buildTransformUrl(asset, opts);
347
362
  }
348
363
  function getSignedTransformUrl(asset, opts, signingKey) {
364
+ assertSha(asset, "getSignedTransformUrl");
349
365
  assertPublic(
350
366
  asset,
351
367
  "getSignedTransformUrl",
@@ -381,6 +397,7 @@ async function hmacSha256Hex(key, message) {
381
397
  return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
382
398
  }
383
399
  function getTransformSrcSet(asset, widths, extraOpts = {}) {
400
+ assertSha(asset, "getTransformSrcSet");
384
401
  assertPublic(
385
402
  asset,
386
403
  "getTransformSrcSet",
@@ -489,6 +506,28 @@ function materializeResolution(dto, overridePreset) {
489
506
  }
490
507
 
491
508
  // src/index.ts
509
+ var REQUESTABLE_PRESETS = [
510
+ "thumb",
511
+ "sm",
512
+ "md",
513
+ "lg",
514
+ "xl",
515
+ "original",
516
+ "poster",
517
+ "video",
518
+ "aiproxy",
519
+ "probe"
520
+ ];
521
+ var isRequestablePreset = (v) => REQUESTABLE_PRESETS.includes(v);
522
+ var toRequestablePresets = (input) => {
523
+ const bad = input.filter((p) => !isRequestablePreset(p));
524
+ if (bad.length > 0) {
525
+ throw new Error(
526
+ `Cannot request ${bad.map((b) => `"${b}"`).join(", ")}. \`hls\` and \`mp3\` are things a variant can BE, not things you may ask for \u2014 the server derives them itself (hls when a video is transcoded, mp3 alongside any audio original). Requestable: ${REQUESTABLE_PRESETS.join(", ")}.`
527
+ );
528
+ }
529
+ return input;
530
+ };
492
531
  var PRESET_SHORT = {
493
532
  thumb: "q",
494
533
  sm: "s",
@@ -614,6 +653,7 @@ function originalExtForMime(mime) {
614
653
  return (mime ? ORIGINAL_EXT_BY_MIME[mime] : void 0) ?? PRESET_EXT.original;
615
654
  }
616
655
  function getAssetUrl(asset, preset) {
656
+ assertSha(asset, "getAssetUrl");
617
657
  assertPublic(
618
658
  asset,
619
659
  "getAssetUrl",
@@ -631,6 +671,7 @@ function transformFallbackFor(asset, preset) {
631
671
  return `${cdnBaseUrl}/t/format=webp,width=${maxDim}/${asset.sha}.webp`;
632
672
  }
633
673
  function buildPublicAssetUrl(asset, preset) {
674
+ assertSha(asset, "getPrivateAssetUrl");
634
675
  if (preset === "original") {
635
676
  const stored = asset.variants?.find((v) => v.preset === "original")?.url;
636
677
  if (stored) return stored;
@@ -672,6 +713,7 @@ function stripMultiCharTokens(presets) {
672
713
  }
673
714
  var IMAGE_PRESETS = ["thumb", "sm", "md", "lg", "xl"];
674
715
  function getAssetSrcSet(asset) {
716
+ assertSha(asset, "getAssetSrcSet");
675
717
  assertPublic(
676
718
  asset,
677
719
  "getAssetSrcSet",
@@ -702,9 +744,11 @@ function getAssetDimensions(asset) {
702
744
  PRESET_LONG,
703
745
  PRESET_MAX_DIM,
704
746
  PRESET_SHORT,
747
+ REQUESTABLE_PRESETS,
705
748
  TRANSFORM_WIDTHS,
706
749
  accessMessage,
707
750
  assertPublic,
751
+ assertSha,
708
752
  bestTextContrast,
709
753
  computeVariantDimensions,
710
754
  configureSlotResolver,
@@ -731,6 +775,7 @@ function getAssetDimensions(asset) {
731
775
  hasPreset,
732
776
  hlsLadderAlignment,
733
777
  invalidateSlotCache,
778
+ isRequestablePreset,
734
779
  iteratePaletteSwatches,
735
780
  pickAmbientBackground,
736
781
  relativeLuminance,
@@ -740,6 +785,7 @@ function getAssetDimensions(asset) {
740
785
  setCdnBase,
741
786
  setTenantId,
742
787
  signAccessUrl,
743
- signTransformUrl
788
+ signTransformUrl,
789
+ toRequestablePresets
744
790
  });
745
791
  //# sourceMappingURL=index.cjs.map