@nitida/sdk 0.24.0 → 0.25.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/src/index.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  *
5
5
  * One ergonomic facade over the underlying packages
6
6
  * (`@nitida/asset-client` URL builders + `@aquienpz/asset-uploader-web`
7
- * + the slot resolver). Auth is a Better Auth API key (`amk_rt_*`)
8
- * issued by aquienpz `bootstrap-project.ts`; tenant scope comes from
7
+ * + the slot resolver). Auth is a bearer API key (`amk_rt_*`), issued
8
+ * per tenant when the tenant is created; tenant scope comes from
9
9
  * the key's metadata (`X-Tenant-Code` is log-only).
10
10
  *
11
11
  * Usage:
@@ -14,8 +14,8 @@
14
14
  *
15
15
  * const aq = new NitidaClient({
16
16
  * endpoint: "https://api.nitida.gofuture.space",
17
- * apiKey: process.env.ASSET_MANAGER_RUNTIME_KEY!,
18
- * tenantCode: "realtyone-cr",
17
+ * apiKey: process.env.AQUIENPZ_API_KEY!, // amk_rt_* — server-only
18
+ * tenantCode: "acme-co",
19
19
  * cdnBase: "https://8ok.uk", // optional override
20
20
  * tenantId: 4, // required for tenant-prefixed URLs
21
21
  * });
@@ -78,7 +78,7 @@ import {
78
78
  */
79
79
  export type NitidaClientOptions = {
80
80
  /**
81
- * Base URL of the aquienpz asset-manager (Cloud Run service URL).
81
+ * Base URL of the nitida API (e.g. `https://api.nitida.gofuture.space`).
82
82
  *
83
83
  * May be relative (e.g. `/api/am`) ONLY in browser contexts where the
84
84
  * SDK resolves it against `window.location.origin`. Node/Bun consumers
@@ -86,7 +86,7 @@ export type NitidaClientOptions = {
86
86
  */
87
87
  endpoint: string;
88
88
  /**
89
- * Better Auth API key with the `amk_rt_*` prefix.
89
+ * Bearer API key with the `amk_rt_*` prefix.
90
90
  *
91
91
  * **Server-only.** Omit when constructing from `@nitida/sdk/web` —
92
92
  * your BFF / route handler injects the bearer header in proxy mode.
@@ -111,8 +111,8 @@ export type NitidaClientOptions = {
111
111
  * Tenant's HMAC signing key for transform URLs (Phase 3). Required
112
112
  * only when calling `aq.transform(asset, opts, { sign: true })`.
113
113
  *
114
- * Generated server-side per tenant (see `infra/sql/tenants_signed_transforms.sql`);
115
- * fetch via `GET /admin/tenants/:id` with an admin key. **Keep it
114
+ * 32 random bytes, generated server-side on tenant creation; fetch
115
+ * via `GET /admin/tenants/:id` with an admin key. **Keep it
116
116
  * server-side only** — do not ship in `NEXT_PUBLIC_*` env vars. Sign
117
117
  * URLs from a BFF route handler, or pre-sign at build time.
118
118
  */
@@ -190,9 +190,19 @@ function authHeaders(opts: NitidaClientOptions): Record<string, string> {
190
190
  // Re-exports (so consumers don't double-import from asset-client)
191
191
  // ---------------------------------------------------------------------------
192
192
 
193
+ // This barrel is COMPLETE on purpose: every public symbol of
194
+ // `@nitida/asset-client` is reachable from `@nitida/sdk`, so a consumer never
195
+ // has to know which of the two packages a helper happens to live in. It had
196
+ // drifted to 30 of 53 — the gap silently included `TRANSFORM_WIDTHS` and
197
+ // `TransformWidth`, which both READMEs tell you to import precisely so an
198
+ // off-ladder width fails at compile time instead of returning 400, and the
199
+ // palette helpers the README imports from here by name.
193
200
  export type {
194
201
  AssetDTO,
202
+ AssetPalette,
195
203
  AssetVariant,
204
+ HlsRung,
205
+ PaletteSwatch,
196
206
  ResolveSlotOptions,
197
207
  SignedTransformOptions,
198
208
  SlotDTO,
@@ -202,24 +212,47 @@ export type {
202
212
  TransformFormat,
203
213
  TransformGravity,
204
214
  TransformOptions,
215
+ TransformWidth,
205
216
  VariantPreset,
206
217
  } from "@nitida/asset-client";
207
218
  export {
219
+ bestTextContrast,
208
220
  computeVariantDimensions,
221
+ configureSlotResolver,
222
+ contrastRatio,
209
223
  extractAssetSha,
224
+ getAmbientGradient,
210
225
  getAssetDimensions,
211
226
  getAssetSrcSet,
212
227
  getAssetUrl,
228
+ getCdnBase,
229
+ getHlsLadder,
213
230
  getHlsStreamingUrl,
231
+ getPaletteBlurBackground,
232
+ getPaletteCssVars,
214
233
  getSignedTransformUrl,
215
234
  getTenantId,
235
+ getTextColorForBackground,
216
236
  getTransformSrcSet,
217
237
  getTransformUrl,
218
238
  getVideoTransformUrl,
219
239
  hasPreset,
240
+ hlsLadderAlignment,
241
+ invalidateSlotCache,
242
+ iteratePaletteSwatches,
243
+ PRESET_EXT,
244
+ PRESET_LONG,
245
+ PRESET_MAX_DIM,
246
+ PRESET_SHORT,
247
+ pickAmbientBackground,
248
+ relativeLuminance,
249
+ resolveSlot,
250
+ resolveSlots,
220
251
  serializeTransform,
252
+ setCdnBase,
221
253
  setTenantId,
222
254
  signTransformUrl,
255
+ TRANSFORM_WIDTHS,
223
256
  } from "@nitida/asset-client";
224
257
 
225
258
  // ---------------------------------------------------------------------------
@@ -348,8 +381,8 @@ class SlotsApi {
348
381
  /**
349
382
  * Returned by `aq.assets.regenerate(...)`. The shape varies by kind —
350
383
  * images return immediately with the merged variant list; videos
351
- * return a dispatch handle (the actual transcode runs in a Cloud Run
352
- * Job and finishes async).
384
+ * return a dispatch handle (the actual transcode runs in a background
385
+ * job and finishes async).
353
386
  */
354
387
  export type RegenerateResult =
355
388
  | {
@@ -383,7 +416,7 @@ export type RegenerateResult =
383
416
  /**
384
417
  * Wire shape returned by `POST /assets/upload-url`. Either the server
385
418
  * resolves the upload synchronously via dedup (`deduped: true` + existing
386
- * asset DTO) or it returns a presigned R2 PUT URL plus a `process` payload
419
+ * asset DTO) or it returns a presigned storage PUT URL plus a `process` payload
387
420
  * the caller must POST to `/assets/process` after the PUT lands.
388
421
  */
389
422
  export type UploadUrlResult =
@@ -403,13 +436,13 @@ export type UploadVideoOptions = {
403
436
  /**
404
437
  * `false` → skip the auto-dispatched HLS adaptive ladder (240p–2160p). Use
405
438
  * for download-only assets served as a progressive `-v.mp4` and never
406
- * streamed (e.g. share-video reels) — it avoids a second Cloud Run Job no
439
+ * streamed (e.g. share-video reels) — it avoids a second background job no
407
440
  * one watches. Default/absent → the ladder is generated as before.
408
441
  */
409
442
  hls?: boolean;
410
443
  /**
411
444
  * `true` → when the uploaded MP4 is ALREADY web-safe (H.264 + yuv420p),
412
- * re-mux the `video` variant with `-c copy` instead of re-encoding. Use for
445
+ * re-mux the `video` variant instead of re-encoding it. Use for
413
446
  * delivery-ready uploads (the bytes are already H.264 High / yuv420p /
414
447
  * +faststart / capped bitrate) to skip a wasteful re-encode + generational
415
448
  * quality loss. Falls back to a full re-encode automatically when the source
@@ -420,7 +453,7 @@ export type UploadVideoOptions = {
420
453
 
421
454
  /** Input shape accepted by `aq.assets.presignUploadUrl(...)`. */
422
455
  export type PresignUploadUrlOptions = {
423
- /** Full sha256 (64 hex) of the bytes that will be PUT to R2. */
456
+ /** Full sha256 (64 hex) of the bytes that will be PUT to storage. */
424
457
  sha256: string;
425
458
  /** MIME type of the bytes (e.g. `image/jpeg`, `video/mp4`). */
426
459
  mime: string;
@@ -435,8 +468,8 @@ export type PresignUploadUrlOptions = {
435
468
  presets?: VariantPreset[];
436
469
  /**
437
470
  * Pre-compression size of the source (useful when the browser ran
438
- * compressorjs / heic2any before computing `bytes`). Surfaces in admin
439
- * dashboards under `assets.client_original_bytes`.
471
+ * compressorjs / heic2any before computing `bytes`). Recorded
472
+ * server-side, so the savings show up in the admin usage dashboards.
440
473
  */
441
474
  clientOriginalBytes?: number;
442
475
  /** VIDEO-only delivery knobs forwarded into `/assets/process`. See {@link UploadVideoOptions}. */
@@ -571,8 +604,8 @@ class AssetsApi {
571
604
  * const v = await aq.assets.variants(logoId);
572
605
  * v.map((x) => x.preset); // → ("thumb" | "sm" | … | "original")[]
573
606
  *
574
- * ⚠️ Returns `[]` — not an error — against an `asset-manager` older than the
575
- * 2026-08-17 deploy, which never sent the field (doc 240 §4.3). An empty
607
+ * ⚠️ Returns `[]` — not an error — against a server older than the
608
+ * 2026-08-17 release, which never sent the field at all. An empty
576
609
  * array is therefore "no variants OR old server". For a plain existence
577
610
  * check prefer `hasPreset(dto, preset)` on `dto.presets`, which every server
578
611
  * version sends; use this when you need the URLs and sizes.
@@ -601,12 +634,13 @@ class AssetsApi {
601
634
  * Passing no presets re-runs the FULL default pipeline for that
602
635
  * asset's kind (thumb+sm+md+lg for images, poster+video for video).
603
636
  *
604
- * If the asset was uploaded original-only and the cleanup job has
605
- * already reaped `raw/`, the route falls back to reading the source
606
- * bytes from `variants/o.<ext>` no need to re-upload.
637
+ * If the asset was uploaded original-only and the ~24 h grace window
638
+ * on the uploaded bytes has already closed, the route falls back to
639
+ * reading the source bytes from the permanent `original` variant
640
+ * no need to re-upload.
607
641
  *
608
642
  * Video presets are filtered to `["poster","video","aiproxy"]` and
609
- * dispatched to the Cloud Run Job (the call returns immediately
643
+ * dispatched to a background job (the call returns immediately
610
644
  * with a dispatch handle; poll `aq.assets.get(id).status` for
611
645
  * completion).
612
646
  */
@@ -642,13 +676,13 @@ class AssetsApi {
642
676
  }
643
677
 
644
678
  /**
645
- * Request a presigned R2 PUT URL for direct browser-side uploads.
679
+ * Request a presigned storage PUT URL for direct browser-side uploads.
646
680
  *
647
681
  * Mirrors the first half of `aq.upload()` — the caller (typically a
648
682
  * BFF / share-link dropzone) computes sha256 in the browser, then
649
- * uploads bytes straight to R2 with the returned `upload.url`, then
650
- * POSTs `process.body` to `/assets/process` (see {@link processAndWait})
651
- * once R2 has the bytes.
683
+ * uploads bytes straight to object storage with the returned `upload.url`,
684
+ * then POSTs `process.body` to `/assets/process` (see {@link processAndWait})
685
+ * once storage has the bytes.
652
686
  *
653
687
  * If the sha is already known to the tenant the server short-circuits
654
688
  * with `{ deduped: true, asset }` — no PUT needed.
@@ -660,16 +694,16 @@ class AssetsApi {
660
694
  * if (presign.deduped) return presign.asset; // those bytes already exist; none fly
661
695
  *
662
696
  * // BROWSER: PUT straight to presign.upload.url — the bytes never touch your server.
663
- * // ⚠️ R2 answers that preflight ITSELF, so your origin must be in the BUCKET's CORS policy.
664
- * // Symptom when it is not: "PUT failed: network error" with every earlier step green —
665
- * // and it cannot be fixed in this SDK, in your app, or in `storefront_origins`.
697
+ * // ⚠️ The STORAGE BUCKET answers that preflight itself, so your origin must be in its CORS
698
+ * // policy. Symptom when it is not: "PUT failed: network error" with every earlier step
699
+ * // green — and it cannot be fixed in this SDK, in your app, or by the API's allowed origins.
666
700
  *
667
701
  * // SERVER again, forwarding presign.process.body VERBATIM:
668
702
  * const asset = await aq.assets.processAndWait(presign.process.body, { timeoutMs: 300_000 });
669
703
  * ```
670
704
  *
671
705
  * Works for images AND video. A video answers immediately with
672
- * `{ assetId, status: "processing" }` while a Cloud Run Job transcodes, so
706
+ * `{ assetId, status: "processing" }` while a background job transcodes, so
673
707
  * give `processAndWait` a bigger `timeoutMs` (a transcode + HLS ladder runs
674
708
  * 1–2 min; 300_000 is a sane floor).
675
709
  */
@@ -701,7 +735,7 @@ class AssetsApi {
701
735
  * {@link presignUploadUrl} call, then poll until the asset transitions
702
736
  * to `ready` or `failed`. Throws on `failed` or timeout.
703
737
  *
704
- * Use this when bytes were uploaded directly from the browser to R2
738
+ * Use this when bytes were uploaded directly from the browser to storage
705
739
  * `aq.upload()` already does presign + PUT + process + wait in one
706
740
  * step when the server holds the bytes.
707
741
  */
@@ -755,7 +789,7 @@ class AssetsApi {
755
789
  * {@link waitReady} (typical timeout: 10 min for multi-segment kits).
756
790
  *
757
791
  * Tenant scope is inherited from the SDK client; `tenantCode` is added
758
- * to the request body so the Cloud Run Job can resolve it without
792
+ * to the request body so the background job can resolve it without
759
793
  * re-reading the header.
760
794
  */
761
795
  async composeMarketing(
@@ -858,7 +892,7 @@ export type UploadOptions = {
858
892
  * MIME type of the bytes. **Only needed for a `Uint8Array` input** — a `File`/`Blob`
859
893
  * already carries its `.type`. Raw bytes have no inherent MIME, so without this (and
860
894
  * without an extension on `fileName` to infer from) they upload as
861
- * `application/octet-stream`, which the asset-manager classifies as `kind:"other"` —
895
+ * `application/octet-stream`, which the server classifies as `kind:"other"` —
862
896
  * meaning NO image/video variants are generated and `regenerate()` is unsupported.
863
897
  * Resolution order for the effective MIME: `Blob.type` → `contentType` →
864
898
  * inferred from `fileName`'s extension → `application/octet-stream`.
@@ -916,8 +950,8 @@ export type UploadOptions = {
916
950
  /**
917
951
  * Max time to wait for the asset to transition to `ready` (or `failed`)
918
952
  * after dispatch. Default `5 * 60_000` (5 min). Bump higher for large
919
- * videos / HLS transcodes — aquienpz processing time scales with input
920
- * size and per-instance CPU.
953
+ * videos / HLS transcodes — processing time scales with input size,
954
+ * and with how much other work the platform is doing at that moment.
921
955
  *
922
956
  * Throws `Error("waitReady timeout for <id>")` if the deadline passes
923
957
  * without the asset transitioning. The asset row stays in aquienpz
@@ -1002,9 +1036,37 @@ export type UsageWindow = {
1002
1036
  admins: number;
1003
1037
  upscales: number;
1004
1038
  processes: number;
1005
- bytesIn: number;
1039
+ /**
1040
+ * Minutes of video actually TRANSCODED in the window — source minutes ×
1041
+ * encode passes, so a 7-rung HLS ladder over a 2-minute clip books 14.
1042
+ *
1043
+ * A re-encode of a video already ingested counts AGAIN. That is the point:
1044
+ * an asset count cannot report it, because re-encoding an asset that already
1045
+ * exists creates no new asset.
1046
+ *
1047
+ * A stream copy (`video: { passthrough: true }`, or an idempotent cache skip)
1048
+ * books nothing, so a tenant that ran no encoder reads `0` — and that `0` is real.
1049
+ */
1050
+ videoMinutes: number;
1051
+ /** Encode passes behind `videoMinutes`. 0 when nothing was encoded. */
1052
+ videoEncodes: number;
1006
1053
  };
1007
1054
 
1055
+ /**
1056
+ * ⚠️ REMOVED 2026-08-17: `bytesIn` on `UsageWindow`/`UsageDailyPoint` and
1057
+ * `bytesTotal` on `UsagePerKey`.
1058
+ *
1059
+ * All three were structurally zero: the field behind them was never populated,
1060
+ * on any row. They could not be instrumented in place either — uploads go to
1061
+ * object storage through presigned URLs, so the payload never passes through
1062
+ * the API, and the only figure measurable there is a JSON envelope of a few
1063
+ * hundred bytes.
1064
+ *
1065
+ * A field that always reads `0` is worse than an absent field, because absent
1066
+ * is honest — a `0` next to a real `storage.totalBytes` reads as a measurement.
1067
+ * The byte number that IS true is still served: `storage.totalBytes`.
1068
+ */
1069
+
1008
1070
  export type UsageDailyPoint = {
1009
1071
  date: string;
1010
1072
  reads: number;
@@ -1012,8 +1074,10 @@ export type UsageDailyPoint = {
1012
1074
  lists: number;
1013
1075
  deletes: number;
1014
1076
  processes: number;
1015
- bytesIn: number;
1016
1077
  bytesStored: number;
1078
+ /** See `UsageWindow.videoMinutes`. Minutes transcoded on this day. */
1079
+ videoMinutes: number;
1080
+ videoEncodes: number;
1017
1081
  };
1018
1082
 
1019
1083
  export type UsagePerKey = {
@@ -1021,7 +1085,6 @@ export type UsagePerKey = {
1021
1085
  prefix: string | null;
1022
1086
  name: string | null;
1023
1087
  opsTotal: number;
1024
- bytesTotal: number;
1025
1088
  lastSeen: string;
1026
1089
  };
1027
1090
 
@@ -1136,7 +1199,7 @@ export class NitidaClient {
1136
1199
  * Returns the canonical `lg` variant URL when called with empty options,
1137
1200
  * so callers can swap `urlFor()` for `transform()` without thinking.
1138
1201
  *
1139
- * URLs with the same params in different order produce the same R2
1202
+ * URLs with the same params in different order produce the same
1140
1203
  * cache entry (the server canonicalizes both sides). Safe to use as
1141
1204
  * stable cache keys.
1142
1205
  *
@@ -1236,12 +1299,12 @@ export class NitidaClient {
1236
1299
  * Build an on-the-fly VIDEO transform URL — Phase 4.
1237
1300
  *
1238
1301
  * Same DSL shape as `transform()` but the URL has a `.mp4` (default)
1239
- * or `.webm` extension and the server routes the request to a Cloud
1240
- * Run Job for ffmpeg encoding (vs the inline sharp pipeline for
1302
+ * or `.webm` extension and the server routes the request to a
1303
+ * background job for video encoding (vs the inline pipeline for
1241
1304
  * images).
1242
1305
  *
1243
1306
  * On the first request the route returns **202 Accepted** with
1244
- * `Retry-After: 10` while the Job runs (typically 5-30 s for a
1307
+ * `Retry-After: 10` while the encode runs (typically 5-30 s for a
1245
1308
  * short clip). The response body includes `outputUrl` which is the
1246
1309
  * eventual CDN URL — poll the same transform URL after the
1247
1310
  * retry-after window to get a 302 redirect to it.
@@ -1283,7 +1346,7 @@ export class NitidaClient {
1283
1346
  * />
1284
1347
  *
1285
1348
  * On the first request the server returns **202 Accepted** while a
1286
- * Cloud Run Job builds the multi-rung ladder (typically 1-3 min for
1349
+ * background job builds the multi-rung ladder (typically 1-3 min for
1287
1350
  * a 90 s source — five rungs of 240p/360p/480p/720p/1080p @ AAC).
1288
1351
  * Subsequent requests hit the cache → **302** to the master.m3u8.
1289
1352
  *
@@ -1308,13 +1371,13 @@ export class NitidaClient {
1308
1371
  * but isn't always available depending on the runtime).
1309
1372
  */
1310
1373
  /**
1311
- * Upload bytes end to end: optional client compression → sha256 → presign → **direct-to-R2 PUT**
1374
+ * Upload bytes end to end: optional client compression → sha256 → presign → **direct-to-storage PUT**
1312
1375
  * → `/assets/process` → wait until the asset is ready.
1313
1376
  *
1314
1377
  * ⚠️ `presets` decides what exists FOREVER. Omit it and only `original` is written; ask for
1315
1378
  * `["thumb"]` and the bytes you just uploaded are **not retrievable**. A variant not requested in
1316
- * this first ingest cannot be added later once the cleanup job reaps `raw/` measured once as
1317
- * "97 files archived successfully, zero recoverable".
1379
+ * this first ingest cannot be added later once the ~24 h grace window on the uploaded bytes
1380
+ * closes — measured once as "97 files archived successfully, zero recoverable".
1318
1381
  *
1319
1382
  * @example Deliver an image on a site (the responsive ladder)
1320
1383
  * ```ts
@@ -1345,11 +1408,11 @@ export class NitidaClient {
1345
1408
  * @example Video — and what does NOT work there
1346
1409
  * ```ts
1347
1410
  * // `original` is accepted and then silently DROPPED: /assets/process filters video presets to
1348
- * // {poster, video, aiproxy, probe} before dispatching the transcode Job.
1411
+ * // {poster, video, aiproxy, probe} before dispatching the background transcode.
1349
1412
  * await aq.upload(clip, { fileName: "tour.mp4", presets: ["poster", "video"] });
1350
1413
  *
1351
- * // Omit `aiproxy`/`probe` unless the asset really goes to a vision model — they cost Job time
1352
- * // and permanent R2 objects that nothing else reads.
1414
+ * // Omit `aiproxy`/`probe` unless the asset really goes to a vision model — they cost encode
1415
+ * // time and permanent stored objects that nothing else reads.
1353
1416
  * ```
1354
1417
  */
1355
1418
  async upload(
@@ -1360,7 +1423,7 @@ export class NitidaClient {
1360
1423
  // "should we compress this?")
1361
1424
  const sourceIsBlob = input instanceof File || input instanceof Blob;
1362
1425
  // A Uint8Array has no inherent MIME. Fall back to an explicit `contentType`, then to
1363
- // the file extension, before octet-stream (which the asset-manager files as
1426
+ // the file extension, before octet-stream (which the server files as
1364
1427
  // `kind:"other"` — no variants). A File/Blob's own `.type` always wins when present.
1365
1428
  const sourceMime =
1366
1429
  (sourceIsBlob ? input.type : "") ||
@@ -1494,7 +1557,7 @@ export class NitidaClient {
1494
1557
  body: new Blob([bytes as unknown as ArrayBuffer], { type: mime }),
1495
1558
  });
1496
1559
  if (!putR.ok)
1497
- throw new Error(`R2 PUT ${putR.status}: ${await putR.text()}`);
1560
+ throw new Error(`Storage PUT ${putR.status}: ${await putR.text()}`);
1498
1561
 
1499
1562
  const procR = await fetch(endpointHref(this.opts, presign.process.url), {
1500
1563
  method: "POST",
@@ -10,9 +10,9 @@
10
10
  * import { NitidaClient } from "@nitida/sdk/server";
11
11
  *
12
12
  * const aq = new NitidaClient({
13
- * endpoint: process.env.ASSET_MANAGER_URL!,
14
- * apiKey: process.env.ASSET_MANAGER_API_KEY!, // <- required
15
- * tenantCode: "realtyone-cr",
13
+ * endpoint: process.env.AQUIENPZ_URL!,
14
+ * apiKey: process.env.AQUIENPZ_API_KEY!, // <- required
15
+ * tenantCode: "acme-co",
16
16
  * tenantId: 1,
17
17
  * // signingKey: optional, only for `aq.transform(..., { sign: true })`
18
18
  * });
@@ -32,9 +32,11 @@
32
32
  * globally; File-API workflows are documented on the /web subpath instead)
33
33
  *
34
34
  * What's NOT here (use `@nitida/sdk/web` instead):
35
- * - `createWebUploader` (multipart UploadTask with IndexedDB resume)
36
35
  * - `compressImage` (browser-side compressorjs + heic2any)
37
36
  *
37
+ * And what exists nowhere: there is no `createWebUploader`. Multipart is not
38
+ * exposed from any subpath — `aq.upload()` is the supported path.
39
+ *
38
40
  * Stripe/Cloudinary historically shipped two separate packages
39
41
  * (`stripe` vs `@stripe/stripe-js`, `cloudinary` vs `@cloudinary/url-gen`)
40
42
  * for this split. Modern providers (Vercel Blob, Uploadthing, Better
@@ -51,9 +53,9 @@ import { NitidaClient as BaseNitidaClient, type NitidaClientOptions } from "..";
51
53
  * Cloud Run, Vercel Functions, edge runtimes, BFFs).
52
54
  *
53
55
  * const aq = new NitidaClient({
54
- * endpoint: process.env.ASSET_MANAGER_URL!,
55
- * apiKey: process.env.ASSET_MANAGER_API_KEY!,
56
- * tenantCode: "realtyone-cr",
56
+ * endpoint: process.env.AQUIENPZ_URL!,
57
+ * apiKey: process.env.AQUIENPZ_API_KEY!,
58
+ * tenantCode: "acme-co",
57
59
  * tenantId: 1,
58
60
  * });
59
61
  */
package/src/web/index.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * // Point at your BFF route — the SDK calls
14
14
  * // `${endpoint}/assets/by-hash/...`, `${endpoint}/slots/...`, etc.
15
15
  * endpoint: "/api/am", // relative ⇒ same-origin proxy
16
- * tenantCode: "realtyone-cr",
16
+ * tenantCode: "acme-co",
17
17
  * tenantId: 1,
18
18
  * // NO apiKey — the type strips it. Your BFF injects the bearer.
19
19
  * });
@@ -28,7 +28,7 @@
28
28
  * return fetch(url, {
29
29
  * headers: {
30
30
  * Authorization: `Bearer ${process.env.AQUIENPZ_API_KEY!}`,
31
- * "X-Tenant-Code": "realtyone-cr",
31
+ * "X-Tenant-Code": "acme-co",
32
32
  * },
33
33
  * });
34
34
  * }
@@ -64,7 +64,7 @@ import { NitidaClient as BaseNitidaClient, type NitidaClientOptions } from "..";
64
64
  *
65
65
  * const aq = new NitidaClient({
66
66
  * endpoint: "/api/am", // OK: relative → same-origin BFF
67
- * tenantCode: "realtyone-cr",
67
+ * tenantCode: "acme-co",
68
68
  * tenantId: 1,
69
69
  * // apiKey: "amk_rt_...", // ERROR: TS error: not assignable
70
70
  * });
@@ -150,9 +150,9 @@ export {
150
150
  // ---------------------------------------------------------------------------
151
151
  //
152
152
  // Wraps `@nitida/asset-compressor-web` with the SDK's
153
- // `DEFAULT_COMPRESSION_OPTIONS` — values derived from the realtyone-cr
154
- // webapp's production tuning (LISTING_STANDARD_*: quality 0.80, max-edge
155
- // 3840px, WebP output, 5MB PNG→JPEG threshold, strict mode). Those defaults
153
+ // `DEFAULT_COMPRESSION_OPTIONS` — values tuned against a production
154
+ // photo-upload workload (quality 0.80, max-edge 3840px, WebP output,
155
+ // 5MB PNG→JPEG threshold, strict mode). Those defaults
156
156
  // differ from `@nitida/asset-compressor-web`'s own defaults (0.85 / 2880 /
157
157
  // convertSize=0) which target a slightly different audience; SDK callers
158
158
  // get the webapp-tuned values, package-direct callers keep theirs.
@@ -184,17 +184,16 @@ export type CompressStage =
184
184
 
185
185
  /**
186
186
  * Subset of compressorjs options exposed by the SDK. Matches the
187
- * webapp's `DEFAULT_COMPRESSION_OPTIONS` shape from the realtyone-cr
188
- * production back-office uploader.
187
+ * `DEFAULT_COMPRESSION_OPTIONS` shape below.
189
188
  */
190
189
  export type CompressOptions = {
191
- /** 0..1. Default 0.80 (`LISTING_STANDARD_IMAGE_QUALITY / 100`). */
190
+ /** 0..1. Default 0.80. */
192
191
  quality?: number;
193
- /** Output format. Default `"image/webp"` (`DEFAULT_IMAGE_TARGET_FORMAT`). */
192
+ /** Output format. Default `"image/webp"`. */
194
193
  mimeType?: "image/jpeg" | "image/webp";
195
- /** Max edge in pixels. Default 3840 (`LISTING_STANDARD_IMAGE_WIDTH`). */
194
+ /** Max edge in pixels. Default 3840. */
196
195
  maxWidth?: number;
197
- /** Default 3840 (`LISTING_STANDARD_IMAGE_HEIGHT`). */
196
+ /** Max edge in pixels. Default 3840. */
198
197
  maxHeight?: number;
199
198
  /**
200
199
  * compressorjs `convertSize`: PNG > this byte count auto-converts to
@@ -215,9 +214,9 @@ export type CompressOptions = {
215
214
  };
216
215
 
217
216
  /**
218
- * Webapp-tuned defaults (LISTING_STANDARD_*). These intentionally differ
219
- * from `@nitida/asset-compressor-web`'s package-level defaults — the
220
- * SDK overrides at call time.
217
+ * Defaults tuned against a production photo-upload workload. These
218
+ * intentionally differ from `@nitida/asset-compressor-web`'s
219
+ * package-level defaults — the SDK overrides at call time.
221
220
  */
222
221
  export const DEFAULT_COMPRESSION_OPTIONS: Required<
223
222
  Omit<CompressOptions, "keepOriginalDimensions" | "convertHeic" | "onProgress">
@@ -274,8 +273,8 @@ export async function compressImage(
274
273
  // String indirection defeats bundler static-analysis of dynamic
275
274
  // imports — Turbopack/webpack will leave this for the runtime to
276
275
  // resolve instead of failing the build when the optional peer dep
277
- // isn't installed. See feedback_bun_compile_dynamic_imports (same
278
- // trick used in reverse to opt OUT of bundling).
276
+ // isn't installed. (The same trick is used in reverse elsewhere in
277
+ // this SDK to opt OUT of bundling.)
279
278
  const compressorPkg = "@nitida/asset-compressor-web";
280
279
  const { compressImage: doCompress } = (await import(
281
280
  /* @vite-ignore */ /* webpackIgnore: true */ compressorPkg