콘텐츠로 이동

@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으로 확인됩니다.

완료 상태: 앱이 업로드 권한을 계속 소유하는 Expo 기반 미디어 kit을 만듭니다.

Terminal window
pnpm add @gj-kit/expo-media

2. 앱이 소유할 경계를 정합니다

섹션 제목: “2. 앱이 소유할 경계를 정합니다”

앱에서 upload intent와 완료를 담당하는 uploadApi를 구현하고, 파일 제한을 명시합니다.

먼저 아래 코드를 복사한 뒤, 위에서 언급한 앱 소유 값만 교체하세요.

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 정리 정책을 이 라이브러리에 넣지 마세요.

엔진: 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에서만
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에는 선언 파일에서 생성한 모든 공개 함수, 클래스, 타입, 상수, 오류 계약이 있습니다. 각 항목은 import path, release signature, package version을 표시합니다.

관계 패키지 함께 쓰는 이유
단독 사용 — 다른 GJ Kit 패키지 없이 도입할 수 있습니다.
선택 조합 @gj-kit/expo-auth 미디어 업로드 권한을 앱 세션에 둘 때 함께 사용합니다.

npm · GitHub