@gj-kit/expo-media core concepts
An upload with no size limit, or an iCloud download nobody asked for, is a compile error.
Why this boundary exists
Section titled “Why this boundary exists”Expo media failures rarely look like failures. On iOS 26 FileSystem.uploadAsync ends the process while it is starting an upload, so no promise ever rejects and no retry fires; the localUri MediaLibrary hands back points inside the photo library rather than your app container, so it passes stat and then kills URLSession mid-upload. Android still reports the original fileSize after a quality<1 re-encode, so the presigned size and the bytes storage actually receives disagree.
A selected asset is not always upload-safe
Section titled “A selected asset is not always upload-safe”Device-library URIs may not outlive the uploader. The adapter boundary makes durable staging and cleanup an explicit application decision.
Upload recovery names the uncertain state
Section titled “Upload recovery names the uncertain state”A network failure can mean uploaded, possibly-uploaded, or never reached storage. Public recovery metadata preserves that distinction without leaking URLs.
What the package enforces
Section titled “What the package enforces”- Unlimited uploads must be spelled out —
createMediaKit({ api })does not compile:limitsis required, andNumber.POSITIVE_INFINITYis rejected, so unlimited is written'server-enforced'. - duplicate cannot be forgotten —
UploadResult.duplicateis required rather than optional, because a missing flag reads as “newly created” and the cancel path then deletes the user’s older photo. - Whoever copies owns the cleanup —
createDeviceLibrarywill not compile withoutstaging, so the factory that materializes cache copies of device photos always carries theStagingCache.cleanupthat deletes them. - iCloud downloads never default on —
adapter.getAssetInfo('id')is a compile error: the second argument is required and carriesdownloadFromNetwork, so the caller decides on every call and no adapter can quietly inherit the legacy default oftruethat started cellular transfers. - Peer isolation is a CI assertion — The
dist-peer-graphguard re-extracts each entry’s external specifiers from the built output across browser/node/native by ESM/CJS;./core,./image/pure,./weband./testingresolve zero peers.
What remains application-owned
Section titled “What remains application-owned”Never expose presigned URLs or native URI details in public errors. Keep cleanup authorization and attachment transactions in the consuming application.
Continue with the two verified recipes.