@nitida/sdk 0.24.0 → 0.24.1

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
@@ -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
@@ -348,8 +348,8 @@ class SlotsApi {
348
348
  /**
349
349
  * Returned by `aq.assets.regenerate(...)`. The shape varies by kind —
350
350
  * 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).
351
+ * return a dispatch handle (the actual transcode runs in a background
352
+ * job and finishes async).
353
353
  */
354
354
  export type RegenerateResult =
355
355
  | {
@@ -383,7 +383,7 @@ export type RegenerateResult =
383
383
  /**
384
384
  * Wire shape returned by `POST /assets/upload-url`. Either the server
385
385
  * resolves the upload synchronously via dedup (`deduped: true` + existing
386
- * asset DTO) or it returns a presigned R2 PUT URL plus a `process` payload
386
+ * asset DTO) or it returns a presigned storage PUT URL plus a `process` payload
387
387
  * the caller must POST to `/assets/process` after the PUT lands.
388
388
  */
389
389
  export type UploadUrlResult =
@@ -403,7 +403,7 @@ export type UploadVideoOptions = {
403
403
  /**
404
404
  * `false` → skip the auto-dispatched HLS adaptive ladder (240p–2160p). Use
405
405
  * 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
406
+ * streamed (e.g. share-video reels) — it avoids a second background job no
407
407
  * one watches. Default/absent → the ladder is generated as before.
408
408
  */
409
409
  hls?: boolean;
@@ -420,7 +420,7 @@ export type UploadVideoOptions = {
420
420
 
421
421
  /** Input shape accepted by `aq.assets.presignUploadUrl(...)`. */
422
422
  export type PresignUploadUrlOptions = {
423
- /** Full sha256 (64 hex) of the bytes that will be PUT to R2. */
423
+ /** Full sha256 (64 hex) of the bytes that will be PUT to storage. */
424
424
  sha256: string;
425
425
  /** MIME type of the bytes (e.g. `image/jpeg`, `video/mp4`). */
426
426
  mime: string;
@@ -571,7 +571,7 @@ class AssetsApi {
571
571
  * const v = await aq.assets.variants(logoId);
572
572
  * v.map((x) => x.preset); // → ("thumb" | "sm" | … | "original")[]
573
573
  *
574
- * ⚠️ Returns `[]` — not an error — against an `asset-manager` older than the
574
+ * ⚠️ Returns `[]` — not an error — against a server deploy older than the
575
575
  * 2026-08-17 deploy, which never sent the field (doc 240 §4.3). An empty
576
576
  * array is therefore "no variants OR old server". For a plain existence
577
577
  * check prefer `hasPreset(dto, preset)` on `dto.presets`, which every server
@@ -606,7 +606,7 @@ class AssetsApi {
606
606
  * bytes from `variants/o.<ext>` — no need to re-upload.
607
607
  *
608
608
  * Video presets are filtered to `["poster","video","aiproxy"]` and
609
- * dispatched to the Cloud Run Job (the call returns immediately
609
+ * dispatched to the background job (the call returns immediately
610
610
  * with a dispatch handle; poll `aq.assets.get(id).status` for
611
611
  * completion).
612
612
  */
@@ -642,13 +642,13 @@ class AssetsApi {
642
642
  }
643
643
 
644
644
  /**
645
- * Request a presigned R2 PUT URL for direct browser-side uploads.
645
+ * Request a presigned storage PUT URL for direct browser-side uploads.
646
646
  *
647
647
  * Mirrors the first half of `aq.upload()` — the caller (typically a
648
648
  * 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.
649
+ * uploads bytes straight to object storage with the returned `upload.url`,
650
+ * then POSTs `process.body` to `/assets/process` (see {@link processAndWait})
651
+ * once storage has the bytes.
652
652
  *
653
653
  * If the sha is already known to the tenant the server short-circuits
654
654
  * with `{ deduped: true, asset }` — no PUT needed.
@@ -660,16 +660,16 @@ class AssetsApi {
660
660
  * if (presign.deduped) return presign.asset; // those bytes already exist; none fly
661
661
  *
662
662
  * // 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`.
663
+ * // ⚠️ The STORAGE BUCKET answers that preflight itself, so your origin must be in its CORS
664
+ * // policy. Symptom when it is not: "PUT failed: network error" with every earlier step
665
+ * // green — and it cannot be fixed in this SDK, in your app, or by the API's allowed origins.
666
666
  *
667
667
  * // SERVER again, forwarding presign.process.body VERBATIM:
668
668
  * const asset = await aq.assets.processAndWait(presign.process.body, { timeoutMs: 300_000 });
669
669
  * ```
670
670
  *
671
671
  * Works for images AND video. A video answers immediately with
672
- * `{ assetId, status: "processing" }` while a Cloud Run Job transcodes, so
672
+ * `{ assetId, status: "processing" }` while a background job transcodes, so
673
673
  * give `processAndWait` a bigger `timeoutMs` (a transcode + HLS ladder runs
674
674
  * 1–2 min; 300_000 is a sane floor).
675
675
  */
@@ -701,7 +701,7 @@ class AssetsApi {
701
701
  * {@link presignUploadUrl} call, then poll until the asset transitions
702
702
  * to `ready` or `failed`. Throws on `failed` or timeout.
703
703
  *
704
- * Use this when bytes were uploaded directly from the browser to R2
704
+ * Use this when bytes were uploaded directly from the browser to storage
705
705
  * `aq.upload()` already does presign + PUT + process + wait in one
706
706
  * step when the server holds the bytes.
707
707
  */
@@ -755,7 +755,7 @@ class AssetsApi {
755
755
  * {@link waitReady} (typical timeout: 10 min for multi-segment kits).
756
756
  *
757
757
  * 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
758
+ * to the request body so the background job can resolve it without
759
759
  * re-reading the header.
760
760
  */
761
761
  async composeMarketing(
@@ -858,7 +858,7 @@ export type UploadOptions = {
858
858
  * MIME type of the bytes. **Only needed for a `Uint8Array` input** — a `File`/`Blob`
859
859
  * already carries its `.type`. Raw bytes have no inherent MIME, so without this (and
860
860
  * without an extension on `fileName` to infer from) they upload as
861
- * `application/octet-stream`, which the asset-manager classifies as `kind:"other"` —
861
+ * `application/octet-stream`, which the server classifies as `kind:"other"` —
862
862
  * meaning NO image/video variants are generated and `regenerate()` is unsupported.
863
863
  * Resolution order for the effective MIME: `Blob.type` → `contentType` →
864
864
  * inferred from `fileName`'s extension → `application/octet-stream`.
@@ -1002,9 +1002,37 @@ export type UsageWindow = {
1002
1002
  admins: number;
1003
1003
  upscales: number;
1004
1004
  processes: number;
1005
- bytesIn: number;
1005
+ /**
1006
+ * Minutes of video actually TRANSCODED in the window — source minutes ×
1007
+ * encode passes, so a 7-rung HLS ladder over a 2-minute clip books 14.
1008
+ *
1009
+ * A re-encode of a video already ingested counts AGAIN. That is the point:
1010
+ * `assets.assets` cannot report it, because its insert is
1011
+ * `ON CONFLICT DO UPDATE` and a re-encode creates no row there.
1012
+ *
1013
+ * A stream copy (`-c copy` passthrough, an idempotent cache skip) books
1014
+ * nothing, so a tenant that ran no encoder reads `0` — and that `0` is real.
1015
+ */
1016
+ videoMinutes: number;
1017
+ /** Encode passes behind `videoMinutes`. 0 when nothing was encoded. */
1018
+ videoEncodes: number;
1006
1019
  };
1007
1020
 
1021
+ /**
1022
+ * ⚠️ REMOVED 2026-08-17: `bytesIn` on `UsageWindow`/`UsageDailyPoint` and
1023
+ * `bytesTotal` on `UsagePerKey`.
1024
+ *
1025
+ * All three were structurally zero: the field behind them was never populated,
1026
+ * on any row. They could not be instrumented in place either — uploads go to
1027
+ * object storage through presigned URLs, so the payload never passes through
1028
+ * the API, and the only figure measurable there is a JSON envelope of a few
1029
+ * hundred bytes.
1030
+ *
1031
+ * A field that always reads `0` is worse than an absent field, because absent
1032
+ * is honest — a `0` next to a real `storage.totalBytes` reads as a measurement.
1033
+ * The byte number that IS true is still served: `storage.totalBytes`.
1034
+ */
1035
+
1008
1036
  export type UsageDailyPoint = {
1009
1037
  date: string;
1010
1038
  reads: number;
@@ -1012,8 +1040,10 @@ export type UsageDailyPoint = {
1012
1040
  lists: number;
1013
1041
  deletes: number;
1014
1042
  processes: number;
1015
- bytesIn: number;
1016
1043
  bytesStored: number;
1044
+ /** See `UsageWindow.videoMinutes`. Minutes transcoded on this day. */
1045
+ videoMinutes: number;
1046
+ videoEncodes: number;
1017
1047
  };
1018
1048
 
1019
1049
  export type UsagePerKey = {
@@ -1021,7 +1051,6 @@ export type UsagePerKey = {
1021
1051
  prefix: string | null;
1022
1052
  name: string | null;
1023
1053
  opsTotal: number;
1024
- bytesTotal: number;
1025
1054
  lastSeen: string;
1026
1055
  };
1027
1056
 
@@ -1136,7 +1165,7 @@ export class NitidaClient {
1136
1165
  * Returns the canonical `lg` variant URL when called with empty options,
1137
1166
  * so callers can swap `urlFor()` for `transform()` without thinking.
1138
1167
  *
1139
- * URLs with the same params in different order produce the same R2
1168
+ * URLs with the same params in different order produce the same
1140
1169
  * cache entry (the server canonicalizes both sides). Safe to use as
1141
1170
  * stable cache keys.
1142
1171
  *
@@ -1236,8 +1265,8 @@ export class NitidaClient {
1236
1265
  * Build an on-the-fly VIDEO transform URL — Phase 4.
1237
1266
  *
1238
1267
  * 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
1268
+ * or `.webm` extension and the server routes the request to a
1269
+ * background job for video encoding (vs the inline pipeline for
1241
1270
  * images).
1242
1271
  *
1243
1272
  * On the first request the route returns **202 Accepted** with
@@ -1283,7 +1312,7 @@ export class NitidaClient {
1283
1312
  * />
1284
1313
  *
1285
1314
  * 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
1315
+ * background job builds the multi-rung ladder (typically 1-3 min for
1287
1316
  * a 90 s source — five rungs of 240p/360p/480p/720p/1080p @ AAC).
1288
1317
  * Subsequent requests hit the cache → **302** to the master.m3u8.
1289
1318
  *
@@ -1308,7 +1337,7 @@ export class NitidaClient {
1308
1337
  * but isn't always available depending on the runtime).
1309
1338
  */
1310
1339
  /**
1311
- * Upload bytes end to end: optional client compression → sha256 → presign → **direct-to-R2 PUT**
1340
+ * Upload bytes end to end: optional client compression → sha256 → presign → **direct-to-storage PUT**
1312
1341
  * → `/assets/process` → wait until the asset is ready.
1313
1342
  *
1314
1343
  * ⚠️ `presets` decides what exists FOREVER. Omit it and only `original` is written; ask for
@@ -1348,8 +1377,8 @@ export class NitidaClient {
1348
1377
  * // {poster, video, aiproxy, probe} before dispatching the transcode Job.
1349
1378
  * await aq.upload(clip, { fileName: "tour.mp4", presets: ["poster", "video"] });
1350
1379
  *
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.
1380
+ * // Omit `aiproxy`/`probe` unless the asset really goes to a vision model — they cost encode
1381
+ * // time and permanent stored objects that nothing else reads.
1353
1382
  * ```
1354
1383
  */
1355
1384
  async upload(
@@ -1360,7 +1389,7 @@ export class NitidaClient {
1360
1389
  // "should we compress this?")
1361
1390
  const sourceIsBlob = input instanceof File || input instanceof Blob;
1362
1391
  // 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
1392
+ // the file extension, before octet-stream (which the server files as
1364
1393
  // `kind:"other"` — no variants). A File/Blob's own `.type` always wins when present.
1365
1394
  const sourceMime =
1366
1395
  (sourceIsBlob ? input.type : "") ||
@@ -1494,7 +1523,7 @@ export class NitidaClient {
1494
1523
  body: new Blob([bytes as unknown as ArrayBuffer], { type: mime }),
1495
1524
  });
1496
1525
  if (!putR.ok)
1497
- throw new Error(`R2 PUT ${putR.status}: ${await putR.text()}`);
1526
+ throw new Error(`Storage PUT ${putR.status}: ${await putR.text()}`);
1498
1527
 
1499
1528
  const procR = await fetch(endpointHref(this.opts, presign.process.url), {
1500
1529
  method: "POST",