@nitida/sdk 0.28.0 → 0.30.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 +1 -1
- package/LICENSE +21 -0
- package/README.md +16 -7
- package/SECURITY.md +56 -0
- package/dist/expo.d.ts +4 -4
- package/dist/expo.js +2 -2
- package/dist/expo.js.map +1 -1
- package/dist/index.d.ts +52 -2
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/server.js +18 -2
- package/dist/server.js.map +1 -1
- package/dist/web.d.ts +1 -1
- package/dist/web.js +18 -2
- package/dist/web.js.map +1 -1
- package/package.json +7 -5
- package/skills/nitida-sdk/SKILL.md +3 -2
- package/src/expo/index.ts +5 -5
- package/src/index.ts +87 -5
- package/src/web/index.ts +3 -3
package/src/expo/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @nitida/sdk/expo — native multipart uploads for React Native / Expo.
|
|
3
3
|
*
|
|
4
|
-
* Wraps `@
|
|
4
|
+
* Wraps `@nitida/asset-uploader-expo`'s `UploadTask`, which delegates
|
|
5
5
|
* the actual byte transfer to a native background session (URLSession
|
|
6
6
|
* on iOS, WorkManager on Android). The upload survives:
|
|
7
7
|
* - JS thread freezing
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
* is not built. It is the same gap `/web` documents. Ask; there is no public
|
|
54
54
|
* tracker.
|
|
55
55
|
*
|
|
56
|
-
* Peer dep: `@
|
|
56
|
+
* Peer dep: `@nitida/asset-uploader-expo` (lazy — apps that don't
|
|
57
57
|
* use the mobile SDK skip the install).
|
|
58
58
|
* @module @nitida/sdk/expo
|
|
59
59
|
*/
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
import {
|
|
62
62
|
UploadTask,
|
|
63
63
|
type UploadTaskOptions,
|
|
64
|
-
} from "@
|
|
64
|
+
} from "@nitida/asset-uploader-expo";
|
|
65
65
|
import type { NitidaClient } from "..";
|
|
66
66
|
|
|
67
67
|
export type ExpoUploadOptions = Omit<
|
|
@@ -97,9 +97,9 @@ export type {
|
|
|
97
97
|
UploadFileInput,
|
|
98
98
|
UploadSessionState,
|
|
99
99
|
UploadTaskOptions,
|
|
100
|
-
} from "@
|
|
100
|
+
} from "@nitida/asset-uploader-expo";
|
|
101
101
|
export {
|
|
102
102
|
cancelResumableSession,
|
|
103
103
|
listResumableSessions,
|
|
104
104
|
UploadTask,
|
|
105
|
-
} from "@
|
|
105
|
+
} from "@nitida/asset-uploader-expo";
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* platform.
|
|
4
4
|
*
|
|
5
5
|
* One ergonomic facade over the underlying packages
|
|
6
|
-
* (`@nitida/asset-client` URL builders + `@
|
|
6
|
+
* (`@nitida/asset-client` URL builders + `@nitida/asset-uploader-web`
|
|
7
7
|
* + the slot resolver). Auth is a bearer API key (`amk_rt_*`), issued
|
|
8
8
|
* per tenant when the tenant is created; tenant scope comes from
|
|
9
9
|
* the key's metadata (`X-Tenant-Code` is log-only).
|
|
@@ -820,7 +820,49 @@ class AssetsApi {
|
|
|
820
820
|
* Default timeout is 5 minutes; videos / HLS ladders may need a
|
|
821
821
|
* higher cap (pass `10 * 60_000` for compositions, transcodes).
|
|
822
822
|
*/
|
|
823
|
-
async waitReady(
|
|
823
|
+
async waitReady(
|
|
824
|
+
assetId: string,
|
|
825
|
+
/**
|
|
826
|
+
* Milliseconds, OR `{ timeoutMs }`.
|
|
827
|
+
*
|
|
828
|
+
* ⭐ It accepts the object because that is what people write. Found
|
|
829
|
+
* 2026-08-23 while smoke-testing a brand-new tenant: I passed
|
|
830
|
+
* `{ timeoutMs: 90_000 }` — every other option-taking method in this SDK
|
|
831
|
+
* takes an object — and got
|
|
832
|
+
*
|
|
833
|
+
* Error: waitReady: no asset a379fbbb-…
|
|
834
|
+
*
|
|
835
|
+
* about an asset that existed, was `ready`, and whose variants were all
|
|
836
|
+
* serving 200. `Date.now() - start < {…}` compares against NaN, so the
|
|
837
|
+
* loop body never ran, `last` stayed null, and the message blamed the one
|
|
838
|
+
* thing that was fine.
|
|
839
|
+
*
|
|
840
|
+
* TypeScript catches the wrong shape. Running it through `bun` does not —
|
|
841
|
+
* the same gap that let `upload()`'s `sha256` reach a URL builder wanting
|
|
842
|
+
* `sha` and produce `.../undefined.webp`. The lesson there was the same as
|
|
843
|
+
* here: a guard with a good message is still a mistake the user has to
|
|
844
|
+
* make first. Make the obvious call correct instead.
|
|
845
|
+
*/
|
|
846
|
+
timeout: number | { timeoutMs?: number } = 5 * 60_000,
|
|
847
|
+
): Promise<AssetDTO> {
|
|
848
|
+
// A string, a Date, anything else: refuse it. Falling back to the default
|
|
849
|
+
// would be the same silent-success this whole method just stopped doing —
|
|
850
|
+
// `waitReady(id, "90s")` would wait five minutes and the caller would
|
|
851
|
+
// never learn why.
|
|
852
|
+
const timeoutMs =
|
|
853
|
+
typeof timeout === "number"
|
|
854
|
+
? timeout
|
|
855
|
+
: timeout !== null && typeof timeout === "object"
|
|
856
|
+
? (timeout.timeoutMs ?? 5 * 60_000)
|
|
857
|
+
: Number.NaN;
|
|
858
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
859
|
+
throw new Error(
|
|
860
|
+
"waitReady: timeout must be a positive number of milliseconds or " +
|
|
861
|
+
`{ timeoutMs }, got ${JSON.stringify(timeout)}. A non-numeric ` +
|
|
862
|
+
"timeout makes the poll loop exit before its first iteration, which " +
|
|
863
|
+
`used to surface as "no asset <id>" about an asset that was fine.`,
|
|
864
|
+
);
|
|
865
|
+
}
|
|
824
866
|
const start = Date.now();
|
|
825
867
|
let delay = 500;
|
|
826
868
|
let last: AssetDTO | null = null;
|
|
@@ -830,7 +872,15 @@ class AssetsApi {
|
|
|
830
872
|
await new Promise((r) => setTimeout(r, delay));
|
|
831
873
|
delay = Math.min(delay * 1.5, 5_000);
|
|
832
874
|
}
|
|
833
|
-
|
|
875
|
+
// Unreachable now that a non-numeric timeout is refused above: any
|
|
876
|
+
// positive timeout runs the loop at least once, so `last` is set. Kept as
|
|
877
|
+
// a belt, with a message that no longer accuses the asset of not existing.
|
|
878
|
+
if (!last) {
|
|
879
|
+
throw new Error(
|
|
880
|
+
`waitReady: polled ${assetId} zero times in ${timeoutMs}ms — this is a ` +
|
|
881
|
+
"bug in the SDK, not a missing asset.",
|
|
882
|
+
);
|
|
883
|
+
}
|
|
834
884
|
throw new Error(`waitReady timeout for ${assetId}`);
|
|
835
885
|
}
|
|
836
886
|
|
|
@@ -1059,6 +1109,32 @@ export type UploadResult = {
|
|
|
1059
1109
|
* message about the wrong one.
|
|
1060
1110
|
*/
|
|
1061
1111
|
sha: string;
|
|
1112
|
+
/**
|
|
1113
|
+
* The MIME the upload was stored under.
|
|
1114
|
+
*
|
|
1115
|
+
* ⭐ It exists because it did not, and that cost the THIRD 404 of this exact
|
|
1116
|
+
* family. Found 2026-08-23 by an agent running the getting-started doc
|
|
1117
|
+
* verbatim: `getAssetUrl(up, "original")` built `<sha>-o.bin` and 404'd,
|
|
1118
|
+
* while the object served fine at `-o.jpg`.
|
|
1119
|
+
*
|
|
1120
|
+
* `original` is the one preset whose extension is not fixed — it is the
|
|
1121
|
+
* bytes you uploaded, so the extension comes from the MIME (`ORIGINAL_EXT_BY_MIME`),
|
|
1122
|
+
* and `PRESET_EXT.original` is only the `"bin"` fallback for when nothing
|
|
1123
|
+
* says otherwise. `UploadResult` said nothing, so every caller handing an
|
|
1124
|
+
* upload result straight to a URL builder got the fallback.
|
|
1125
|
+
*
|
|
1126
|
+
* The pattern is now three for three — `sha256` vs `sha`, and this:
|
|
1127
|
+
* **a result type that omits what the next call needs turns the obvious
|
|
1128
|
+
* call into a silent 404.** The fix is never a better error message.
|
|
1129
|
+
*/
|
|
1130
|
+
mime: string;
|
|
1131
|
+
/**
|
|
1132
|
+
* The extension the server actually stored the original under, when it said
|
|
1133
|
+
* so. Authoritative — it beats any client-side MIME table, because the
|
|
1134
|
+
* server keys the object off the uploaded filename for the cases no table
|
|
1135
|
+
* can close (`.mpga`, `.docx`, `.m4a` all arrive as octet-stream).
|
|
1136
|
+
*/
|
|
1137
|
+
oext?: string | null;
|
|
1062
1138
|
cdnUrl: string;
|
|
1063
1139
|
};
|
|
1064
1140
|
|
|
@@ -1545,10 +1621,10 @@ export class NitidaClient {
|
|
|
1545
1621
|
// was not enough: `bun build --compile` constant-folds simple
|
|
1546
1622
|
// strings and still eagerly bundled `./web.js`, which
|
|
1547
1623
|
// top-level-imports the browser-only peer deps
|
|
1548
|
-
// `@
|
|
1624
|
+
// `@nitida/asset-uploader-web` / `@nitida/asset-compressor-web`
|
|
1549
1625
|
// — neither installed on server consumers — crashing the
|
|
1550
1626
|
// single-binary on boot with `Cannot find module
|
|
1551
|
-
// '@
|
|
1627
|
+
// '@nitida/asset-uploader-web'`.
|
|
1552
1628
|
//
|
|
1553
1629
|
// The earlier `new URL("./web.js", import.meta.url)` + `await import(URL)`
|
|
1554
1630
|
// dance survived bun-compile but Turbopack still tracks the URL literal
|
|
@@ -1606,6 +1682,8 @@ export class NitidaClient {
|
|
|
1606
1682
|
assetId: existing.id,
|
|
1607
1683
|
sha256: sha,
|
|
1608
1684
|
sha: sha.slice(0, 16),
|
|
1685
|
+
mime: existing.mime ?? mime,
|
|
1686
|
+
oext: existing.oext ?? null,
|
|
1609
1687
|
cdnUrl: this.urlFor(existing, this.bestPresetForAsset(existing, mime)),
|
|
1610
1688
|
};
|
|
1611
1689
|
}
|
|
@@ -1632,6 +1710,8 @@ export class NitidaClient {
|
|
|
1632
1710
|
assetId: presign.asset.id,
|
|
1633
1711
|
sha256: sha,
|
|
1634
1712
|
sha: sha.slice(0, 16),
|
|
1713
|
+
mime: presign.asset.mime ?? mime,
|
|
1714
|
+
oext: presign.asset.oext ?? null,
|
|
1635
1715
|
cdnUrl: this.urlFor(presign.asset, this.defaultPresetForMime(mime)),
|
|
1636
1716
|
};
|
|
1637
1717
|
}
|
|
@@ -1683,6 +1763,8 @@ export class NitidaClient {
|
|
|
1683
1763
|
assetId,
|
|
1684
1764
|
sha256: sha,
|
|
1685
1765
|
sha: sha.slice(0, 16),
|
|
1766
|
+
mime: final.mime ?? mime,
|
|
1767
|
+
oext: final.oext ?? null,
|
|
1686
1768
|
cdnUrl: this.urlFor(final, this.bestPresetForAsset(final, mime)),
|
|
1687
1769
|
};
|
|
1688
1770
|
}
|
package/src/web/index.ts
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* `apiKey` and bundles cleanly only in Node/Bun/edge runtimes.
|
|
45
45
|
*
|
|
46
46
|
* Peer deps (auto-installed via npm peer deps — declared optional):
|
|
47
|
-
* - `@
|
|
47
|
+
* - `@nitida/asset-uploader-web` for multipart uploads
|
|
48
48
|
* - `@nitida/asset-compressor-web` for client-side compression
|
|
49
49
|
*
|
|
50
50
|
* Both are lazy-imported; bundles that never call into them skip the cost.
|
|
@@ -197,7 +197,7 @@ export {
|
|
|
197
197
|
|
|
198
198
|
// Multipart uploader helpers are NOT re-exported from this subpath.
|
|
199
199
|
//
|
|
200
|
-
// `@
|
|
200
|
+
// `@nitida/asset-uploader-web` is an optional peer-dep that isn't on
|
|
201
201
|
// npm yet, so a static `import` at module top broke every consumer of
|
|
202
202
|
// `@nitida/sdk/web` (the bundler tries to resolve before the optional
|
|
203
203
|
// peer check kicks in). The old `createWebUploader` + `UploadTask`
|
|
@@ -209,7 +209,7 @@ export {
|
|
|
209
209
|
// token is not exposed yet. Ask us; there is no public tracker.
|
|
210
210
|
//
|
|
211
211
|
// Apps that need `UploadTask` directly today should depend on
|
|
212
|
-
// `@
|
|
212
|
+
// `@nitida/asset-uploader-web` themselves once it's published.
|
|
213
213
|
|
|
214
214
|
// ---------------------------------------------------------------------------
|
|
215
215
|
// Client-side compression (Phase 1.5)
|