@nitida/sdk 0.22.0 → 0.24.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/README.md +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/server.js +6 -0
- package/dist/server.js.map +1 -1
- package/dist/web.js +6 -0
- package/dist/web.js.map +1 -1
- package/package.json +5 -13
- package/skills/nitida-sdk/SKILL.md +135 -49
- package/src/index.ts +8 -2
|
@@ -1,33 +1,68 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nitida-sdk
|
|
3
|
-
description: How to consume the nitida media platform (
|
|
3
|
+
description: How to consume the nitida media platform (@nitida/sdk + @nitida/asset-client, API api.nitida.gofuture.space, CDN 8ok.uk) from an app — build image/video URLs, resolve stored refs, migrate an app off a legacy CDN, and provision a tenant. Use when wiring images/videos through nitida, debugging 400/404/410 on 8ok.uk URLs, or onboarding a new tenant. Captures the gotchas that bit real migrations (base36 video prefix, /t/ vs /v/, the width ladder, audio, the 0-cdn build gate).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Consuming the nitida media platform
|
|
7
7
|
|
|
8
|
-
>
|
|
8
|
+
> **The product is called nitida.** It used to be called *aquienpz*, and that name survives on
|
|
9
|
+
> purpose in three places that were NOT renamed: the GitHub repo (`aquienpz`), the CDN host
|
|
10
|
+
> (`8ok.uk`), and three packages that still publish under `@aquienpz/*` (`tenant-config`,
|
|
11
|
+
> `asset-uploader-web`, `asset-uploader-expo`). Everything else you install or import is
|
|
12
|
+
> `@nitida/*`.
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
> Durable source of truth: **the published docs at <https://nitida.gofuture.space>** — 187 pages, 166
|
|
15
|
+
> of them generated from the types, so the API reference cannot rot. Agents can download it: the site
|
|
16
|
+
> serves `/agents/skill.md`, `/agents/index.json`, `llms.txt` / `llms-small.txt` / `llms-full.txt`,
|
|
17
|
+
> and every page as raw `.md`. Source lives in `apps/nitida-docs`.
|
|
18
|
+
>
|
|
19
|
+
> ⚠️ An earlier version of this line pointed at `docs/SDK_CONSUMER_GUIDE.md`. **That file does not
|
|
20
|
+
> exist** (verified 2026-08-17) and did not when the line was written — a citation to nothing is
|
|
21
|
+
> worse than a wrong one, because it sends the reader hunting instead of correcting them. The three
|
|
22
|
+
> other docs this file cites (`ASSET_MANAGER_V2.md`, `CLIENT_OPERATIONS.md`, `TENANT_ONBOARDING.md`)
|
|
23
|
+
> were checked and DO exist. This file is mirrored to `~/.claude/skills/nitida-sdk/SKILL.md` for skill
|
|
24
|
+
> activation; update both when these learnings change.
|
|
25
|
+
|
|
26
|
+
The platform stores assets in R2, is driven through the API **`https://api.nitida.gofuture.space`**
|
|
27
|
+
and serves bytes through the CDN **`https://8ok.uk`**.
|
|
11
28
|
Apps are **external consumers**: install the npm packages, never vendor neo's in-tree copies.
|
|
12
29
|
|
|
13
30
|
```bash
|
|
14
31
|
bun add @nitida/sdk @nitida/asset-client
|
|
15
32
|
```
|
|
16
33
|
|
|
17
|
-
Both packages export the same URL builders (`@nitida/sdk` re-exports `@nitida/asset-client`). For plain URL-building in a Next.js app, importing from `@nitida/asset-client` is enough; use `@nitida/sdk`'s `
|
|
34
|
+
Both packages export the same URL builders (`@nitida/sdk` re-exports `@nitida/asset-client`). For plain URL-building in a Next.js app, importing from `@nitida/asset-client` is enough; use `@nitida/sdk`'s `NitidaClient` only when you also upload.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { NitidaClient } from "@nitida/sdk";
|
|
38
|
+
|
|
39
|
+
const nt = new NitidaClient({
|
|
40
|
+
endpoint: "https://api.nitida.gofuture.space",
|
|
41
|
+
apiKey: process.env.NITIDA_API_KEY!, // amk_rt_… — server-only
|
|
42
|
+
tenantCode: "your-code",
|
|
43
|
+
tenantId: 12,
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
:::note[The old Cloud Run address still works, and always will]
|
|
48
|
+
Until 2026-08-17 the published endpoint was
|
|
49
|
+
`https://aquienpz-asset-manager-nlchzy26qa-uc.a.run.app`. The branded name is an **additional**
|
|
50
|
+
mapping onto the *same* Cloud Run service — same instance, no extra hop, byte-identical responses —
|
|
51
|
+
and Cloud Run keeps a service's generated URL forever. Nothing breaks if you are still on it; point
|
|
52
|
+
new work at `api.nitida.gofuture.space` and move the rest whenever it suits you.
|
|
53
|
+
:::
|
|
18
54
|
|
|
19
55
|
## 1. Configure once at module load
|
|
20
56
|
|
|
21
|
-
The URL builders read **process-global** config. Pin it where your helpers live (a `lib/
|
|
57
|
+
The URL builders read **process-global** config. Pin it where your helpers live (a `lib/nitida-images.ts`), so every Server/Client Component that imports a builder is configured:
|
|
22
58
|
|
|
23
59
|
```ts
|
|
24
60
|
import { setCdnBase, setTenantId } from "@nitida/asset-client";
|
|
25
61
|
|
|
26
|
-
const
|
|
27
|
-
const TENANT_ID = Number(process.env.NEXT_PUBLIC_AQUIENPZ_TENANT_ID) || /* your id */ 0;
|
|
62
|
+
const TENANT_ID = Number(process.env.NEXT_PUBLIC_NITIDA_TENANT_ID) || /* your id */ 0;
|
|
28
63
|
|
|
29
|
-
setCdnBase(
|
|
30
|
-
setTenantId(TENANT_ID);
|
|
64
|
+
setCdnBase("https://8ok.uk"); // OPTIONAL — this is already the default; set it only if you were given another host
|
|
65
|
+
setTenantId(TENANT_ID); // REQUIRED for video URLs (base36 variant prefix) — see §3
|
|
31
66
|
```
|
|
32
67
|
|
|
33
68
|
`NEXT_PUBLIC_*` so the tenant id reaches the client bundle (hero/about videos render client-side). The runtime read-only consumer needs only these public vars — the `amk_rt_*` runtime key is for **uploads** (server-only), not for building URLs.
|
|
@@ -77,7 +112,31 @@ Output: `https://8ok.uk/<tenantId.toString(36)>/v/<sha16>-v.mp4`.
|
|
|
77
112
|
- ❌ Use `getVideoTransformUrl` — that builds a `/t/...` transform URL, which **410s** for a stored video sha. (`getVideoTransformUrl` is for on-the-fly re-encodes, a different feature.)
|
|
78
113
|
- ❌ Hand-roll the path with the decimal tenant id. The path segment is **base36**: `tenantId.toString(36)`. **Tenant 10 → `/a/v/`**, and the decimal `/10/v/` **404s**. This is invisible for tenants ≤ 9 (`8`→`8`, `9`→`9`) and bit a real migration only at tenant 10. Always delegate to `getAssetUrl` so the encoding can't drift.
|
|
79
114
|
|
|
80
|
-
**Audio IS supported (updated 2026-07-01 — verify against the SDK types, this used to say "not supported").** The platform
|
|
115
|
+
**Audio IS supported (updated 2026-07-01 — verify against the SDK types, this used to say "not supported").** The platform recognizes `kind: "image" | "video" | "document" | "audio" | "other"` and ships an **`mp3`** variant preset (`VariantPreset` in `@nitida/asset-client`). Uploads are hash-deduped (byte-identical re-uploads return the existing sha — that's *byte* dedup, NOT semantic "find a similar track"). `nt.upload` also accepts an `audioTrack` on video-composition calls. Serve via the `mp3` preset / `original`. Confirm the current preset/kind list in `node_modules/@nitida/asset-client/dist/index.d.ts` before relying on a specific ext.
|
|
116
|
+
|
|
117
|
+
> ✅ **FIXED 2026-08-17 (aquienpz #230/#231): you no longer have to pass `contentType` to avoid
|
|
118
|
+
> `kind:"other"`.** This section used to insist on
|
|
119
|
+
> `upload(bytes, { contentType: "audio/mpeg", fileName: "track.mp3" })` as a *defence*. Two
|
|
120
|
+
> independent holes made that necessary and both are closed:
|
|
121
|
+
>
|
|
122
|
+
> - **The mime round-trip lost the type.** Presign derives the stored extension with
|
|
123
|
+
> `mime.extension(body.mime)`, so `audio/mpeg` became `.mpga` — and the server's hand-written
|
|
124
|
+
> ext→mime `switch` did not know `.mpga`, so it came back as `application/octet-stream` ⇒
|
|
125
|
+
> `kind:"other"` ⇒ no variants. That swallowed **104 MP3 uploads**. `detectMime` now derives from
|
|
126
|
+
> the same `mime-types` table that produced the extension, closing the whole class by construction
|
|
127
|
+
> (7 broken round-trips fixed: `mpga`, `heif`, `adts`, `tif`, `bmp`, `svg`, `flac`), and
|
|
128
|
+
> `test/mime-roundtrip.test.ts` keeps it closed.
|
|
129
|
+
> - **Genuinely untyped uploads are rescued by their bytes.** When the key implies
|
|
130
|
+
> `application/octet-stream`, `/process` sniffs the magic bytes (`sniffMagicMime`) and processes
|
|
131
|
+
> the file as what it actually is.
|
|
132
|
+
>
|
|
133
|
+
> Passing `contentType` + `fileName` is still *good practice* — it is the cheapest possible signal
|
|
134
|
+
> and it decides the stored extension. It is no longer load-bearing.
|
|
135
|
+
>
|
|
136
|
+
> ⚠️ **Against an `asset-manager` older than 2026-08-17 this bug is live**, so code that must run
|
|
137
|
+
> against an old deploy should keep declaring the MIME explicitly. Also note the owner's decision:
|
|
138
|
+
> the **230 rows already misclassified** as `kind:"other"` are NOT being repaired — the fix is
|
|
139
|
+
> forward-only.
|
|
81
140
|
|
|
82
141
|
## 3b. Keeping the ORIGINAL bytes (backup, not delivery) ← learned the hard way 2026-08-07
|
|
83
142
|
|
|
@@ -106,30 +165,35 @@ variantKey(tenantId, sha256, "original", ext)
|
|
|
106
165
|
|
|
107
166
|
The client's `PRESET_EXT.original` is the literal `"bin"`, so `getAssetUrl({sha}, "original")` **without `mime`** emits `-o.bin` while a PNG is really stored at `-o.png` → 404, always.
|
|
108
167
|
|
|
109
|
-
>
|
|
110
|
-
>
|
|
111
|
-
>
|
|
168
|
+
> ✅ **FIXED 2026-08-17 (doc 240 §4.3 + §4.4). Pass the whole DTO and it is simply right:**
|
|
169
|
+
>
|
|
170
|
+
> ```ts
|
|
171
|
+
> const asset = await nt.assets.get(id);
|
|
172
|
+
> getAssetUrl(asset, "original"); // → the stored key, verbatim
|
|
173
|
+
> ```
|
|
112
174
|
>
|
|
113
|
-
>
|
|
114
|
-
>
|
|
115
|
-
>
|
|
116
|
-
> - **`getAssetUrl` now takes the asset's `mime`** and resolves a real extension:
|
|
117
|
-
> `getAssetUrl({ sha, mime }, "original")` → `…-o.webp`, HTTP **200**. The old `-o.bin` sentinel
|
|
118
|
-
> only appears when you omit `mime`.
|
|
119
|
-
> - **…but the extension the SERVER stored comes from the uploaded FILENAME, not the mime.**
|
|
120
|
-
> `variantKey(..., ext)` uses `rawKey.split(".").pop()`. For JPEG these disagree —
|
|
121
|
-
> `image/jpeg` → `jpeg` while a camera writes `.jpg`. Measured: `…-o.jpeg` **404**, `…-o.jpg` 200.
|
|
175
|
+
> `GET /assets/:id` now sends `variants` (the full list, URLs included) and `oext` (the extension
|
|
176
|
+
> the original was really stored under). `getAssetUrl` prefers the stored URL, falls back to
|
|
177
|
+
> `oext`, and only then guesses from the mime.
|
|
122
178
|
>
|
|
123
|
-
>
|
|
124
|
-
> `
|
|
125
|
-
>
|
|
126
|
-
>
|
|
127
|
-
> (`
|
|
179
|
+
> **Why guessing could never work.** The server keys the original off the **uploaded filename**
|
|
180
|
+
> (`rawKey.split(".").pop()`), which the mime does not determine. Measured over the 2 001 stored
|
|
181
|
+
> originals in production: all **420** `image/jpeg` originals are `.jpg` and none are `.jpeg`, so
|
|
182
|
+
> the old mime table 404'd on every JPEG; and **234** originals are `application/octet-stream`
|
|
183
|
+
> (`.mpga`, `.docx`, `.m4a`), where no mime table can ever produce the right key.
|
|
184
|
+
>
|
|
185
|
+
> Two things that did NOT change: existence still comes from `dto.presets` + `hasPreset` (the only
|
|
186
|
+
> field on every response shape), and `getAssetUrl` was always correct for `video`/`poster`, whose
|
|
187
|
+
> extensions are fixed.
|
|
188
|
+
>
|
|
189
|
+
> ⚠️ **Against an `asset-manager` older than 2026-08-17**, `variants` is `[]` and `oext` is absent
|
|
190
|
+
> — the mime guess is all you have, so HEAD the URL before relying on it. Reference implementation
|
|
191
|
+
> of the old dance: `apps/media-harness/scripts/seed-fixtures.ts` (`resolveOriginalUrl`).
|
|
128
192
|
|
|
129
193
|
**3. Re-uploading does NOT repair a missing preset.** Ingest dedupes by hash and returns `deduped: true` with the existing asset; no variants are regenerated. To add a preset to an existing asset use `regenerate`, which MERGES:
|
|
130
194
|
|
|
131
195
|
```ts
|
|
132
|
-
await
|
|
196
|
+
await nt.assets.regenerate(assetId, { presets: ["original"] });
|
|
133
197
|
```
|
|
134
198
|
|
|
135
199
|
⚠️ **There is a time window.** `admin.routes.ts` notes that once the cleanup job reaps `raw/`, regenerate reads from `variants/o.<ext>` — which is exactly what's missing. **If the first ingest didn't request `original`, the raw bytes may already be unrecoverable.** In the 2026-08-07 run, `regenerate({presets:["original"]})` returned without error and the `original` variant still did not appear — consistent with `raw/` already being gone. Get `original` right on the FIRST ingest.
|
|
@@ -139,7 +203,29 @@ await aq.assets.regenerate(assetId, { presets: ["original"] });
|
|
|
139
203
|
| Goal | presets | Where the URL comes from |
|
|
140
204
|
|---|---|---|
|
|
141
205
|
| Deliver images on a site | `["thumb"]` (+ what you need) | `getTransformUrl` / `getTransformSrcSet` |
|
|
142
|
-
| **Archive the source file** | **`["original", "thumb"]`** | **`
|
|
206
|
+
| **Archive the source file** | **`["original", "thumb"]`** | **`getAssetUrl(await nt.assets.get(id), "original")`** |
|
|
207
|
+
|
|
208
|
+
### 3b-bis. `hasPreset` is the existence check, and it stopped lying too
|
|
209
|
+
|
|
210
|
+
`presets` is documented as a concatenation of **one-character** codes, and membership is a
|
|
211
|
+
one-character `includes`. It used to carry multi-character tokens as well (`transform-3da0019…`,
|
|
212
|
+
`probe`), and every one of them answered `true` for presets that do not exist. Counted on live
|
|
213
|
+
rows: **16 299** assets were told they had an `original` they did not have — sending callers to
|
|
214
|
+
exactly the 404 that `oext` came to remove — plus 16 479 phantom `aiproxy` and 487 phantom `sm`/`md`.
|
|
215
|
+
|
|
216
|
+
✅ **Fixed 2026-08-17 (#228) on both sides:** the server emits only the 1-char vocabulary (plus the
|
|
217
|
+
sanctioned `mp3` token, which is deliberate and stays), and `hasPreset` strips multi-char tokens
|
|
218
|
+
before the `includes`. **`hasPreset(dto, preset)` remains the recommended existence check** — it is
|
|
219
|
+
the only field present on every response shape, including the slim list/resolver one that carries no
|
|
220
|
+
`variants` at all.
|
|
221
|
+
|
|
222
|
+
⚠️ **Against an `asset-manager` older than 2026-08-17 the contaminated strings are still being sent**
|
|
223
|
+
(62 % of live rows had one). The client-side strip covers you; a hand-rolled
|
|
224
|
+
`dto.presets.includes("o")` does not.
|
|
225
|
+
|
|
226
|
+
⚠️ **`mp3` is a real, multi-char token in the vocabulary on purpose.** Any substring test you write
|
|
227
|
+
yourself must discount it — otherwise the `m` of `mp3` reads as `md`. That is a live bug class in
|
|
228
|
+
consumer code, not in the SDK.
|
|
143
229
|
|
|
144
230
|
## 3c. PLAYING a video — HLS, and the snippet that went stale in April 2026
|
|
145
231
|
|
|
@@ -226,33 +312,32 @@ const sha = extractAssetSha(storedUrl); // → "e0ef99988f4a3c9b" | null
|
|
|
226
312
|
|
|
227
313
|
A robust app resolver tries, in order: an explicit `sha` on the row → a migrated-metadata blob → a migration-map lookup by old URL → `extractAssetSha` → raw fallback.
|
|
228
314
|
|
|
229
|
-
## 5. Migrating an app OFF a legacy CDN onto
|
|
315
|
+
## 5. Migrating an app OFF a legacy CDN onto nitida
|
|
230
316
|
|
|
231
317
|
Pattern proven on carniceria, novatibas, sueños (all off `cdn.espaciofuturo.io`):
|
|
232
318
|
|
|
233
|
-
1. **Re-ingest is required, not a host rename.** Legacy hashes (64-hex sha256) ≠
|
|
319
|
+
1. **Re-ingest is required, not a host rename.** Legacy hashes (64-hex sha256) ≠ nitida (16-hex sha). You must **fetch each legacy asset's bytes and re-upload** to the tenant (SDK server client + `amk_rt_*` key), producing a NEW sha. Emit an idempotent `old-URL → { sha, w, h, blur, kind }` map (`scripts/asset-migration-map.json`). Upload presets: images→`thumb`, videos→`poster`+`video`. Dedupe by sha; checkpoint (re-runnable).
|
|
234
320
|
2. **Regenerate metadata** (`blur`/`w`/`h`/`aspectRatio`) from the uploaded asset (`assets.get`) — don't carry legacy values.
|
|
235
321
|
3. **Render: bake resolved `8ok.uk` URLs at author time.** Do NOT `import` the map JSON into runtime code — its old-URL *keys* ship into the client/server bundle and fail a "0 legacy refs in built output" check. Resolve at build/author time and write the literal `8ok.uk` URL.
|
|
236
322
|
4. **Next.js**: add `8ok.uk` to `images.remotePatterns`. With `images.unoptimized: true`, pass the CDN srcSet straight through. With Next optimization ON, set `unoptimized` **per slot** for CDN-backed images to avoid paying for double optimization.
|
|
237
323
|
5. **Completeness sweep before declaring done**: `grep cdn-host` must be 0 in (a) rendered HTML of every route, (b) serialized/embedded JSON payloads (this is where a stale ref hides even when the *image* renders correctly), (c) JSON-LD / OG / preconnect, (d) any **DB mirror table** (an app DB's `assets`/asset tables are easy to miss), and (e) **cached API responses** (a CDN/data cache can persist a pre-cutover payload across deploys → bust the tag, don't assume redeploy clears it). "Images load from 8ok.uk" is NOT sufficient proof.
|
|
238
324
|
|
|
239
|
-
## 6. Provisioning a new tenant (ops
|
|
325
|
+
## 6. Provisioning a new tenant (ops)
|
|
240
326
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
# 2) Mint keys (prints runtime/admin/ci ONCE — store immediately):
|
|
250
|
-
cd apps/asset-manager
|
|
251
|
-
PLATFORM_DATABASE_URL=… ASSET_AUTH_SECRET=… bun run scripts/bootstrap-project.ts <tenant-code>
|
|
252
|
-
```
|
|
327
|
+
**Use the console: [`https://console.gofuture.space`](https://console.gofuture.space)** (sign in with
|
|
328
|
+
Google). *Tenants → New* creates the tenant rows, and the tenant's page **issues, shows and rotates**
|
|
329
|
+
its runtime / admin / CI keys. A key's secret is displayed **once**, at issue time — store it then.
|
|
330
|
+
|
|
331
|
+
That replaces `apps/asset-manager/scripts/bootstrap-project.ts` and the hand-written SQL that used to
|
|
332
|
+
live here. The point is not convenience: **provisioning no longer requires the production database
|
|
333
|
+
URL** (`PLATFORM_DATABASE_URL`) or `ASSET_AUTH_SECRET` on somebody's laptop. Reach for the script
|
|
334
|
+
only if the console is down, and treat that as an incident, not a workflow.
|
|
253
335
|
|
|
254
|
-
- Endpoint = the
|
|
255
|
-
|
|
336
|
+
- Endpoint = `https://api.nitida.gofuture.space` (the Cloud Run generated URL keeps working — see the
|
|
337
|
+
note at the top). Verify a fresh tenant: `GET /usage` with `Authorization: Bearer <amk_rt_*>` +
|
|
338
|
+
`X-Tenant-Code: <code>` → `{ "tenant": { "id": N } }`.
|
|
339
|
+
- The `@aquienpz/tenant-config` HTTP layer (still published under the old scope) **caches tenant
|
|
340
|
+
lookups for 60s** — a fresh tenant resolves in services within a minute.
|
|
256
341
|
- Asset-only consumers (a storefront with its own catalog DB) can skip the Qdrant/voice/scraper steps in `docs/TENANT_ONBOARDING.md` — only the two tenant rows + keys + `storefront_origins` are needed.
|
|
257
342
|
|
|
258
343
|
## Quick reference
|
|
@@ -297,11 +382,12 @@ Measured 2026-08-15 building `neo/apps/media-harness`, the bench for this SDK. R
|
|
|
297
382
|
| `process returned no assetId` | fixed 2026-08-16 — you are on an `asset-manager` older than that deploy, §7.2 |
|
|
298
383
|
| `waitReady timeout` on a video | a transcode + HLS ladder takes 1–2 min; the default `timeoutMs` is 5 min but a 4K source can beat it. Raise it, §7.2 |
|
|
299
384
|
| `R2 PUT failed: network error` | your origin is not in the R2 BUCKET's CORS policy, §7.1 |
|
|
300
|
-
| `
|
|
385
|
+
| `nt.assets.variants()` returns `[]` | fixed 2026-08-17 (#225) — you are on an `asset-manager` older than that deploy. Existence from `dto.presets` + `hasPreset` works on every version |
|
|
386
|
+
| `hasPreset` says a preset exists and its URL 404s | fixed 2026-08-17 (#228) — an old server sends multi-char tokens in `presets`; a current `@nitida/asset-client` strips them, a hand-rolled `includes` does not, §3b-bis |
|
|
301
387
|
| `tenant_not_found` from a service | tenant rows missing, or the 60s tenant-config cache is stale |
|
|
302
|
-
| `original` 404s | (a) never written — `presets` omitted `"original"` at ingest, or the asset is a VIDEO (always dropped, §7.3); (b) `getAssetUrl
|
|
388
|
+
| `original` 404s | (a) never written — `presets` omitted `"original"` at ingest, or the asset is a VIDEO (always dropped, §7.3); (b) `getAssetUrl` called with a bare `{sha}` → the `-o.bin` sentinel; (c) called with `{sha, mime}` against an old server, so it guessed the extension. Pass the whole DTO from `nt.assets.get(id)` — see §3b |
|
|
303
389
|
| "uploaded OK" but nothing to restore | `["thumb"]`-style presets store no original; verify by SHA-256 round-trip, not by upload count — see §3b |
|
|
304
|
-
| audio
|
|
390
|
+
| audio stored as `kind:"other"` | fixed 2026-08-17 (#230/#231) — the ext→mime round-trip lost `.mpga`, and untyped bytes are now sniffed. Against an older `asset-manager`, upload with an explicit `contentType:"audio/mpeg"`. Rows misclassified **before** the fix stay that way by decision, §3 |
|
|
305
391
|
|
|
306
392
|
> Source of truth for deeper detail: `docs/ASSET_MANAGER_V2.md`, `docs/CLIENT_OPERATIONS.md`, `docs/TENANT_ONBOARDING.md`, and `packages/asset-client/src/{transform.ts,index.ts}`.
|
|
307
393
|
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* import { NitidaClient } from "@nitida/sdk";
|
|
14
14
|
*
|
|
15
15
|
* const aq = new NitidaClient({
|
|
16
|
-
* endpoint: "https://
|
|
16
|
+
* endpoint: "https://api.nitida.gofuture.space",
|
|
17
17
|
* apiKey: process.env.ASSET_MANAGER_RUNTIME_KEY!,
|
|
18
18
|
* tenantCode: "realtyone-cr",
|
|
19
19
|
* cdnBase: "https://8ok.uk", // optional override
|
|
@@ -126,7 +126,7 @@ export type NitidaClientOptions = {
|
|
|
126
126
|
/**
|
|
127
127
|
* Build a fully-qualified URL for an aquienpz endpoint path.
|
|
128
128
|
*
|
|
129
|
-
* Accepts both absolute endpoints (`https://
|
|
129
|
+
* Accepts both absolute endpoints (`https://api.nitida.gofuture.space`) and
|
|
130
130
|
* relative ones (`/api/am`) — the latter only works in browser contexts
|
|
131
131
|
* (resolved against `window.location.origin`). Node/Bun throws a clear
|
|
132
132
|
* error if a relative endpoint is configured.
|
|
@@ -570,6 +570,12 @@ class AssetsApi {
|
|
|
570
570
|
*
|
|
571
571
|
* const v = await aq.assets.variants(logoId);
|
|
572
572
|
* v.map((x) => x.preset); // → ("thumb" | "sm" | … | "original")[]
|
|
573
|
+
*
|
|
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
|
|
576
|
+
* array is therefore "no variants OR old server". For a plain existence
|
|
577
|
+
* check prefer `hasPreset(dto, preset)` on `dto.presets`, which every server
|
|
578
|
+
* version sends; use this when you need the URLs and sizes.
|
|
573
579
|
*/
|
|
574
580
|
async variants(assetId: string): Promise<AssetVariant[]> {
|
|
575
581
|
const dto = await this.get(assetId);
|