@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/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 `@aquienpz/asset-uploader-expo`'s `UploadTask`, which delegates
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: `@aquienpz/asset-uploader-expo` (lazy — apps that don't
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 "@aquienpz/asset-uploader-expo";
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 "@aquienpz/asset-uploader-expo";
100
+ } from "@nitida/asset-uploader-expo";
101
101
  export {
102
102
  cancelResumableSession,
103
103
  listResumableSessions,
104
104
  UploadTask,
105
- } from "@aquienpz/asset-uploader-expo";
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 + `@aquienpz/asset-uploader-web`
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(assetId: string, timeoutMs = 5 * 60_000): Promise<AssetDTO> {
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
- if (!last) throw new Error(`waitReady: no asset ${assetId}`);
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
- // `@aquienpz/asset-uploader-web` / `@nitida/asset-compressor-web`
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
- // '@aquienpz/asset-uploader-web'`.
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
- * - `@aquienpz/asset-uploader-web` for multipart uploads
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
- // `@aquienpz/asset-uploader-web` is an optional peer-dep that isn't on
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
- // `@aquienpz/asset-uploader-web` themselves once it's published.
212
+ // `@nitida/asset-uploader-web` themselves once it's published.
213
213
 
214
214
  // ---------------------------------------------------------------------------
215
215
  // Client-side compression (Phase 1.5)