@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/AGENTS.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Read `skills/nitida-sdk/SKILL.md` in this package first.** It is the canonical guide and it
|
|
4
4
|
ships inside the tarball, so it is already on disk in `node_modules/@nitida/sdk/skills/`. It
|
|
5
5
|
carries the things that cost real incidents to learn — the base36 tenant prefix, the width ladder,
|
|
6
|
-
the `original`-preset trap, the
|
|
6
|
+
the `original`-preset trap, the object-storage CORS boundary — not just the API shape.
|
|
7
7
|
|
|
8
8
|
**Then read `src/`.** This package publishes its TypeScript sources (`files: ["dist/**", "src/**"]`),
|
|
9
9
|
so the source *is* the reference: every public method carries TSDoc, and the ones worth copying
|
|
@@ -13,12 +13,12 @@ recalling an API from training data — this SDK moves.
|
|
|
13
13
|
## The five-minute version
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import {
|
|
16
|
+
import { NitidaClient } from "@nitida/sdk/server"; // uploads: server-only, holds the amk_rt_* key
|
|
17
17
|
import { getTransformUrl, setCdnBase, setTenantId } from "@nitida/asset-client"; // URLs: anywhere
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
- **Uploading** → `aq.upload(file, { fileName, contentType, presets })`. One call does compress →
|
|
21
|
-
sha256 → presign → direct-to-
|
|
21
|
+
sha256 → presign → direct-to-storage PUT → `/assets/process` → wait-until-ready.
|
|
22
22
|
- **Image URLs** → `getTransformUrl({ sha }, { format: "webp", width })`. The width MUST be on
|
|
23
23
|
`TRANSFORM_WIDTHS` or the edge answers **400**.
|
|
24
24
|
- **Video URLs** → `getAssetUrl({ sha }, "video")` after `setTenantId(id)`. The path segment is
|
|
@@ -30,14 +30,15 @@ import { getTransformUrl, setCdnBase, setTenantId } from "@nitida/asset-client";
|
|
|
30
30
|
`["thumb"]` and the bytes you PUT are *not* retrievable. A variant not requested at the FIRST
|
|
31
31
|
ingest cannot be added later once the cleanup job reaps `raw/`.
|
|
32
32
|
2. **The `amk_rt_*` key is server-only.** The browser talks to *your* route; your server talks to
|
|
33
|
-
|
|
34
|
-
3. **The PUT goes browser →
|
|
35
|
-
the bucket policy cannot be fixed in this SDK, in your app,
|
|
36
|
-
|
|
33
|
+
the platform. A runtime key in a client bundle is a write key to a paid platform.
|
|
34
|
+
3. **The PUT goes browser → object storage directly, so the STORAGE BUCKET answers the CORS
|
|
35
|
+
preflight.** An origin missing from the bucket policy cannot be fixed in this SDK, in your app,
|
|
36
|
+
or by the API's allowed-origins setting — ask us to add it. Symptom: the direct PUT fails with a
|
|
37
|
+
bare network error while every earlier step is green.
|
|
37
38
|
4. **Video is not image.** `/assets/process` filters video presets to
|
|
38
39
|
`{poster, video, aiproxy, probe}` — `original` is silently dropped — and a video finalizes
|
|
39
|
-
ASYNCHRONOUSLY: the call answers `{ assetId, status: "processing" }` and a
|
|
40
|
-
to `ready` 1–2 min later, so `processAndWait` needs a `timeoutMs` of at least `300_000`.
|
|
40
|
+
ASYNCHRONOUSLY: the call answers `{ assetId, status: "processing" }` and a background job flips
|
|
41
|
+
it to `ready` 1–2 min later, so `processAndWait` needs a `timeoutMs` of at least `300_000`.
|
|
41
42
|
|
|
42
43
|
## Related packages
|
|
43
44
|
|
package/README.md
CHANGED
|
@@ -721,11 +721,11 @@ const asset = await aq.assets.byHash(sha256);
|
|
|
721
721
|
> `2400`). `height` stays `number` — for the responsive path it's derived from `width`
|
|
722
722
|
> by aspect ratio; only fixed-canvas crops / `genfill` set it explicitly.
|
|
723
723
|
|
|
724
|
-
`format=auto` resolves to **WebP** by default.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
724
|
+
`format=auto` resolves to **WebP** by default. Our bench over 100 random
|
|
725
|
+
production `lg.webp` samples showed AVIF was 4.5–9.8% **larger** than WebP in every
|
|
726
|
+
source-size bracket with virtually identical SSIM. The policy is re-evaluated as
|
|
727
|
+
libavif improves, so `auto` may resolve differently in the future — that is the
|
|
728
|
+
point of asking for `auto` instead of naming a format.
|
|
729
729
|
|
|
730
730
|
`gravity=face` runs the source image through a ~1 MB Ultra-Light face
|
|
731
731
|
detector (ultraface-RFB-320 via ONNX Runtime) and crops to the
|
|
@@ -746,12 +746,11 @@ instance boots.
|
|
|
746
746
|
| normal (25–55) — most photos | 70 | 60 | 80 |
|
|
747
747
|
| complex (≥ 55) — busy textures | 72 | 65 | 82 |
|
|
748
748
|
|
|
749
|
-
Validated on 100 random
|
|
749
|
+
Validated on 100 random production `lg.webp` samples: **+15.4 %
|
|
750
750
|
bytes saved vs fixed `quality=80` baseline, |ΔSSIM| 0.0011** (budget
|
|
751
|
-
0.005).
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
re-running `bun run apps/asset-manager/scripts/bench-auto-quality.ts`.
|
|
751
|
+
0.005). The bucket boundaries and the per-format table are server-side
|
|
752
|
+
policy and may be re-tuned; pass an explicit `quality=` when you need a
|
|
753
|
+
number that does not move.
|
|
755
754
|
|
|
756
755
|
### Canonicalization & caching
|
|
757
756
|
|
|
@@ -844,19 +843,15 @@ const jpegUrl = aq.transform(asset, { format: "jpeg" });
|
|
|
844
843
|
// → https://8ok.uk/t/format=jpeg/<sha>.jpg
|
|
845
844
|
```
|
|
846
845
|
|
|
847
|
-
`effect=removebg`
|
|
848
|
-
|
|
846
|
+
`effect=removebg` runs on one of two server-side matting backends, chosen
|
|
847
|
+
per deployment. What the caller sees:
|
|
849
848
|
|
|
850
|
-
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
~5-7 s cold-start. Free runtime.
|
|
854
|
-
- **`replicate`** — proxies to Replicate's `851-labs/background-remover`
|
|
855
|
-
(BRIA-quality, commercially licensed via Replicate). ~3-8 s GPU,
|
|
856
|
-
~$0.001-0.005 per image. Requires `REPLICATE_API_TOKEN` in Secret
|
|
857
|
-
Manager.
|
|
849
|
+
- **CPU matting** (default) — ~1-2 s warm, ~5-7 s cold. No per-image cost.
|
|
850
|
+
- **GPU matting** — ~3-8 s, ~$0.001-0.005 per image, better edges on hair
|
|
851
|
+
and fine detail.
|
|
858
852
|
|
|
859
|
-
|
|
853
|
+
Either way the contract is the same, and the first request is the only one
|
|
854
|
+
that pays. In both cases the route caches the PNG in R2 under the standard
|
|
860
855
|
`<sha>-t<dslHash>.png` key, so subsequent identical requests are 302
|
|
861
856
|
redirects to the CDN — no inference, no per-image cost. **Always
|
|
862
857
|
forces `format=png`** because the entire point is preserving alpha.
|
|
@@ -865,12 +860,9 @@ The output is the same dimensions as the source. Chain with `width`
|
|
|
865
860
|
to resize the cutout in a single request (cached as one R2 entry per
|
|
866
861
|
canonical DSL).
|
|
867
862
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
3. Update the Dockerfile's `COPY src/features/assets/bg/*.onnx /app/models/`
|
|
872
|
-
4. Re-bench against a sample set (a CSV in `apps/asset-manager/bench/`
|
|
873
|
-
makes sense once the comparison is non-trivial).
|
|
863
|
+
The matting model is server-side and may be swapped for a better one
|
|
864
|
+
without any change on your side — the URL, the PNG-with-alpha output and
|
|
865
|
+
the cache semantics are the contract.
|
|
874
866
|
|
|
875
867
|
### Generative fill / aspect outpaint (`effect=genfill`)
|
|
876
868
|
|
|
@@ -923,9 +915,8 @@ Powered by Replicate's `black-forest-labs/flux-fill-pro`. ~$0.05 per
|
|
|
923
915
|
first request per (sha, dsl, format) tuple; subsequent identical
|
|
924
916
|
requests are 302 redirects to the R2 cache — zero Replicate cost.
|
|
925
917
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
provisioning needed.
|
|
918
|
+
Server-side credentials for the generative provider are part of the
|
|
919
|
+
deployment; nothing to provision on your side.
|
|
929
920
|
|
|
930
921
|
**Short-circuit**: when the source already matches the target aspect
|
|
931
922
|
exactly (resized to fill the canvas with zero padding), the server
|
|
@@ -1141,8 +1132,8 @@ const usage = await aq.usage.snapshot();
|
|
|
1141
1132
|
// {
|
|
1142
1133
|
// tenant: { id: 4, code: "realtyone-cr" },
|
|
1143
1134
|
// storage: { totalBytes: 4_810_000_000, assetCount: 15_760 },
|
|
1144
|
-
// today: { reads: 3201, writes: 18, processes: 6,
|
|
1145
|
-
// last30Days: { reads: 86_400, writes: 412, ... }
|
|
1135
|
+
// today: { reads: 3201, writes: 18, processes: 6, videoMinutes: 4.5, ... },
|
|
1136
|
+
// last30Days: { reads: 86_400, writes: 412, videoMinutes: 312.75, ... }
|
|
1146
1137
|
// }
|
|
1147
1138
|
|
|
1148
1139
|
const chart = await aq.usage.timeseries(30); // for a 30-day line chart
|
|
@@ -1151,7 +1142,24 @@ const byKey = await aq.usage.keys(); // who's using the most quota
|
|
|
1151
1142
|
|
|
1152
1143
|
Backed by the daily rollup of `assets.api_key_usage` →
|
|
1153
1144
|
`assets.tenant_usage_daily`. The "today" window queries the raw
|
|
1154
|
-
`api_key_usage` table since the rollup runs once per day at ~00:30 UTC
|
|
1145
|
+
`api_key_usage` table since the rollup runs once per day at ~00:30 UTC —
|
|
1146
|
+
except `videoMinutes`, which is accumulated live and so is real-time on
|
|
1147
|
+
every window.
|
|
1148
|
+
|
|
1149
|
+
**`videoMinutes` counts minutes actually TRANSCODED, not videos stored.**
|
|
1150
|
+
It is *source minutes × encode passes*, so a 7-rung HLS ladder over a
|
|
1151
|
+
2-minute clip books 14. Re-encoding a video you already uploaded counts
|
|
1152
|
+
**again** — that is the point: the asset row is upserted on `sha256`, so
|
|
1153
|
+
nothing else in the system can see a re-encode. A passthrough stream copy
|
|
1154
|
+
or a cache hit runs no encoder and books nothing, so `0` means zero.
|
|
1155
|
+
|
|
1156
|
+
> ⚠️ **Removed 2026-08-17:** `bytesIn` (on the usage windows and daily
|
|
1157
|
+
> points) and `bytesTotal` (on the per-key rows). They were fed by
|
|
1158
|
+
> `api_key_usage.bytes`, which is structurally zero — measured at 440,820
|
|
1159
|
+
> rows, none above 0 — and could not be instrumented, because uploads go to
|
|
1160
|
+
> R2 through presigned URLs and never traverse the API. A field that always
|
|
1161
|
+
> reads `0` is worse than an absent one. The byte figure that is true,
|
|
1162
|
+
> `storage.totalBytes`, is unchanged.
|
|
1155
1163
|
|
|
1156
1164
|
## License
|
|
1157
1165
|
|
package/dist/index.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ export { AssetDTO, AssetVariant, ResolveSlotOptions, SignedTransformOptions, Slo
|
|
|
52
52
|
*/
|
|
53
53
|
type NitidaClientOptions = {
|
|
54
54
|
/**
|
|
55
|
-
* Base URL of the
|
|
55
|
+
* Base URL of the nitida API (e.g. `https://api.nitida.gofuture.space`).
|
|
56
56
|
*
|
|
57
57
|
* May be relative (e.g. `/api/am`) ONLY in browser contexts where the
|
|
58
58
|
* SDK resolves it against `window.location.origin`. Node/Bun consumers
|
|
@@ -150,8 +150,8 @@ declare class SlotsApi {
|
|
|
150
150
|
/**
|
|
151
151
|
* Returned by `aq.assets.regenerate(...)`. The shape varies by kind —
|
|
152
152
|
* images return immediately with the merged variant list; videos
|
|
153
|
-
* return a dispatch handle (the actual transcode runs in a
|
|
154
|
-
*
|
|
153
|
+
* return a dispatch handle (the actual transcode runs in a background
|
|
154
|
+
* job and finishes async).
|
|
155
155
|
*/
|
|
156
156
|
type RegenerateResult = {
|
|
157
157
|
ok: true;
|
|
@@ -182,7 +182,7 @@ type RegenerateResult = {
|
|
|
182
182
|
/**
|
|
183
183
|
* Wire shape returned by `POST /assets/upload-url`. Either the server
|
|
184
184
|
* resolves the upload synchronously via dedup (`deduped: true` + existing
|
|
185
|
-
* asset DTO) or it returns a presigned
|
|
185
|
+
* asset DTO) or it returns a presigned storage PUT URL plus a `process` payload
|
|
186
186
|
* the caller must POST to `/assets/process` after the PUT lands.
|
|
187
187
|
*/
|
|
188
188
|
type UploadUrlResult = {
|
|
@@ -208,7 +208,7 @@ type UploadVideoOptions = {
|
|
|
208
208
|
/**
|
|
209
209
|
* `false` → skip the auto-dispatched HLS adaptive ladder (240p–2160p). Use
|
|
210
210
|
* for download-only assets served as a progressive `-v.mp4` and never
|
|
211
|
-
* streamed (e.g. share-video reels) — it avoids a second
|
|
211
|
+
* streamed (e.g. share-video reels) — it avoids a second background job no
|
|
212
212
|
* one watches. Default/absent → the ladder is generated as before.
|
|
213
213
|
*/
|
|
214
214
|
hls?: boolean;
|
|
@@ -224,7 +224,7 @@ type UploadVideoOptions = {
|
|
|
224
224
|
};
|
|
225
225
|
/** Input shape accepted by `aq.assets.presignUploadUrl(...)`. */
|
|
226
226
|
type PresignUploadUrlOptions = {
|
|
227
|
-
/** Full sha256 (64 hex) of the bytes that will be PUT to
|
|
227
|
+
/** Full sha256 (64 hex) of the bytes that will be PUT to storage. */
|
|
228
228
|
sha256: string;
|
|
229
229
|
/** MIME type of the bytes (e.g. `image/jpeg`, `video/mp4`). */
|
|
230
230
|
mime: string;
|
|
@@ -318,7 +318,7 @@ declare class AssetsApi {
|
|
|
318
318
|
* const v = await aq.assets.variants(logoId);
|
|
319
319
|
* v.map((x) => x.preset); // → ("thumb" | "sm" | … | "original")[]
|
|
320
320
|
*
|
|
321
|
-
* ⚠️ Returns `[]` — not an error — against
|
|
321
|
+
* ⚠️ Returns `[]` — not an error — against a server deploy older than the
|
|
322
322
|
* 2026-08-17 deploy, which never sent the field (doc 240 §4.3). An empty
|
|
323
323
|
* array is therefore "no variants OR old server". For a plain existence
|
|
324
324
|
* check prefer `hasPreset(dto, preset)` on `dto.presets`, which every server
|
|
@@ -347,7 +347,7 @@ declare class AssetsApi {
|
|
|
347
347
|
* bytes from `variants/o.<ext>` — no need to re-upload.
|
|
348
348
|
*
|
|
349
349
|
* Video presets are filtered to `["poster","video","aiproxy"]` and
|
|
350
|
-
* dispatched to the
|
|
350
|
+
* dispatched to the background job (the call returns immediately
|
|
351
351
|
* with a dispatch handle; poll `aq.assets.get(id).status` for
|
|
352
352
|
* completion).
|
|
353
353
|
*/
|
|
@@ -360,13 +360,13 @@ declare class AssetsApi {
|
|
|
360
360
|
metadata: Record<string, unknown>;
|
|
361
361
|
}>;
|
|
362
362
|
/**
|
|
363
|
-
* Request a presigned
|
|
363
|
+
* Request a presigned storage PUT URL for direct browser-side uploads.
|
|
364
364
|
*
|
|
365
365
|
* Mirrors the first half of `aq.upload()` — the caller (typically a
|
|
366
366
|
* BFF / share-link dropzone) computes sha256 in the browser, then
|
|
367
|
-
* uploads bytes straight to
|
|
368
|
-
* POSTs `process.body` to `/assets/process` (see {@link processAndWait})
|
|
369
|
-
* once
|
|
367
|
+
* uploads bytes straight to object storage with the returned `upload.url`,
|
|
368
|
+
* then POSTs `process.body` to `/assets/process` (see {@link processAndWait})
|
|
369
|
+
* once storage has the bytes.
|
|
370
370
|
*
|
|
371
371
|
* If the sha is already known to the tenant the server short-circuits
|
|
372
372
|
* with `{ deduped: true, asset }` — no PUT needed.
|
|
@@ -378,16 +378,16 @@ declare class AssetsApi {
|
|
|
378
378
|
* if (presign.deduped) return presign.asset; // those bytes already exist; none fly
|
|
379
379
|
*
|
|
380
380
|
* // BROWSER: PUT straight to presign.upload.url — the bytes never touch your server.
|
|
381
|
-
* // ⚠️
|
|
382
|
-
* // Symptom when it is not: "PUT failed: network error" with every earlier step
|
|
383
|
-
* // and it cannot be fixed in this SDK, in your app, or
|
|
381
|
+
* // ⚠️ The STORAGE BUCKET answers that preflight itself, so your origin must be in its CORS
|
|
382
|
+
* // policy. Symptom when it is not: "PUT failed: network error" with every earlier step
|
|
383
|
+
* // green — and it cannot be fixed in this SDK, in your app, or by the API's allowed origins.
|
|
384
384
|
*
|
|
385
385
|
* // SERVER again, forwarding presign.process.body VERBATIM:
|
|
386
386
|
* const asset = await aq.assets.processAndWait(presign.process.body, { timeoutMs: 300_000 });
|
|
387
387
|
* ```
|
|
388
388
|
*
|
|
389
389
|
* Works for images AND video. A video answers immediately with
|
|
390
|
-
* `{ assetId, status: "processing" }` while a
|
|
390
|
+
* `{ assetId, status: "processing" }` while a background job transcodes, so
|
|
391
391
|
* give `processAndWait` a bigger `timeoutMs` (a transcode + HLS ladder runs
|
|
392
392
|
* 1–2 min; 300_000 is a sane floor).
|
|
393
393
|
*/
|
|
@@ -397,7 +397,7 @@ declare class AssetsApi {
|
|
|
397
397
|
* {@link presignUploadUrl} call, then poll until the asset transitions
|
|
398
398
|
* to `ready` or `failed`. Throws on `failed` or timeout.
|
|
399
399
|
*
|
|
400
|
-
* Use this when bytes were uploaded directly from the browser to
|
|
400
|
+
* Use this when bytes were uploaded directly from the browser to storage —
|
|
401
401
|
* `aq.upload()` already does presign + PUT + process + wait in one
|
|
402
402
|
* step when the server holds the bytes.
|
|
403
403
|
*/
|
|
@@ -420,7 +420,7 @@ declare class AssetsApi {
|
|
|
420
420
|
* {@link waitReady} (typical timeout: 10 min for multi-segment kits).
|
|
421
421
|
*
|
|
422
422
|
* Tenant scope is inherited from the SDK client; `tenantCode` is added
|
|
423
|
-
* to the request body so the
|
|
423
|
+
* to the request body so the background job can resolve it without
|
|
424
424
|
* re-reading the header.
|
|
425
425
|
*/
|
|
426
426
|
composeMarketing(opts: ComposeMarketingOptions): Promise<ComposeMarketingResult>;
|
|
@@ -451,7 +451,7 @@ type UploadOptions = {
|
|
|
451
451
|
* MIME type of the bytes. **Only needed for a `Uint8Array` input** — a `File`/`Blob`
|
|
452
452
|
* already carries its `.type`. Raw bytes have no inherent MIME, so without this (and
|
|
453
453
|
* without an extension on `fileName` to infer from) they upload as
|
|
454
|
-
* `application/octet-stream`, which the
|
|
454
|
+
* `application/octet-stream`, which the server classifies as `kind:"other"` —
|
|
455
455
|
* meaning NO image/video variants are generated and `regenerate()` is unsupported.
|
|
456
456
|
* Resolution order for the effective MIME: `Blob.type` → `contentType` →
|
|
457
457
|
* inferred from `fileName`'s extension → `application/octet-stream`.
|
|
@@ -567,8 +567,35 @@ type UsageWindow = {
|
|
|
567
567
|
admins: number;
|
|
568
568
|
upscales: number;
|
|
569
569
|
processes: number;
|
|
570
|
-
|
|
570
|
+
/**
|
|
571
|
+
* Minutes of video actually TRANSCODED in the window — source minutes ×
|
|
572
|
+
* encode passes, so a 7-rung HLS ladder over a 2-minute clip books 14.
|
|
573
|
+
*
|
|
574
|
+
* A re-encode of a video already ingested counts AGAIN. That is the point:
|
|
575
|
+
* `assets.assets` cannot report it, because its insert is
|
|
576
|
+
* `ON CONFLICT DO UPDATE` and a re-encode creates no row there.
|
|
577
|
+
*
|
|
578
|
+
* A stream copy (`-c copy` passthrough, an idempotent cache skip) books
|
|
579
|
+
* nothing, so a tenant that ran no encoder reads `0` — and that `0` is real.
|
|
580
|
+
*/
|
|
581
|
+
videoMinutes: number;
|
|
582
|
+
/** Encode passes behind `videoMinutes`. 0 when nothing was encoded. */
|
|
583
|
+
videoEncodes: number;
|
|
571
584
|
};
|
|
585
|
+
/**
|
|
586
|
+
* ⚠️ REMOVED 2026-08-17: `bytesIn` on `UsageWindow`/`UsageDailyPoint` and
|
|
587
|
+
* `bytesTotal` on `UsagePerKey`.
|
|
588
|
+
*
|
|
589
|
+
* All three were structurally zero: the field behind them was never populated,
|
|
590
|
+
* on any row. They could not be instrumented in place either — uploads go to
|
|
591
|
+
* object storage through presigned URLs, so the payload never passes through
|
|
592
|
+
* the API, and the only figure measurable there is a JSON envelope of a few
|
|
593
|
+
* hundred bytes.
|
|
594
|
+
*
|
|
595
|
+
* A field that always reads `0` is worse than an absent field, because absent
|
|
596
|
+
* is honest — a `0` next to a real `storage.totalBytes` reads as a measurement.
|
|
597
|
+
* The byte number that IS true is still served: `storage.totalBytes`.
|
|
598
|
+
*/
|
|
572
599
|
type UsageDailyPoint = {
|
|
573
600
|
date: string;
|
|
574
601
|
reads: number;
|
|
@@ -576,15 +603,16 @@ type UsageDailyPoint = {
|
|
|
576
603
|
lists: number;
|
|
577
604
|
deletes: number;
|
|
578
605
|
processes: number;
|
|
579
|
-
bytesIn: number;
|
|
580
606
|
bytesStored: number;
|
|
607
|
+
/** See `UsageWindow.videoMinutes`. Minutes transcoded on this day. */
|
|
608
|
+
videoMinutes: number;
|
|
609
|
+
videoEncodes: number;
|
|
581
610
|
};
|
|
582
611
|
type UsagePerKey = {
|
|
583
612
|
apiKeyId: string;
|
|
584
613
|
prefix: string | null;
|
|
585
614
|
name: string | null;
|
|
586
615
|
opsTotal: number;
|
|
587
|
-
bytesTotal: number;
|
|
588
616
|
lastSeen: string;
|
|
589
617
|
};
|
|
590
618
|
declare class UsageApi {
|
|
@@ -644,7 +672,7 @@ declare class NitidaClient {
|
|
|
644
672
|
* Returns the canonical `lg` variant URL when called with empty options,
|
|
645
673
|
* so callers can swap `urlFor()` for `transform()` without thinking.
|
|
646
674
|
*
|
|
647
|
-
* URLs with the same params in different order produce the same
|
|
675
|
+
* URLs with the same params in different order produce the same
|
|
648
676
|
* cache entry (the server canonicalizes both sides). Safe to use as
|
|
649
677
|
* stable cache keys.
|
|
650
678
|
*
|
|
@@ -675,8 +703,8 @@ declare class NitidaClient {
|
|
|
675
703
|
* Build an on-the-fly VIDEO transform URL — Phase 4.
|
|
676
704
|
*
|
|
677
705
|
* Same DSL shape as `transform()` but the URL has a `.mp4` (default)
|
|
678
|
-
* or `.webm` extension and the server routes the request to a
|
|
679
|
-
*
|
|
706
|
+
* or `.webm` extension and the server routes the request to a
|
|
707
|
+
* background job for video encoding (vs the inline pipeline for
|
|
680
708
|
* images).
|
|
681
709
|
*
|
|
682
710
|
* On the first request the route returns **202 Accepted** with
|
|
@@ -716,7 +744,7 @@ declare class NitidaClient {
|
|
|
716
744
|
* />
|
|
717
745
|
*
|
|
718
746
|
* On the first request the server returns **202 Accepted** while a
|
|
719
|
-
*
|
|
747
|
+
* background job builds the multi-rung ladder (typically 1-3 min for
|
|
720
748
|
* a 90 s source — five rungs of 240p/360p/480p/720p/1080p @ AAC).
|
|
721
749
|
* Subsequent requests hit the cache → **302** to the master.m3u8.
|
|
722
750
|
*
|
|
@@ -735,7 +763,7 @@ declare class NitidaClient {
|
|
|
735
763
|
* but isn't always available depending on the runtime).
|
|
736
764
|
*/
|
|
737
765
|
/**
|
|
738
|
-
* Upload bytes end to end: optional client compression → sha256 → presign → **direct-to-
|
|
766
|
+
* Upload bytes end to end: optional client compression → sha256 → presign → **direct-to-storage PUT**
|
|
739
767
|
* → `/assets/process` → wait until the asset is ready.
|
|
740
768
|
*
|
|
741
769
|
* ⚠️ `presets` decides what exists FOREVER. Omit it and only `original` is written; ask for
|
|
@@ -775,8 +803,8 @@ declare class NitidaClient {
|
|
|
775
803
|
* // {poster, video, aiproxy, probe} before dispatching the transcode Job.
|
|
776
804
|
* await aq.upload(clip, { fileName: "tour.mp4", presets: ["poster", "video"] });
|
|
777
805
|
*
|
|
778
|
-
* // Omit `aiproxy`/`probe` unless the asset really goes to a vision model — they cost
|
|
779
|
-
* // and permanent
|
|
806
|
+
* // Omit `aiproxy`/`probe` unless the asset really goes to a vision model — they cost encode
|
|
807
|
+
* // time and permanent stored objects that nothing else reads.
|
|
780
808
|
* ```
|
|
781
809
|
*/
|
|
782
810
|
upload(input: File | Blob | Uint8Array, opts?: UploadOptions): Promise<UploadResult>;
|
package/dist/index.js
CHANGED
|
@@ -223,7 +223,7 @@ var AssetsApi = class {
|
|
|
223
223
|
* const v = await aq.assets.variants(logoId);
|
|
224
224
|
* v.map((x) => x.preset); // → ("thumb" | "sm" | … | "original")[]
|
|
225
225
|
*
|
|
226
|
-
* ⚠️ Returns `[]` — not an error — against
|
|
226
|
+
* ⚠️ Returns `[]` — not an error — against a server deploy older than the
|
|
227
227
|
* 2026-08-17 deploy, which never sent the field (doc 240 §4.3). An empty
|
|
228
228
|
* array is therefore "no variants OR old server". For a plain existence
|
|
229
229
|
* check prefer `hasPreset(dto, preset)` on `dto.presets`, which every server
|
|
@@ -255,7 +255,7 @@ var AssetsApi = class {
|
|
|
255
255
|
* bytes from `variants/o.<ext>` — no need to re-upload.
|
|
256
256
|
*
|
|
257
257
|
* Video presets are filtered to `["poster","video","aiproxy"]` and
|
|
258
|
-
* dispatched to the
|
|
258
|
+
* dispatched to the background job (the call returns immediately
|
|
259
259
|
* with a dispatch handle; poll `aq.assets.get(id).status` for
|
|
260
260
|
* completion).
|
|
261
261
|
*/
|
|
@@ -283,13 +283,13 @@ var AssetsApi = class {
|
|
|
283
283
|
return await r.json();
|
|
284
284
|
}
|
|
285
285
|
/**
|
|
286
|
-
* Request a presigned
|
|
286
|
+
* Request a presigned storage PUT URL for direct browser-side uploads.
|
|
287
287
|
*
|
|
288
288
|
* Mirrors the first half of `aq.upload()` — the caller (typically a
|
|
289
289
|
* BFF / share-link dropzone) computes sha256 in the browser, then
|
|
290
|
-
* uploads bytes straight to
|
|
291
|
-
* POSTs `process.body` to `/assets/process` (see {@link processAndWait})
|
|
292
|
-
* once
|
|
290
|
+
* uploads bytes straight to object storage with the returned `upload.url`,
|
|
291
|
+
* then POSTs `process.body` to `/assets/process` (see {@link processAndWait})
|
|
292
|
+
* once storage has the bytes.
|
|
293
293
|
*
|
|
294
294
|
* If the sha is already known to the tenant the server short-circuits
|
|
295
295
|
* with `{ deduped: true, asset }` — no PUT needed.
|
|
@@ -301,16 +301,16 @@ var AssetsApi = class {
|
|
|
301
301
|
* if (presign.deduped) return presign.asset; // those bytes already exist; none fly
|
|
302
302
|
*
|
|
303
303
|
* // BROWSER: PUT straight to presign.upload.url — the bytes never touch your server.
|
|
304
|
-
* // ⚠️
|
|
305
|
-
* // Symptom when it is not: "PUT failed: network error" with every earlier step
|
|
306
|
-
* // and it cannot be fixed in this SDK, in your app, or
|
|
304
|
+
* // ⚠️ The STORAGE BUCKET answers that preflight itself, so your origin must be in its CORS
|
|
305
|
+
* // policy. Symptom when it is not: "PUT failed: network error" with every earlier step
|
|
306
|
+
* // green — and it cannot be fixed in this SDK, in your app, or by the API's allowed origins.
|
|
307
307
|
*
|
|
308
308
|
* // SERVER again, forwarding presign.process.body VERBATIM:
|
|
309
309
|
* const asset = await aq.assets.processAndWait(presign.process.body, { timeoutMs: 300_000 });
|
|
310
310
|
* ```
|
|
311
311
|
*
|
|
312
312
|
* Works for images AND video. A video answers immediately with
|
|
313
|
-
* `{ assetId, status: "processing" }` while a
|
|
313
|
+
* `{ assetId, status: "processing" }` while a background job transcodes, so
|
|
314
314
|
* give `processAndWait` a bigger `timeoutMs` (a transcode + HLS ladder runs
|
|
315
315
|
* 1–2 min; 300_000 is a sane floor).
|
|
316
316
|
*/
|
|
@@ -338,7 +338,7 @@ var AssetsApi = class {
|
|
|
338
338
|
* {@link presignUploadUrl} call, then poll until the asset transitions
|
|
339
339
|
* to `ready` or `failed`. Throws on `failed` or timeout.
|
|
340
340
|
*
|
|
341
|
-
* Use this when bytes were uploaded directly from the browser to
|
|
341
|
+
* Use this when bytes were uploaded directly from the browser to storage —
|
|
342
342
|
* `aq.upload()` already does presign + PUT + process + wait in one
|
|
343
343
|
* step when the server holds the bytes.
|
|
344
344
|
*/
|
|
@@ -387,7 +387,7 @@ var AssetsApi = class {
|
|
|
387
387
|
* {@link waitReady} (typical timeout: 10 min for multi-segment kits).
|
|
388
388
|
*
|
|
389
389
|
* Tenant scope is inherited from the SDK client; `tenantCode` is added
|
|
390
|
-
* to the request body so the
|
|
390
|
+
* to the request body so the background job can resolve it without
|
|
391
391
|
* re-reading the header.
|
|
392
392
|
*/
|
|
393
393
|
async composeMarketing(opts) {
|
|
@@ -553,8 +553,8 @@ var NitidaClient = class {
|
|
|
553
553
|
* Build an on-the-fly VIDEO transform URL — Phase 4.
|
|
554
554
|
*
|
|
555
555
|
* Same DSL shape as `transform()` but the URL has a `.mp4` (default)
|
|
556
|
-
* or `.webm` extension and the server routes the request to a
|
|
557
|
-
*
|
|
556
|
+
* or `.webm` extension and the server routes the request to a
|
|
557
|
+
* background job for video encoding (vs the inline pipeline for
|
|
558
558
|
* images).
|
|
559
559
|
*
|
|
560
560
|
* On the first request the route returns **202 Accepted** with
|
|
@@ -596,7 +596,7 @@ var NitidaClient = class {
|
|
|
596
596
|
* />
|
|
597
597
|
*
|
|
598
598
|
* On the first request the server returns **202 Accepted** while a
|
|
599
|
-
*
|
|
599
|
+
* background job builds the multi-rung ladder (typically 1-3 min for
|
|
600
600
|
* a 90 s source — five rungs of 240p/360p/480p/720p/1080p @ AAC).
|
|
601
601
|
* Subsequent requests hit the cache → **302** to the master.m3u8.
|
|
602
602
|
*
|
|
@@ -617,7 +617,7 @@ var NitidaClient = class {
|
|
|
617
617
|
* but isn't always available depending on the runtime).
|
|
618
618
|
*/
|
|
619
619
|
/**
|
|
620
|
-
* Upload bytes end to end: optional client compression → sha256 → presign → **direct-to-
|
|
620
|
+
* Upload bytes end to end: optional client compression → sha256 → presign → **direct-to-storage PUT**
|
|
621
621
|
* → `/assets/process` → wait until the asset is ready.
|
|
622
622
|
*
|
|
623
623
|
* ⚠️ `presets` decides what exists FOREVER. Omit it and only `original` is written; ask for
|
|
@@ -657,8 +657,8 @@ var NitidaClient = class {
|
|
|
657
657
|
* // {poster, video, aiproxy, probe} before dispatching the transcode Job.
|
|
658
658
|
* await aq.upload(clip, { fileName: "tour.mp4", presets: ["poster", "video"] });
|
|
659
659
|
*
|
|
660
|
-
* // Omit `aiproxy`/`probe` unless the asset really goes to a vision model — they cost
|
|
661
|
-
* // and permanent
|
|
660
|
+
* // Omit `aiproxy`/`probe` unless the asset really goes to a vision model — they cost encode
|
|
661
|
+
* // time and permanent stored objects that nothing else reads.
|
|
662
662
|
* ```
|
|
663
663
|
*/
|
|
664
664
|
async upload(input, opts = {}) {
|
|
@@ -731,7 +731,7 @@ var NitidaClient = class {
|
|
|
731
731
|
body: new Blob([bytes], { type: mime })
|
|
732
732
|
});
|
|
733
733
|
if (!putR.ok)
|
|
734
|
-
throw new Error(`
|
|
734
|
+
throw new Error(`Storage PUT ${putR.status}: ${await putR.text()}`);
|
|
735
735
|
const procR = await fetch(endpointHref(this.opts, presign.process.url), {
|
|
736
736
|
method: "POST",
|
|
737
737
|
headers: {
|