@nitida/sdk 0.23.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/AGENTS.md +10 -9
- package/README.md +41 -33
- package/dist/index.d.ts +57 -29
- package/dist/index.js +19 -19
- package/dist/index.js.map +1 -1
- package/dist/server.js +19 -19
- package/dist/server.js.map +1 -1
- package/dist/web.js +19 -19
- package/dist/web.js.map +1 -1
- package/package.json +2 -2
- package/skills/nitida-sdk/SKILL.md +206 -75
- package/src/index.ts +60 -31
package/src/index.ts
CHANGED
|
@@ -78,7 +78,7 @@ import {
|
|
|
78
78
|
*/
|
|
79
79
|
export type NitidaClientOptions = {
|
|
80
80
|
/**
|
|
81
|
-
* Base URL of the
|
|
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
|
|
352
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
650
|
-
* POSTs `process.body` to `/assets/process` (see {@link processAndWait})
|
|
651
|
-
* once
|
|
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
|
-
* // ⚠️
|
|
664
|
-
* // Symptom when it is not: "PUT failed: network error" with every earlier step
|
|
665
|
-
* // and it cannot be fixed in this SDK, in your app, or
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
1240
|
-
*
|
|
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
|
-
*
|
|
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-
|
|
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
|
|
1352
|
-
* // and permanent
|
|
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
|
|
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(`
|
|
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",
|