@nitida/sdk 0.25.3 → 0.26.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/AGENTS.md +34 -0
- package/README.md +148 -15
- package/dist/expo.d.ts +32 -3
- package/dist/expo.js.map +1 -1
- package/dist/index.d.ts +12 -5
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +23 -0
- package/dist/server.js.map +1 -1
- package/dist/web.d.ts +2 -2
- package/dist/web.js +25 -0
- package/dist/web.js.map +1 -1
- package/package.json +3 -7
- package/skills/nitida-sdk/SKILL.md +31 -4
- package/src/expo/index.ts +32 -3
- package/src/index.ts +24 -5
- package/src/server/index.ts +45 -1
- package/src/web/index.ts +57 -3
package/AGENTS.md
CHANGED
|
@@ -24,6 +24,40 @@ import { getTransformUrl, setCdnBase, setTenantId } from "@nitida/asset-client";
|
|
|
24
24
|
- **Video URLs** → `getAssetUrl({ sha }, "video")` after `setTenantId(id)`. The path segment is
|
|
25
25
|
**base36** (`tenant 12 → /c/v/…`); a decimal prefix 404s.
|
|
26
26
|
|
|
27
|
+
## Which entry point exports what — read this before your first import
|
|
28
|
+
|
|
29
|
+
**`@nitida/sdk`, `@nitida/sdk/server` and `@nitida/sdk/web` all carry the same
|
|
30
|
+
surface.** Pick the one that matches your runtime and import everything from it:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { NitidaClient, getTransformUrl, getHlsLadder } from "@nitida/sdk/server";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The other three subpaths are **additive** — they carry only their own
|
|
37
|
+
runtime-specific symbols and you import a complete one alongside:
|
|
38
|
+
|
|
39
|
+
| subpath | carries | |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `/react` | `NitidaProvider`, `useSlot`, `useSlots`, `useNitidaClient` | + `/web` or `/server` |
|
|
42
|
+
| `/native` | `compressImage`, `compressImages` for React Native | + `/web` |
|
|
43
|
+
| `/expo` | `createExpoUploader`, `listResumableSessions`, `cancelResumableSession` | + `/web` |
|
|
44
|
+
|
|
45
|
+
⚠️ **React Native needs two imports.** `/web` for the client and every URL
|
|
46
|
+
builder — it has no top-level browser imports, its compressor is a dynamic
|
|
47
|
+
`import()` — plus `/native` and/or `/expo`. `/native` on its own gives you a
|
|
48
|
+
compressor and no way to build a URL.
|
|
49
|
+
|
|
50
|
+
The single deliberate omission: **`NitidaClientOptions` is not on `/web`**, because
|
|
51
|
+
it carries `apiKey` and this subpath exists so that shape is unreachable from
|
|
52
|
+
browser code. Use `WebClientOptions`.
|
|
53
|
+
|
|
54
|
+
⚠️ **This was not true before 2026-08-21.** `/server` was short **28** of the
|
|
55
|
+
root's 72 exports and `/web` short **37** — the whole palette family, every slot
|
|
56
|
+
helper, the HLS ladder helpers, the preset constants, and on `/web` even
|
|
57
|
+
`setTenantId`. Importing `getHlsLadder` from `/server` failed at **runtime**, not
|
|
58
|
+
at compile time. It is now asserted in CI, so if you are reading an older
|
|
59
|
+
version of this file, verify against the `.d.ts` you actually installed.
|
|
60
|
+
|
|
27
61
|
## The four things that bite hardest
|
|
28
62
|
|
|
29
63
|
1. **`presets` decides what exists forever.** Omit it and you get `original` only. Ask for
|
package/README.md
CHANGED
|
@@ -15,14 +15,45 @@ SDK**. Stripe/Cloudinary's split into two separate npm packages is the legacy
|
|
|
15
15
|
shape — modern bundlers tree-shake subpaths perfectly and a single version
|
|
16
16
|
eliminates type drift between server and client surfaces.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
### Two kinds of subpath, and the difference matters
|
|
19
|
+
|
|
20
|
+
**COMPLETE entry points** carry the whole surface. Import one and you are done.
|
|
21
|
+
|
|
22
|
+
| Where you run | Import | |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Node 20+, Bun, Cloud Run, Lambda, Edge, agents, cron | **`@nitida/sdk/server`** | `NitidaClient` + **every** export of the root. **No browser-only code.** |
|
|
25
|
+
| Browser, and React Native | **`@nitida/sdk/web`** | everything in `/server`, plus `compressImage` (HEIC→WebP). The one deliberate omission is `NitidaClientOptions`, the options type that carries `apiKey` — use `WebClientOptions`. |
|
|
26
|
+
| Anywhere (the root) | `@nitida/sdk` | the full surface, runtime-agnostic. Prefer an explicit subpath in new code. |
|
|
27
|
+
|
|
28
|
+
**ADDITIVE modules** are layered *on top* of one of the above. They carry only
|
|
29
|
+
their own runtime-specific symbols — **you import a complete entry point as
|
|
30
|
+
well**:
|
|
31
|
+
|
|
32
|
+
| For | Import | Carries |
|
|
19
33
|
|---|---|---|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
| React hooks | `@nitida/sdk/react` | `NitidaProvider`, `useSlot`, `useSlots`, `useNitidaClient` — and nothing else |
|
|
35
|
+
| RN image compression | `@nitida/sdk/native` | `compressImage` / `compressImages` for RN (`react-native-compressor`) |
|
|
36
|
+
| Expo background uploads | `@nitida/sdk/expo` | `createExpoUploader` — `URLSession` / `WorkManager` sessions that survive backgrounding. ⚠️ **Needs a runtime key on the device** — see below |
|
|
37
|
+
|
|
38
|
+
⚠️ **`/expo` is the one place a runtime key lives on the device.**
|
|
39
|
+
`createExpoUploader` takes `apiKey` off the client and gives it to the native
|
|
40
|
+
session as its `authToken`, because the OS replays that upload from a background
|
|
41
|
+
task hours later and there is no BFF in that loop. It is the opposite call from
|
|
42
|
+
`@nitida/sdk/web`, which hit the same constraint and chose *not* to expose its
|
|
43
|
+
multipart uploader at all. An `amk_rt_*` in an IPA/APK is readable by anyone who
|
|
44
|
+
unzips it, and it is a write key to a paid platform. If background survival is
|
|
45
|
+
not worth that, build the client from `/web` against your own route and use
|
|
46
|
+
`aq.upload(file)` — no key on the device, and the upload dies with the JS thread.
|
|
47
|
+
|
|
48
|
+
⚠️ **React Native needs two imports**, and this table used to imply otherwise:
|
|
49
|
+
`@nitida/sdk/web` for the client and every URL builder — it has no top-level
|
|
50
|
+
browser imports, the compressor behind it is a dynamic `import()` — **plus**
|
|
51
|
+
`/native` and/or `/expo` for the native pieces. `/native` alone gives you a
|
|
52
|
+
compressor and no way to build a URL.
|
|
53
|
+
|
|
54
|
+
That `/server` and `/web` really do mirror the root is **asserted in CI**, not
|
|
55
|
+
maintained by hand. It was not always true: before 2026-08-21 `/server` was
|
|
56
|
+
short 28 of the root's 72 exports and `/web` short 37.
|
|
26
57
|
|
|
27
58
|
Uploaders + compressors are **optional** peer deps (`@aquienpz/asset-uploader-{web,expo}`,
|
|
28
59
|
`@nitida/asset-compressor-{web,native}`). Skip them if your app only resolves
|
|
@@ -32,8 +63,9 @@ slots and reads assets — your bundle stays a few KB.
|
|
|
32
63
|
|
|
33
64
|
- Server-side rendering, API routes, BFF, cron, agents → `@nitida/sdk/server`.
|
|
34
65
|
- Browser components, build-time pre-resolution → `@nitida/sdk/web`.
|
|
35
|
-
- Expo / React Native app → `@nitida/sdk/
|
|
36
|
-
|
|
66
|
+
- Expo / React Native app → `@nitida/sdk/web` (the client and the URL builders)
|
|
67
|
+
**plus** `@nitida/sdk/native` and/or `@nitida/sdk/expo` for the native pieces.
|
|
68
|
+
- React hooks (any env) → `@nitida/sdk/react` **plus** `/web` or `/server`.
|
|
37
69
|
|
|
38
70
|
**Never ship the API key to the browser.** Whichever subpath you import,
|
|
39
71
|
the long-lived `amk_rt_*` key lives on the server. Browser flows hit a BFF
|
|
@@ -45,8 +77,44 @@ route that proxies to aquienpz with the real key.
|
|
|
45
77
|
|---|---|---|
|
|
46
78
|
| Browser (Next.js client component, SPA, Worker, web extension) | `@nitida/sdk/web` | **No `apiKey`** — your BFF injects it. Type omits the field; build error if you try. |
|
|
47
79
|
| Node/Bun server (API route, Server Action, Cloud Run, Fly, BFF) | `@nitida/sdk/server` | Requires `apiKey` at construction. |
|
|
48
|
-
| React Native / Expo | `@nitida/sdk/native` or
|
|
49
|
-
| Universal React hook | `@nitida/sdk/react` |
|
|
80
|
+
| React Native / Expo | `@nitida/sdk/web` **plus** `/native` and/or `/expo` | Same BFF model as `/web` — keep keys server-side. `/native` and `/expo` are additive; on their own they carry no client. |
|
|
81
|
+
| Universal React hook | `@nitida/sdk/react` **plus** `/web` or `/server` | The hooks read a client from context; something has to construct it. |
|
|
82
|
+
|
|
83
|
+
### …and where does each HELPER live?
|
|
84
|
+
|
|
85
|
+
The table above answers *"where do I get the client"*. It did **not** used to
|
|
86
|
+
answer *"where do I get `getHlsLadder`"*, and that is the question that cost the
|
|
87
|
+
most time — an agent evaluating this SDK followed `AGENTS.md` to `/server`,
|
|
88
|
+
imported `getHlsLadder` from there, and got a `SyntaxError` at runtime.
|
|
89
|
+
|
|
90
|
+
**Now there is a one-line answer, and CI keeps it true:**
|
|
91
|
+
|
|
92
|
+
> **`@nitida/sdk`, `/server` and `/web` all carry the same surface.**
|
|
93
|
+
> Import any helper from whichever of the three you already use.
|
|
94
|
+
|
|
95
|
+
| what you want | `@nitida/asset-client` | `@nitida/sdk` · `/server` · `/web` | elsewhere |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| URL builders — `getAssetUrl`, `getTransformUrl`, `getVideoTransformUrl`, `getAssetSrcSet`, `getTransformSrcSet`, `getHlsStreamingUrl`, `serializeTransform` | ✅ | ✅ | |
|
|
98
|
+
| Signing — `signTransformUrl`, `getSignedTransformUrl` | ✅ | ✅ | |
|
|
99
|
+
| Asset facts — `hasPreset`, `extractAssetSha`, `getAssetDimensions`, `computeVariantDimensions` | ✅ | ✅ | |
|
|
100
|
+
| HLS — `getHlsLadder`, `hlsLadderAlignment`, `HlsRung` | ✅ | ✅ | |
|
|
101
|
+
| Palette — `pickAmbientBackground`, `iteratePaletteSwatches`, `bestTextContrast`, `getAmbientGradient`, `getPaletteCssVars`, `getPaletteBlurBackground`, `getTextColorForBackground`, `contrastRatio`, `relativeLuminance` | ✅ | ✅ | |
|
|
102
|
+
| Slots — `resolveSlot`, `resolveSlots`, `configureSlotResolver`, `invalidateSlotCache` | ✅ | ✅ | |
|
|
103
|
+
| Constants — `TRANSFORM_WIDTHS`, `TransformWidth`, `PRESET_EXT`, `PRESET_LONG`, `PRESET_SHORT`, `PRESET_MAX_DIM` | ✅ | ✅ | |
|
|
104
|
+
| Config — `setCdnBase`/`getCdnBase`, `setTenantId`/`getTenantId` | ✅ | ✅ | |
|
|
105
|
+
| `NitidaClient` and its option/result types | | ✅ | |
|
|
106
|
+
| Client-side compression — `compressImage`, `compressImages` | | `/web` only | `/native` (React Native) |
|
|
107
|
+
| React hooks — `NitidaProvider`, `useNitidaClient`, `useSlot`, `useSlots` | | | `/react` only |
|
|
108
|
+
| Expo resumable upload — `createExpoUploader`, `listResumableSessions`, `cancelResumableSession` | | | `/expo` only |
|
|
109
|
+
|
|
110
|
+
The single deliberate exception: **`NitidaClientOptions` is not on `/web`.** That
|
|
111
|
+
is the options type that carries `apiKey`, and this subpath exists so the
|
|
112
|
+
apiKey-bearing shape is unreachable from browser code — use `WebClientOptions`.
|
|
113
|
+
|
|
114
|
+
This is not maintained by hand. `scripts/check-published-doc-symbols.ts` asserts
|
|
115
|
+
that `/server` and `/web` mirror the root, and that the one denial above still
|
|
116
|
+
has a written reason. Before 2026-08-21 it was not true: `/server` was short
|
|
117
|
+
**28** of the root's 72 and `/web` short **37**, `getHlsLadder` among them.
|
|
50
118
|
|
|
51
119
|
### Why subpaths, not a runtime flag
|
|
52
120
|
|
|
@@ -543,6 +611,39 @@ The preset is a **ceiling**, not a target.
|
|
|
543
611
|
| `video` | `v` | original MP4 |
|
|
544
612
|
| `aiproxy` | `a` | low-res proxy for AI captioning / search (opt-in) |
|
|
545
613
|
|
|
614
|
+
### ⚠️ What you can ASK FOR is not what a variant can BE
|
|
615
|
+
|
|
616
|
+
Two different sets, two different types, and confusing them is the most
|
|
617
|
+
expensive type error this package has shipped — three unknown agents evaluating
|
|
618
|
+
the SDK hit it independently in one afternoon.
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
aq.assets.regenerate(id, { presets: ["hls"] }); // ← used to compile. HTTP 400.
|
|
622
|
+
aq.upload(file, { presets: ["mp3"] }); // ← used to compile. HTTP 400.
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Both symbols are real. Neither is orderable.
|
|
626
|
+
|
|
627
|
+
| | `RequestablePreset` — you may ask | `VariantPreset` — a variant may be |
|
|
628
|
+
|---|---|---|
|
|
629
|
+
| `thumb` `sm` `md` `lg` `xl` `original` `poster` `video` `aiproxy` | ✅ | ✅ |
|
|
630
|
+
| `hls` — the adaptive ladder, built when a video transcodes | ❌ | ✅ |
|
|
631
|
+
| `mp3` — emitted automatically alongside any audio original | ❌ | ✅ |
|
|
632
|
+
| `probe` — indexed stills (`-pr0.jpg`), never on the compact `presets` string | ✅ | ❌ |
|
|
633
|
+
|
|
634
|
+
The write methods (`upload`, `presignUploadUrl`, `regenerate`) take
|
|
635
|
+
`RequestablePreset[]`, so all three lines above are now **compile errors**. The
|
|
636
|
+
read helpers (`hasPreset`, `variants[].preset`, `PRESET_SHORT`) keep
|
|
637
|
+
`VariantPreset`, because `hls` and `mp3` genuinely do exist on assets.
|
|
638
|
+
|
|
639
|
+
`probe` was the same bug mirrored: the server has always accepted it and the
|
|
640
|
+
type forbade it. It is requestable now.
|
|
641
|
+
|
|
642
|
+
This is checked in CI against the server itself —
|
|
643
|
+
`scripts/check-preset-contract.ts` reads the Elysia schemas of all four write
|
|
644
|
+
routes and asserts `RequestablePreset` still equals what they accept, so it
|
|
645
|
+
cannot go stale the way a hand-written list would.
|
|
646
|
+
|
|
546
647
|
### Per-upload preset selection
|
|
547
648
|
|
|
548
649
|
**The SDK's default is `["original"]`** — calling `aq.upload(file)`
|
|
@@ -679,7 +780,23 @@ returned shape gives you `{ preset, url, width?, height?, bytes }[]`.
|
|
|
679
780
|
|
|
680
781
|
Pre-generated presets (`thumb`/`sm`/`md`/`lg`/`xl`) cover the common cases.
|
|
681
782
|
For everything else — exact CSS pixel widths, art-directed crops, devicePixelRatio
|
|
682
|
-
ladders, square thumbs from rectangular sources —
|
|
783
|
+
ladders, square thumbs from rectangular sources — build a `/t/` URL.
|
|
784
|
+
|
|
785
|
+
### `aq.transform()` or `getTransformUrl()`? Both, and the rule is which you have
|
|
786
|
+
|
|
787
|
+
Two agents evaluating this SDK picked different ones for the same task and both
|
|
788
|
+
were right, which is a documentation failure rather than a design one. They are
|
|
789
|
+
**not duplicates**: `aq.transform()` is `getTransformUrl()` plus the client's
|
|
790
|
+
context.
|
|
791
|
+
|
|
792
|
+
| you have | use | why |
|
|
793
|
+
|---|---|---|
|
|
794
|
+
| a client (`aq`) | **`aq.transform(asset, opts)`** | it already knows your CDN base and tenant, and it is the only one that can **sign** — `aq.transform(asset, opts, { sign: true })` returns a `?sig=` URL, which is the only way to an off-ladder width |
|
|
795
|
+
| only a DTO — a component, a Server Component, a worker | **`getTransformUrl(asset, opts)`** | no client needed. Call `setCdnBase()` / `setTenantId()` once at module load first |
|
|
796
|
+
|
|
797
|
+
Same builder underneath, same URL out. If you are holding a client, use its
|
|
798
|
+
method — reaching for the standalone one there means passing the tenant twice
|
|
799
|
+
and losing signing.
|
|
683
800
|
|
|
684
801
|
```tsx
|
|
685
802
|
import { NitidaClient } from "@nitida/sdk";
|
|
@@ -809,16 +926,32 @@ hex-encoded. The server canonicalizes the URL the same way the SDK does
|
|
|
809
926
|
(sort keys, lowercase strings), so two URLs with the same params in
|
|
810
927
|
different order accept the same signature.
|
|
811
928
|
|
|
812
|
-
|
|
929
|
+
### ⚠️ Admin operations need the SYSTEM key, not the admin key in your triplet
|
|
930
|
+
|
|
931
|
+
Both are `amk_ad_*`, and that is the whole trap. The `admin` key issued with
|
|
932
|
+
your tenant is **tenant-scoped**: it manages your assets and cannot touch
|
|
933
|
+
`/admin/projects/*`. Verified 2026-08-21:
|
|
934
|
+
|
|
935
|
+
```
|
|
936
|
+
tenant admin key → GET /admin/projects → 403 SYSTEM_KEY_REQUIRED
|
|
937
|
+
"API key … is tenant-scoped; this endpoint requires a
|
|
938
|
+
system-scope key"
|
|
939
|
+
system admin key → GET /admin/projects → 200
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
There is exactly one system-scope key and we hold it. **These calls are ours to
|
|
943
|
+
run, not yours** — ask, and we run them. They are documented here so you know
|
|
944
|
+
what exists and can name what you want done, not so you can copy the `amk_ad_`
|
|
945
|
+
out of your own triplet and get a 403 that reads like a broken credential.
|
|
813
946
|
|
|
814
947
|
```bash
|
|
815
948
|
# Rotate the signing key — invalidates every URL signed with the old one.
|
|
816
|
-
curl -X POST -H "Authorization: Bearer amk_ad_
|
|
949
|
+
curl -X POST -H "Authorization: Bearer <SYSTEM amk_ad_...>" \
|
|
817
950
|
https://api.nitida.gofuture.space/admin/projects/your-tenant/rotate-signing-key
|
|
818
951
|
# → { ok: true, tenantId, code, signingKey: "<64-hex>" }
|
|
819
952
|
|
|
820
953
|
# Flip strict mode on/off.
|
|
821
|
-
curl -X PATCH -H "Authorization: Bearer amk_ad_
|
|
954
|
+
curl -X PATCH -H "Authorization: Bearer <SYSTEM amk_ad_...>" -H "Content-Type: application/json" \
|
|
822
955
|
-d '{"enabled":true}' \
|
|
823
956
|
https://api.nitida.gofuture.space/admin/projects/your-tenant/strict-transforms
|
|
824
957
|
```
|
package/dist/expo.d.ts
CHANGED
|
@@ -31,6 +31,33 @@ import '@nitida/asset-client';
|
|
|
31
31
|
* const { assetId } = await upload.start();
|
|
32
32
|
* await aq.slots.bind("storefront.tour.video", { assetId, preset: "video" });
|
|
33
33
|
*
|
|
34
|
+
* ⚠️ THIS PATH PUTS A RUNTIME KEY ON THE DEVICE. Read before shipping.
|
|
35
|
+
*
|
|
36
|
+
* `createExpoUploader` reads `client.opts.apiKey` and hands it to the native
|
|
37
|
+
* session as its `authToken` (see below — it is four lines, and they are the
|
|
38
|
+
* whole story). The native uploader talks to the API directly, so it needs a
|
|
39
|
+
* credential the OS can replay hours later from a background task. There is no
|
|
40
|
+
* BFF in that loop to inject one.
|
|
41
|
+
*
|
|
42
|
+
* That is the opposite of what every other surface here does, and the opposite
|
|
43
|
+
* of what `@nitida/sdk/web` chose when it hit the SAME constraint: `/web`
|
|
44
|
+
* deliberately does NOT expose the multipart uploader, because it needs a raw
|
|
45
|
+
* `authToken` a BFF cannot supply. `/expo` exposes it anyway, because
|
|
46
|
+
* background-surviving uploads are the entire reason a native session exists.
|
|
47
|
+
*
|
|
48
|
+
* So, concretely, an `amk_rt_*` key in your app bundle is readable by anyone
|
|
49
|
+
* who unzips the IPA/APK, and it is a WRITE key to a paid platform.
|
|
50
|
+
*
|
|
51
|
+
* - Uploading big video in the background is worth it to you ⇒ use this, and
|
|
52
|
+
* scope the key to one tenant so a leak is contained and revocable.
|
|
53
|
+
* - It is not ⇒ build the client from `@nitida/sdk/web` pointed at your own
|
|
54
|
+
* route and call `aq.upload(file)`. No key on the device. You lose survival
|
|
55
|
+
* across backgrounding and OS kill; the upload dies with the JS thread.
|
|
56
|
+
*
|
|
57
|
+
* The real fix — a BFF-minted short-lived token the native session can carry —
|
|
58
|
+
* is not built. It is the same gap `/web` documents. Ask; there is no public
|
|
59
|
+
* tracker.
|
|
60
|
+
*
|
|
34
61
|
* Peer dep: `@aquienpz/asset-uploader-expo` (lazy — apps that don't
|
|
35
62
|
* use the mobile SDK skip the install).
|
|
36
63
|
* @module @nitida/sdk/expo
|
|
@@ -39,9 +66,11 @@ import '@nitida/asset-client';
|
|
|
39
66
|
type ExpoUploadOptions = Omit<UploadTaskOptions, "tenantCode" | "endpoint" | "authToken">;
|
|
40
67
|
/**
|
|
41
68
|
* Spawn a native-backed `UploadTask` bound to a configured client.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
69
|
+
*
|
|
70
|
+
* ⚠️ It reaches into the client for `apiKey` and uses it as the session's
|
|
71
|
+
* `authToken`, so the client you pass MUST have been built with a real runtime
|
|
72
|
+
* key — which means that key is on the device. See the module header for what
|
|
73
|
+
* that costs and what the alternative is.
|
|
45
74
|
*/
|
|
46
75
|
declare function createExpoUploader(client: NitidaClient, options: ExpoUploadOptions): UploadTask;
|
|
47
76
|
|
package/dist/expo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/expo/index.ts"],"sourcesContent":["/**\n * @nitida/sdk/expo — native multipart uploads for React Native / Expo.\n *\n * Wraps `@aquienpz/asset-uploader-expo`'s `UploadTask`, which delegates\n * the actual byte transfer to a native background session (URLSession\n * on iOS, WorkManager on Android). The upload survives:\n * - JS thread freezing\n * - App backgrounding\n * - OS-initiated kill (low-memory, user swiping away)\n * - Network blips (retries with backoff)\n *\n * Usage:\n *\n * import { createExpoUploader, listResumableSessions } from \"@nitida/sdk/expo\";\n *\n * const aq = new NitidaClient({ ... });\n *\n * // Resume any uploads from a prior app launch on boot.\n * const resumable = await listResumableSessions();\n * // ...show a banner offering to resume them\n *\n * const upload = createExpoUploader(aq, {\n * file: { uri: assetUri, mime: \"video/mp4\", name: \"tour.mp4\" },\n * });\n * upload.on(\"progress\", ({ ratio }) => setProgress(ratio));\n * const { assetId } = await upload.start();\n * await aq.slots.bind(\"storefront.tour.video\", { assetId, preset: \"video\" });\n *\n * Peer dep: `@aquienpz/asset-uploader-expo` (lazy — apps that don't\n * use the mobile SDK skip the install).\n * @module @nitida/sdk/expo\n */\n\nimport {\n UploadTask,\n type UploadTaskOptions,\n} from \"@aquienpz/asset-uploader-expo\";\nimport type { NitidaClient } from \"..\";\n\nexport type ExpoUploadOptions = Omit<\n UploadTaskOptions,\n \"tenantCode\" | \"endpoint\" | \"authToken\"\n>;\n\n/**\n * Spawn a native-backed `UploadTask` bound to a configured client.\n *
|
|
1
|
+
{"version":3,"sources":["../src/expo/index.ts"],"sourcesContent":["/**\n * @nitida/sdk/expo — native multipart uploads for React Native / Expo.\n *\n * Wraps `@aquienpz/asset-uploader-expo`'s `UploadTask`, which delegates\n * the actual byte transfer to a native background session (URLSession\n * on iOS, WorkManager on Android). The upload survives:\n * - JS thread freezing\n * - App backgrounding\n * - OS-initiated kill (low-memory, user swiping away)\n * - Network blips (retries with backoff)\n *\n * Usage:\n *\n * import { createExpoUploader, listResumableSessions } from \"@nitida/sdk/expo\";\n *\n * const aq = new NitidaClient({ ... });\n *\n * // Resume any uploads from a prior app launch on boot.\n * const resumable = await listResumableSessions();\n * // ...show a banner offering to resume them\n *\n * const upload = createExpoUploader(aq, {\n * file: { uri: assetUri, mime: \"video/mp4\", name: \"tour.mp4\" },\n * });\n * upload.on(\"progress\", ({ ratio }) => setProgress(ratio));\n * const { assetId } = await upload.start();\n * await aq.slots.bind(\"storefront.tour.video\", { assetId, preset: \"video\" });\n *\n * ⚠️ THIS PATH PUTS A RUNTIME KEY ON THE DEVICE. Read before shipping.\n *\n * `createExpoUploader` reads `client.opts.apiKey` and hands it to the native\n * session as its `authToken` (see below — it is four lines, and they are the\n * whole story). The native uploader talks to the API directly, so it needs a\n * credential the OS can replay hours later from a background task. There is no\n * BFF in that loop to inject one.\n *\n * That is the opposite of what every other surface here does, and the opposite\n * of what `@nitida/sdk/web` chose when it hit the SAME constraint: `/web`\n * deliberately does NOT expose the multipart uploader, because it needs a raw\n * `authToken` a BFF cannot supply. `/expo` exposes it anyway, because\n * background-surviving uploads are the entire reason a native session exists.\n *\n * So, concretely, an `amk_rt_*` key in your app bundle is readable by anyone\n * who unzips the IPA/APK, and it is a WRITE key to a paid platform.\n *\n * - Uploading big video in the background is worth it to you ⇒ use this, and\n * scope the key to one tenant so a leak is contained and revocable.\n * - It is not ⇒ build the client from `@nitida/sdk/web` pointed at your own\n * route and call `aq.upload(file)`. No key on the device. You lose survival\n * across backgrounding and OS kill; the upload dies with the JS thread.\n *\n * The real fix — a BFF-minted short-lived token the native session can carry —\n * is not built. It is the same gap `/web` documents. Ask; there is no public\n * tracker.\n *\n * Peer dep: `@aquienpz/asset-uploader-expo` (lazy — apps that don't\n * use the mobile SDK skip the install).\n * @module @nitida/sdk/expo\n */\n\nimport {\n UploadTask,\n type UploadTaskOptions,\n} from \"@aquienpz/asset-uploader-expo\";\nimport type { NitidaClient } from \"..\";\n\nexport type ExpoUploadOptions = Omit<\n UploadTaskOptions,\n \"tenantCode\" | \"endpoint\" | \"authToken\"\n>;\n\n/**\n * Spawn a native-backed `UploadTask` bound to a configured client.\n *\n * ⚠️ It reaches into the client for `apiKey` and uses it as the session's\n * `authToken`, so the client you pass MUST have been built with a real runtime\n * key — which means that key is on the device. See the module header for what\n * that costs and what the alternative is.\n */\nexport function createExpoUploader(\n client: NitidaClient,\n options: ExpoUploadOptions,\n): UploadTask {\n const access = client as unknown as {\n opts: { endpoint: string; apiKey: string; tenantCode: string };\n };\n return new UploadTask({\n ...options,\n endpoint: access.opts.endpoint,\n authToken: access.opts.apiKey,\n tenantCode: access.opts.tenantCode,\n });\n}\n\nexport type {\n UploadEvents,\n UploadFileInput,\n UploadSessionState,\n UploadTaskOptions,\n} from \"@aquienpz/asset-uploader-expo\";\nexport {\n cancelResumableSession,\n listResumableSessions,\n UploadTask,\n} from \"@aquienpz/asset-uploader-expo\";\n"],"mappings":";AA4DA;AAAA,EACE;AAAA,OAEK;AAqCP;AAAA,EACE;AAAA,EACA;AAAA,EACA,cAAAA;AAAA,OACK;AAzBA,SAAS,mBACd,QACA,SACY;AACZ,QAAM,SAAS;AAGf,SAAO,IAAI,WAAW;AAAA,IACpB,GAAG;AAAA,IACH,UAAU,OAAO,KAAK;AAAA,IACtB,WAAW,OAAO,KAAK;AAAA,IACvB,YAAY,OAAO,KAAK;AAAA,EAC1B,CAAC;AACH;","names":["UploadTask"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ResolveSlotOptions, SlotResolution, SlotDTO, VariantPreset, AssetDTO, AssetVariant, TransformOptions, SignedTransformOptions } from '@nitida/asset-client';
|
|
2
|
-
export { AssetDTO, AssetPalette, AssetVariant, HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, PaletteSwatch, ResolveSlotOptions, SignedTransformOptions, SlotDTO, SlotResolution, TRANSFORM_WIDTHS, TransformEffect, TransformFit, TransformFormat, TransformGravity, TransformOptions, TransformWidth, VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsLadder, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, hlsLadderAlignment, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl } from '@nitida/asset-client';
|
|
1
|
+
import { ResolveSlotOptions, SlotResolution, SlotDTO, VariantPreset, AssetDTO, AssetVariant, RequestablePreset, TransformOptions, SignedTransformOptions } from '@nitida/asset-client';
|
|
2
|
+
export { AssetDTO, AssetPalette, AssetVariant, HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, PaletteSwatch, RequestablePreset, ResolveSlotOptions, SignedTransformOptions, SlotDTO, SlotResolution, TRANSFORM_WIDTHS, TransformEffect, TransformFit, TransformFormat, TransformGravity, TransformOptions, TransformWidth, VariantEntryPreset, VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsLadder, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, hlsLadderAlignment, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl } from '@nitida/asset-client';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @nitida/sdk — universal client for the aquienpz multi-tenant asset
|
|
@@ -235,8 +235,11 @@ type PresignUploadUrlOptions = {
|
|
|
235
235
|
/**
|
|
236
236
|
* Variant ladder to generate after `/assets/process`. Defaults to
|
|
237
237
|
* `["original"]` server-side when omitted — same contract as `aq.upload`.
|
|
238
|
+
*
|
|
239
|
+
* {@link RequestablePreset}, not {@link VariantPreset}: `hls` and `mp3` are
|
|
240
|
+
* things a variant can BE, never things you can ask for, and asking is a 400.
|
|
238
241
|
*/
|
|
239
|
-
presets?:
|
|
242
|
+
presets?: RequestablePreset[];
|
|
240
243
|
/**
|
|
241
244
|
* Pre-compression size of the source (useful when the browser ran
|
|
242
245
|
* compressorjs / heic2any before computing `bytes`). Recorded
|
|
@@ -353,7 +356,7 @@ declare class AssetsApi {
|
|
|
353
356
|
* completion).
|
|
354
357
|
*/
|
|
355
358
|
regenerate(assetId: string, opts?: {
|
|
356
|
-
presets?:
|
|
359
|
+
presets?: RequestablePreset[];
|
|
357
360
|
}): Promise<RegenerateResult>;
|
|
358
361
|
/** Merge metadata into an asset (role / slot / description / tags). */
|
|
359
362
|
patchMetadata(assetId: string, metadata: Record<string, unknown>): Promise<{
|
|
@@ -505,8 +508,12 @@ type UploadOptions = {
|
|
|
505
508
|
* Idempotent: you can always add missing variants later via
|
|
506
509
|
* `aq.assets.regenerate(id, { presets: [...] })`. The platform
|
|
507
510
|
* stores the source so regeneration doesn't require re-uploading.
|
|
511
|
+
*
|
|
512
|
+
* {@link RequestablePreset}, not {@link VariantPreset}. `hls` and `mp3` are
|
|
513
|
+
* produced FOR you — the ladder when a video transcodes, the mp3 alongside
|
|
514
|
+
* any audio original — and asking for either is a 400.
|
|
508
515
|
*/
|
|
509
|
-
presets?:
|
|
516
|
+
presets?: RequestablePreset[];
|
|
510
517
|
/**
|
|
511
518
|
* Max time to wait for the asset to transition to `ready` (or `failed`)
|
|
512
519
|
* after dispatch. Default `5 * 60_000` (5 min). Bump higher for large
|