S3 호환 버킷(R2, S3, MinIO)과 로컬 디스크 파일 저장, 서명된 직접 업로드
코드파일 11개컨텍스트약 697토큰검사 통과
설치
$
genpm add @core/storage포함 내용
- src/lib/storage/에 소스 코드, 파일 11개. (22.7kB)
- src/lib/storage/AGENTS.md에 AI 규칙, 그리고 IDE 규칙 파일.
- .env.example에 추가되는 환경 변수: STORAGE_DRIVER, STORAGE_BUCKET, STORAGE_ENDPOINT, STORAGE_ACCESS_KEY_ID, STORAGE_SECRET_ACCESS_KEY, STORAGE_PUBLIC_URL, STORAGE_SECRET, STORAGE_LOCAL_DIR.
README
이 패키지에는 README가 없습니다.
약 697토큰→ src/lib/storage/AGENTS.md→ .cursor/rules/genpm-core-storage.mdc
이것이 AI가 src/lib/storage에서 작업할 때 읽는 내용 그대로입니다. 그 외에는 컨텍스트에 아무것도 추가되지 않습니다.
@core/storage — rules for AI agents
Purpose
One StorageProvider interface to store and serve files: S3-compatible buckets (Cloudflare R2, AWS S3, MinIO, B2)
signed with SigV4 (aws4fetch, works on Node and Workers), plus local (disk) and memory drivers for development
and tests. Signed direct uploads from the browser with the exact size and content type signed. No media library,
no image processing (that is @core/media). No tables.
Map
index.ts— public API:getStorage(),setStorage(),generateKey(),assertSafeKey(),s3Storage(), types.s3.ts— S3 driver.memory.ts— local/memory drivers and signed tokens.local-routes.ts— handlers for them.adapters/hono.ts,adapters/next.ts— upload/download route, only needed withlocal/memory.
Integration
- Env for S3/R2 (default driver):
STORAGE_ENDPOINT(R2:https://<account>.r2.cloudflarestorage.com),STORAGE_BUCKET,STORAGE_ACCESS_KEY_ID,STORAGE_SECRET_ACCESS_KEY, optionalSTORAGE_REGION(defaultauto) andSTORAGE_PUBLIC_URL(CDN/custom domain serving thepublic/prefix). Local dev:STORAGE_DRIVER=local,STORAGE_SECRET(≥ 32 chars),STORAGE_PUBLIC_URL=http://localhost:3000(files go toSTORAGE_LOCAL_DIR, default.storage/: add it to.gitignore), and mount the route: Honoapp.route('/api/storage', localStorageRoutes()); Next.jsapp/api/storage/[[...key]]/route.tswithexport { localStoragePut as PUT, localStorageGet as GET } from '@/lib/storage/adapters/next'. - Bucket CORS (S3/R2): allow
PUTfrom your origin with headercontent-type. - Direct upload: server creates
const key = generateKey('public/media', file.name)andawait getStorage().presignUpload(key, { contentType, size, maxBytes: 10_000_000 }); the browser doesfetch(url, { method: 'PUT', headers, body: file }). - Verify: upload a small file, then
getStorage().head(key)returns its size.
Conventions
- Keys are generated by the server (
generateKey); never use the user's filename or path as the key. - Everything is private unless its key starts with
public/; serve private files withpresignDownloadafter an authorization check. - Store the key in your table, not the URL (URLs change with the CDN or driver).
- A presigned upload URL can be reused until it expires. If the server validates or transforms the file after upload,
presign a private key (e.g.
uploads/pending/…) and have the server copy the validated bytes to the finalpublic/key.
Don't
- Don't make the whole bucket public or proxy private files without checking permissions.
- Don't trust the client's content type for security decisions; @core/media sniffs the real type after upload.
- Don't log storage credentials or presigned URLs.
# @core/storage — rules for AI agents
## Purpose
One `StorageProvider` interface to store and serve files: S3-compatible buckets (Cloudflare R2, AWS S3, MinIO, B2)
signed with SigV4 (`aws4fetch`, works on Node and Workers), plus `local` (disk) and `memory` drivers for development
and tests. Signed direct uploads from the browser with the exact size and content type signed. No media library,
no image processing (that is @core/media). No tables.
## Map
- `index.ts` — public API: `getStorage()`, `setStorage()`, `generateKey()`, `assertSafeKey()`, `s3Storage()`, types.
- `s3.ts` — S3 driver. `memory.ts` — local/memory drivers and signed tokens. `local-routes.ts` — handlers for them.
- `adapters/hono.ts`, `adapters/next.ts` — upload/download route, only needed with `local`/`memory`.
## Integration
1. Env for S3/R2 (default driver): `STORAGE_ENDPOINT` (R2: `https://<account>.r2.cloudflarestorage.com`),
`STORAGE_BUCKET`, `STORAGE_ACCESS_KEY_ID`, `STORAGE_SECRET_ACCESS_KEY`, optional `STORAGE_REGION` (default `auto`)
and `STORAGE_PUBLIC_URL` (CDN/custom domain serving the `public/` prefix).
Local dev: `STORAGE_DRIVER=local`, `STORAGE_SECRET` (≥ 32 chars), `STORAGE_PUBLIC_URL=http://localhost:3000`
(files go to `STORAGE_LOCAL_DIR`, default `.storage/`: add it to `.gitignore`), and
mount the route: Hono `app.route('/api/storage', localStorageRoutes())`; Next.js
`app/api/storage/[[...key]]/route.ts` with `export { localStoragePut as PUT, localStorageGet as GET } from '@/lib/storage/adapters/next'`.
2. Bucket CORS (S3/R2): allow `PUT` from your origin with header `content-type`.
3. Direct upload: server creates `const key = generateKey('public/media', file.name)` and
`await getStorage().presignUpload(key, { contentType, size, maxBytes: 10_000_000 })`; the browser does
`fetch(url, { method: 'PUT', headers, body: file })`.
4. Verify: upload a small file, then `getStorage().head(key)` returns its size.
## Conventions
- Keys are generated by the server (`generateKey`); never use the user's filename or path as the key.
- Everything is private unless its key starts with `public/`; serve private files with `presignDownload` after an authorization check.
- Store the key in your table, not the URL (URLs change with the CDN or driver).
- A presigned upload URL can be reused until it expires. If the server validates or transforms the file after upload,
presign a private key (e.g. `uploads/pending/…`) and have the server copy the validated bytes to the final `public/` key.
## Don't
- Don't make the whole bucket public or proxy private files without checking permissions.
- Don't trust the client's content type for security decisions; @core/media sniffs the real type after upload.
- Don't log storage credentials or presigned URLs.
.genpmignore 적용 후 주입될 정확한 트리입니다. 고정 대상:
// Interfaz común de los drivers (S3 compatible, local, memoria).
export type PutBody = Uint8Array | ArrayBuffer | string | Blob;
export type PutOptions = { contentType: string; cacheControl?: string };
export type StoredObject = { key: string; size: number; contentType: string; body: ReadableStream<Uint8Array> };
export type ObjectInfo = { key: string; size: number; contentType: string };
/** Subida directa desde el navegador: `fetch(url, { method, headers, body: file })`. */
export type PresignedUpload = { url: string; method: 'PUT'; headers: Record<string, string>; expiresAt: Date };
export type PresignUploadOptions = {
contentType: string;
/** Tamaño exacto declarado por el cliente; va firmado, así que no puede subir otra cosa. */
size: number;
/** Límite superior; si `size` lo supera se rechaza antes de firmar. */
maxBytes: number;
expiresIn?: number;
};
export interface StorageProvider {
readonly driver: 's3' | 'local' | 'memory';
put(key: string, body: PutBody, opts: PutOptions): Promise<ObjectInfo>;
get(key: string): Promise<StoredObject | null>;
head(key: string): Promise<ObjectInfo | null>;
delete(key: string): Promise<void>;
presignUpload(key: string, opts: PresignUploadOptions): Promise<PresignedUpload>;
/** URL temporal de descarga para objetos privados. */
presignDownload(key: string, expiresIn?: number): Promise<string>;
/** URL pública estable; solo para claves bajo `public/`. */
publicUrl(key: string): string;
}
export const toBytes = async (body: PutBody): Promise<Uint8Array> =>
typeof body === 'string'
? new TextEncoder().encode(body)
: body instanceof Uint8Array
? body
: body instanceof ArrayBuffer
? new Uint8Array(body)
: new Uint8Array(await body.arrayBuffer());
export const streamOf = (bytes: Uint8Array): ReadableStream<Uint8Array> =>
new ReadableStream({
start(c) {
c.enqueue(bytes);
c.close();
},
});
이 패키지는 MCP 서버를 선언하지 않습니다.
| 버전 | 커밋 | 게시일 | 검사 |
|---|---|---|---|
| 1.0.1 | 5b6a9cc | 4시간 전 | 검사 통과 |
- genpm
- 없음
- 제안됨
- GenPM은 npm 명령을 제안하고, 동의한 경우에만 실행합니다.
- 사용하는 패키지 (1)
- @core/media ^1.0.0
- 검사
- 검사 통과 · 문제 0건
- 커밋
- v1.0.1 → 5b6a9cc5574ad638d71214af234145082f01cb98 · 가져온 뒤 검증됨
- 스크립트
- 없음. GenPM은 패키지 코드를 절대 실행하지 않습니다.
- 라이선스
- MIT
- 품질
- 100/100
- 인정된 라이선스충족
- AGENTS.md에 목적 설명충족
- AGENTS.md에 통합 단계충족
- AGENTS.md에 규칙 또는 금지 사항충족
- 테스트 포함충족
- 보안 검사 통과충족
- 최근 6개월 내 게시충족
- 인증된 게시자충족
- 요약과 키워드충족
- 신고
- 문제가 있나요?