@nitida/sdk 0.27.1 → 0.28.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
@@ -101,6 +101,22 @@ await getPrivateTransformUrl(asset, { width: 1280 }, signingKey, { expiresInSeco
101
101
  - **`exp` is mandatory**; revocation is *"within a minute"* (60 s TTL at the edge).
102
102
  - **The signing key is a backend secret** — it mints URLs for every private
103
103
  asset the tenant owns.
104
+ - **⭐ Where the signing key comes from: the response that CREATED your
105
+ project, once.** `POST /admin/projects` returns `signingKey` next to the
106
+ three API keys, and the console shows it in the same panel. Nothing else
107
+ hands it out — `GET /admin/projects/:code` does **not** include it. If it is
108
+ lost, the only endpoint that returns a key is
109
+ `POST /admin/projects/:code/rotate-signing-key`, which **invalidates every
110
+ URL already signed** and needs the system-scope key the platform operator
111
+ holds. A tenant with nothing signed yet can rotate for free; a live one
112
+ cannot, which is why you save it at creation.
113
+ - **⭐ Already have a project and never saw a signing key?** Then you never got
114
+ one — projects created before 2026-08-23 were not handed it, and no endpoint
115
+ shows you the current one. **If you have not signed any URLs yet, ask us to
116
+ rotate: with nothing in flight, rotation invalidates nothing and is free.**
117
+ If you already have signed URLs circulating, ask us for the current key
118
+ instead. This is the wall the T5 agent hit, and the sentence that was
119
+ missing.
104
120
 
105
121
  ## Two ways an image gets smaller, and only one of them is yours to call
106
122
 
package/README.md CHANGED
@@ -259,8 +259,11 @@ const heroes = await aq.slots.resolveMany([
259
259
  "storefront.home.tile-2",
260
260
  ]);
261
261
 
262
- // Lower-level operations.
262
+ // Lower-level operations. `byHash` takes the full 64-hex sha256 that
263
+ // `upload()` returns as `.sha256`, OR its 16-char prefix — the short form
264
+ // that appears inside every CDN URL. Both resolve to the same asset.
263
265
  const asset = await aq.assets.byHash("3c…<64 hex>…");
266
+ const same = await aq.assets.byHash("3c8f1a20b7d94e05"); // 16-char prefix ✓
264
267
  const { assets, nextCursor } = await aq.assets.list({ limit: 50 });
265
268
 
266
269
  // Uploads — hash-deduped; returns the canonical v2 URL immediately.
@@ -894,6 +897,29 @@ as they specify the same params.
894
897
 
895
898
  Every tenant has an HMAC-SHA256 signing key — 32 random bytes, generated
896
899
  on tenant creation.
900
+
901
+ > **Where you get it: the response that created your project, once.**
902
+ > `POST /admin/projects` returns `signingKey` next to the three API keys, and
903
+ > the console shows it in the same panel. Save it with the keys — nothing else
904
+ > hands it out. In particular `GET /admin/projects/:code` does **not** return
905
+ > it, and neither does any tenant-scoped endpoint.
906
+ >
907
+ > **Your project already exists and you never saw a signing key?** Then you
908
+ > never got one: projects created before 2026-08-23 were not handed it, and
909
+ > there is no endpoint that shows you the current one. This is the exact wall
910
+ > to hit, so here is the way through it:
911
+ >
912
+ > - **If you have not signed any URLs yet** — which is true of every project
913
+ > that has not shipped private assets — ask us to rotate. Rotation returns a
914
+ > key, and with nothing in flight it invalidates nothing. It is free.
915
+ > - **If you already have signed URLs in circulation**, rotation kills them.
916
+ > Ask us for the current key instead; we can read it.
917
+ >
918
+ > Either way it is one request to us, because
919
+ > `POST /admin/projects/:code/rotate-signing-key` needs the system-scope key
920
+ > the platform operator holds — your own admin key answers
921
+ > `403 SYSTEM_KEY_REQUIRED`.
922
+
897
923
  Optionally enable `strict_transforms = true` to reject unsigned URLs
898
924
  with a 401 — useful when transform URLs leak from a private surface
899
925
  (internal admin, b2b portal) and you don't want third parties
@@ -905,7 +931,8 @@ const aq = new NitidaClient({
905
931
  apiKey: process.env.AQUIENPZ_API_KEY!,
906
932
  tenantCode: "your-tenant",
907
933
  tenantId: 42,
908
- // Fetch via GET /admin/projects/your-tenant; do NOT ship to the browser.
934
+ // Handed to you once, by the response that created the project.
935
+ // do NOT ship to the browser.
909
936
  signingKey: process.env.AQUIENPZ_SIGNING_KEY!,
910
937
  });
911
938
 
@@ -1304,7 +1331,7 @@ API key tiers:
1304
1331
  | `aq.slots` | `bind(key, {assetId, preset})` | Admin rebind |
1305
1332
  | `aq.slots` | `unbind(key)` | Remove binding |
1306
1333
  | `aq.slots` | `invalidateCache(key?)` | After admin rebind |
1307
- | `aq.assets` | `byHash(sha)` / `byHashes([])` | Lookup |
1334
+ | `aq.assets` | `byHash(sha)` / `byHashes([])` | Lookup — full 64-hex sha **or** its 16-char prefix |
1308
1335
  | `aq.assets` | `list({limit, cursor})` | Paginated |
1309
1336
  | `aq.assets` | `get(id)` | Full DTO |
1310
1337
  | `aq.assets` | `patchMetadata(id, {…})` | Merge JSON |
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ResolveSlotOptions, SlotResolution, SlotDTO, VariantPreset, AssetDTO, AssetVariant, RequestablePreset, TransformOptions, SignedTransformOptions } from '@nitida/asset-client';
2
- export { AssetDTO, AssetPalette, AssetVariant, HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, PaletteSwatch, RequestablePreset, ResolveSlotOptions, SignAccessOptions, SignedTransformOptions, SlotDTO, SlotResolution, TRANSFORM_WIDTHS, TransformEffect, TransformFit, TransformFormat, TransformGravity, TransformOptions, TransformWidth, VariantEntryPreset, VariantPreset, VisibilityHint, accessMessage, assertPublic, assertSha, 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 } from '@nitida/asset-client';
2
+ export { AssetDTO, AssetPalette, AssetVariant, HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, PaletteSwatch, REQUESTABLE_PRESETS, RequestablePreset, ResolveSlotOptions, SignAccessOptions, SignedTransformOptions, SlotDTO, SlotResolution, TRANSFORM_WIDTHS, TransformEffect, TransformFit, TransformFormat, TransformGravity, TransformOptions, TransformWidth, VariantEntryPreset, VariantPreset, VisibilityHint, accessMessage, assertPublic, assertSha, 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, isRequestablePreset, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signAccessUrl, signTransformUrl, toRequestablePresets } from '@nitida/asset-client';
3
3
 
4
4
  /**
5
5
  * Which audio uploads already play everywhere — the single source of truth for
@@ -119,12 +119,25 @@ type NitidaClientOptions = {
119
119
  * Tenant's HMAC signing key for transform URLs (Phase 3). Required
120
120
  * only when calling `aq.transform(asset, opts, { sign: true })`.
121
121
  *
122
- * 32 random bytes, generated server-side on tenant creation; fetch
123
- * via `POST /admin/projects/:code/rotate-signing-key`, which needs a
124
- * SYSTEM-scope credential the platform operator holds your own admin key
125
- * answers `403 SYSTEM_KEY_REQUIRED`. Ask for it. **Keep it
126
- * server-side only** do not ship in `NEXT_PUBLIC_*` env vars. Sign
127
- * URLs from a BFF route handler, or pre-sign at build time.
122
+ * 32 random bytes, generated server-side on tenant creation.
123
+ *
124
+ * **Where you get it: the response to your project's creation, once.**
125
+ * `POST /admin/projects` returns `signingKey` next to the three API keys,
126
+ * and the console shows it in the same panel. It is shown there and nowhere
127
+ * else save it with the keys.
128
+ *
129
+ * **Never saw one?** Projects created before 2026-08-23 were not handed it,
130
+ * and no endpoint shows you the current one. If you have not signed any URLs
131
+ * yet — true of every project that has not shipped private assets — ask for
132
+ * a rotation: with nothing in flight it invalidates nothing and is free. If
133
+ * you already have signed URLs circulating, ask for the current key instead.
134
+ *
135
+ * Either way it is one request to the platform operator:
136
+ * `POST /admin/projects/:code/rotate-signing-key` needs a SYSTEM-scope
137
+ * credential, and your own admin key answers `403 SYSTEM_KEY_REQUIRED`.
138
+ *
139
+ * **Keep it server-side only** — do not ship in `NEXT_PUBLIC_*` env vars.
140
+ * Sign URLs from a BFF route handler, or pre-sign at build time.
128
141
  */
129
142
  signingKey?: string;
130
143
  };
@@ -319,7 +332,12 @@ type ComposeMarketingResult = {
319
332
  declare class AssetsApi {
320
333
  private readonly opts;
321
334
  constructor(opts: NitidaClientOptions);
322
- /** Look up an asset by full sha256 (64 hex). Returns null on 404. */
335
+ /**
336
+ * Look up an asset by sha256. Accepts the full 64-hex digest that
337
+ * `upload()` returns as `sha256`, or the 16-char short prefix that appears
338
+ * in every CDN URL. Returns null on 404; throws with a message naming the
339
+ * expected shape if the string is neither form.
340
+ */
323
341
  byHash(sha256: string): Promise<AssetDTO | null>;
324
342
  /** Bulk lookup by sha256s. */
325
343
  byHashes(hashes: string[]): Promise<{
package/dist/index.js CHANGED
@@ -53,12 +53,14 @@ import {
53
53
  hasPreset as hasPreset2,
54
54
  hlsLadderAlignment,
55
55
  invalidateSlotCache as invalidateSlotCache2,
56
+ isRequestablePreset as isRequestablePreset2,
56
57
  iteratePaletteSwatches,
57
58
  PRESET_EXT,
58
59
  PRESET_LONG,
59
60
  PRESET_MAX_DIM,
60
61
  PRESET_SHORT,
61
62
  pickAmbientBackground,
63
+ REQUESTABLE_PRESETS as REQUESTABLE_PRESETS2,
62
64
  relativeLuminance,
63
65
  resolveSlot as resolveSlot2,
64
66
  resolveSlots as resolveSlots2,
@@ -67,7 +69,8 @@ import {
67
69
  setTenantId as setTenantId2,
68
70
  signAccessUrl,
69
71
  signTransformUrl,
70
- TRANSFORM_WIDTHS
72
+ TRANSFORM_WIDTHS,
73
+ toRequestablePresets as toRequestablePresets2
71
74
  } from "@nitida/asset-client";
72
75
  function endpointUrl(opts, path, searchParams) {
73
76
  const endpoint = opts.endpoint.replace(/\/+$/, "");
@@ -199,7 +202,12 @@ var AssetsApi = class {
199
202
  this.opts = opts;
200
203
  }
201
204
  opts;
202
- /** Look up an asset by full sha256 (64 hex). Returns null on 404. */
205
+ /**
206
+ * Look up an asset by sha256. Accepts the full 64-hex digest that
207
+ * `upload()` returns as `sha256`, or the 16-char short prefix that appears
208
+ * in every CDN URL. Returns null on 404; throws with a message naming the
209
+ * expected shape if the string is neither form.
210
+ */
203
211
  async byHash(sha256) {
204
212
  const r = await fetch(
205
213
  endpointHref(this.opts, `/assets/by-hash/${sha256}`),
@@ -572,7 +580,7 @@ var NitidaClient = class {
572
580
  }
573
581
  if (!this.opts.signingKey) {
574
582
  throw new Error(
575
- "aq.transform({ sign: true }) requires `signingKey` in NitidaClientOptions. No signingKey on this client. The key is minted by POST /admin/projects/:code/rotate-signing-key, which needs a system-scope credential the platform operator holds \u2014 your own admin key gets 403 SYSTEM_KEY_REQUIRED, so ask for it. Then pass it to the SDK constructor on a SERVER-side instance only."
583
+ "aq.transform({ sign: true }) requires `signingKey` in NitidaClientOptions. No signingKey on this client. It is returned ONCE, in the response that creates your project (POST /admin/projects \u2192 `signingKey`, next to the three API keys; the console shows it in the same panel). If you never saw one \u2014 projects created before 2026-08-23 were not handed it \u2014 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."
576
584
  );
577
585
  }
578
586
  return getSignedTransformUrl(asset, opts, this.opts.signingKey) ?? Promise.resolve(this.urlFor(asset, "lg"));
@@ -581,7 +589,7 @@ var NitidaClient = class {
581
589
  if (!signOpts?.sign) return getTransformSrcSet(asset, widths, extraOpts);
582
590
  if (!this.opts.signingKey) {
583
591
  throw new Error(
584
- "aq.transformSrcSet({ sign: true }) requires `signingKey` in NitidaClientOptions."
592
+ "aq.transformSrcSet({ sign: true }) requires `signingKey` in NitidaClientOptions. It was returned ONCE, by the response that created your project (POST /admin/projects \u2192 `signingKey`). See the `signingKey` docs on NitidaClientOptions."
585
593
  );
586
594
  }
587
595
  const key = this.opts.signingKey;
@@ -860,6 +868,7 @@ export {
860
868
  PRESET_LONG,
861
869
  PRESET_MAX_DIM,
862
870
  PRESET_SHORT,
871
+ REQUESTABLE_PRESETS2 as REQUESTABLE_PRESETS,
863
872
  TRANSFORM_WIDTHS,
864
873
  accessMessage,
865
874
  assertPublic,
@@ -890,6 +899,7 @@ export {
890
899
  hasPreset2 as hasPreset,
891
900
  hlsLadderAlignment,
892
901
  invalidateSlotCache2 as invalidateSlotCache,
902
+ isRequestablePreset2 as isRequestablePreset,
893
903
  isUniversallyPlayableAudio,
894
904
  iteratePaletteSwatches,
895
905
  mimeFromFileName,
@@ -901,6 +911,7 @@ export {
901
911
  setCdnBase2 as setCdnBase,
902
912
  setTenantId2 as setTenantId,
903
913
  signAccessUrl,
904
- signTransformUrl
914
+ signTransformUrl,
915
+ toRequestablePresets2 as toRequestablePresets
905
916
  };
906
917
  //# sourceMappingURL=index.js.map