@gj-kit/expo-media
Expo 미디어 업로드의 위험한 기본값을 사고가 아니라 컴파일 에러로 만듭니다.
- npm
0.6.2 - 런타임 의존성 0
- @ts-expect-error 가드 80개
- 유닛 테스트 570개 이상, expo 모킹 없음
- MediaError 코드 17종, 닫힌 유니언
계속 학습하기
섹션 제목: “계속 학습하기”왜 필요한가
섹션 제목: “왜 필요한가”Expo 미디어 파이프라인의 사고는 대부분 실패처럼 보이지 않습니다. iOS 26에서 FileSystem.uploadAsync는 업로드를 시작하는 도중 프로세스를 그대로 종료시켜, promise가 reject될 기회도 재시도 로직이 발화할 기회도 없습니다. iOS MediaLibrary가 돌려주는 localUri는 앱 컨테이너가 아니라 사진 보관함 안을 가리키는 경우가 많아 stat은 성공하지만 업로더에 넘기는 순간 네이티브 URLSession을 종료시킵니다. Android는 quality<1 재인코딩 뒤에도 원본 fileSize를 보고해, presign 크기와 스토리지가 실제로 받은 바이트가 어긋납니다.
무엇으로 막는가
섹션 제목: “무엇으로 막는가”- 무제한 업로드는 명시해야 합니다 —
createMediaKit({ api })는 컴파일되지 않습니다.limits는 필수이고Number.POSITIVE_INFINITY탈출구도 없어, 무제한은'server-enforced'라고 적어야 합니다. - duplicate는 빠뜨릴 수 없습니다 —
UploadResult.duplicate는 옵셔널이 아니라 필수입니다. 값이 빠지면 “새로 생성”으로 오독되고, 그 뒤 중복 취소 경로가 사용자의 예전 사진을 지우기 때문입니다. - 사본을 만든 쪽이 정리까지 책임집니다 —
createDeviceLibrary는staging없이는 컴파일되지 않습니다. 기기 사진을 캐시로 실체화하는 팩토리가 그 사본을 지우는StagingCache.cleanup을 반드시 함께 갖습니다. - iCloud 다운로드는 기본으로 켜지지 않습니다 —
adapter.getAssetInfo('id')처럼 인자 하나로 부르면 컴파일 에러입니다.downloadFromNetwork를 담은 두 번째 인자가 필수라 매 호출에서 호출부가 결정하고, 무단 셀룰러 전송을 시작하던 레거시 기본값true를 adapter가 조용히 물려받을 수 없습니다. - peer 격리는 CI가 검증합니다 —
dist-peer-graph가드가 빌드 산출물에서 엔트리별 외부 지정자를 다시 뽑아 browser·node·네이티브 × ESM·CJS로 대조합니다../core·./image/pure·./web·./testing은 peer 0으로 확인됩니다.
Golden path
섹션 제목: “Golden path”완료 상태: 앱이 업로드 권한을 계속 소유하는 Expo 기반 미디어 kit을 만듭니다.
1. 설치
섹션 제목: “1. 설치”pnpm add @gj-kit/expo-media2. 앱이 소유할 경계를 정합니다
섹션 제목: “2. 앱이 소유할 경계를 정합니다”앱에서 upload intent와 완료를 담당하는 uploadApi를 구현하고, 파일 제한을 명시합니다.
3. 최소 연결부터 시작합니다
섹션 제목: “3. 최소 연결부터 시작합니다”먼저 아래 코드를 복사한 뒤, 위에서 언급한 앱 소유 값만 교체하세요.
import { createMediaKit, type MediaUploadApi } from '@gj-kit/expo-media';
type Asset = { readonly id: string };declare const uploadApi: MediaUploadApi<Asset>; // Your app owns auth and upload URLs.
export const media = createMediaKit({ api: uploadApi, limits: { image: { maxBytes: 15 * 1024 * 1024 } },});실제로는 이렇게 걸립니다
섹션 제목: “실제로는 이렇게 걸립니다”업로드가 중간에 죽으면 오류가 URL 없는 복구 메타데이터로 좁혀집니다. stage와 orphanedObjects(objectName·contentType·sizeBytes·storageState)만 담기고, presigned URL이나 네이티브 예외 원문은 들어가지 않습니다.
import { createMediaKit, mediaUploadFailureInfo } from '@gj-kit/expo-media';import type { MediaUploadApi, MediaUploadFailureInfo } from '@gj-kit/expo-media';
declare const uploadApi: MediaUploadApi<{ readonly id: string }>; // App owns auth + upload URLs.declare function reconcile(failure: MediaUploadFailureInfo): Promise<void>; // App owns cleanup.
// `limits` is required, and there is no numeric escape hatch:// createMediaKit({ api: uploadApi });// -> error TS2345: Argument of type '{ api: MediaUploadApi<...>; }' is not// assignable to parameter of type 'MediaKitConfig<...>'.export const media = createMediaKit({ api: uploadApi, limits: 'server-enforced' });
export async function recover(error: unknown): Promise<void> { const failure = mediaUploadFailureInfo(error); if (!failure) throw error; // Not an upload failure — never swallow it. // failure.stage : 'intent' | 'put' | 'complete' // failure.orphanedObjects : readonly { objectName; contentType; sizeBytes; // storageState: 'uploaded' | 'possibly-uploaded' }[] // No presigned URL and no native error text ever reach this value. await reconcile(failure); throw error;}사용할 때
섹션 제목: “사용할 때”앱이 API와 저장소 정책을 직접 소유하면서 미디어 선택, 업로드 준비, 해싱, 기기 라이브러리, 지속 로컬 파일을 다룰 때 사용합니다.
사용하지 않을 때
섹션 제목: “사용하지 않을 때”레코드 소유권, presign 권한, orphan 정리 정책을 이 라이브러리에 넣지 마세요.
환경과 peer
섹션 제목: “환경과 peer”엔진: node >=20
| Peer | 지원 범위 | 필요한 위치 |
|---|---|---|
expo |
>=56.0.0 <58.0.0 |
문서화된 optional subpath에서만 |
expo-file-system |
>=56.0.0 |
문서화된 optional subpath에서만 |
expo-image-manipulator |
>=56.0.0 |
문서화된 optional subpath에서만 |
expo-image-picker |
>=16.0.0 |
문서화된 optional subpath에서만 |
expo-media-library |
>=56.0.5 |
문서화된 optional subpath에서만 |
expo-video-thumbnails |
>=8.0.0 |
문서화된 optional subpath에서만 |
react-native |
>=0.71.0 |
문서화된 optional subpath에서만 |
공개 subpath
섹션 제목: “공개 subpath”| Entry | Exported symbols |
|---|---|
Root entry |
167 |
./core |
157 |
./device |
1 |
./image |
12 |
./image/pure |
6 |
./picker |
1 |
./save |
1 |
./storage |
10 |
./testing |
36 |
./video |
1 |
./web |
5 |
안전 경계
섹션 제목: “안전 경계”presigned URL이나 native URI 세부 정보를 공개 오류에 노출하지 말고, 정리 권한과 attachment 트랜잭션은 소비 앱에 둡니다.
API reference
섹션 제목: “API reference”전체 API reference에는 선언 파일에서 생성한 모든 공개 함수, 클래스, 타입, 상수, 오류 계약이 있습니다. 각 항목은 import path, release signature, package version을 표시합니다.
패키지 관계
섹션 제목: “패키지 관계”| 관계 | 패키지 | 함께 쓰는 이유 |
|---|---|---|
| 단독 사용 | — | 다른 GJ Kit 패키지 없이 도입할 수 있습니다. |
| 선택 조합 | @gj-kit/expo-auth |
미디어 업로드 권한을 앱 세션에 둘 때 함께 사용합니다. |