[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-10 22:08:15 +09:00
parent 21ac3ed255
commit 504fd5fb42
3011 changed files with 380280 additions and 206977 deletions
@@ -0,0 +1,350 @@
---
id: web-file-system-access
title: File System Access API — browser 가 file 직접
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [web, file-system, vibe-coding]
tech_stack: { language: "TS", applicable_to: ["Frontend"] }
applied_in: []
aliases: [File System Access API, showOpenFilePicker, showSaveFilePicker, FileSystemHandle, OPFS]
---
# File System Access API
> Browser 가 user 의 file 을 read/write. **`showOpenFilePicker` / `showSaveFilePicker`**. Native app 비슷한 UX. Chrome / Edge (Safari / FF 부분).
## 📖 핵심 개념
- File handle: 영구 reference (permission 유지).
- OPFS: Origin Private File System (sandboxed).
- 사용자 명시 grant.
- Streaming 가능.
## 💻 코드 패턴
### File 열기
```ts
const [handle] = await window.showOpenFilePicker({
types: [{ description: 'Text', accept: { 'text/plain': ['.txt'] } }],
});
const file = await handle.getFile();
const text = await file.text();
```
### File 저장
```ts
const handle = await window.showSaveFilePicker({
suggestedName: 'export.json',
types: [{ description: 'JSON', accept: { 'application/json': ['.json'] } }],
});
const writable = await handle.createWritable();
await writable.write(JSON.stringify(data));
await writable.close();
```
→ User 가 location / name 선택.
### Directory 열기
```ts
const dirHandle = await window.showDirectoryPicker();
for await (const [name, handle] of dirHandle.entries()) {
if (handle.kind === 'file') {
const file = await handle.getFile();
console.log(name, file.size);
} else {
// recursive
}
}
```
### File 변경 모니터링 (poll)
```ts
let lastModified = 0;
setInterval(async () => {
const file = await handle.getFile();
if (file.lastModified !== lastModified) {
lastModified = file.lastModified;
const newContent = await file.text();
onChange(newContent);
}
}, 1000);
```
→ True file watch X — poll.
### Persistent handle (IndexedDB 저장)
```ts
// Save
import { openDB } from 'idb';
const db = await openDB('fileHandles', 1);
await db.put('handles', handle, 'lastFile');
// Restore (next session)
const handle = await db.get('handles', 'lastFile');
const perm = await handle.queryPermission({ mode: 'readwrite' });
if (perm !== 'granted') {
await handle.requestPermission({ mode: 'readwrite' });
}
```
→ User 가 같은 file 다시 open 안 함.
### OPFS (Origin Private File System)
```ts
const opfs = await navigator.storage.getDirectory();
// Write
const fileHandle = await opfs.getFileHandle('app.db', { create: true });
const writable = await fileHandle.createWritable();
await writable.write(new Uint8Array([...]));
await writable.close();
// Read
const file = await fileHandle.getFile();
const buf = await file.arrayBuffer();
```
→ Sandboxed (사용자 X). 큰 storage. SQLite WASM 가 사용.
### Sync access (worker only)
```ts
// In Web Worker
const opfs = await navigator.storage.getDirectory();
const handle = await opfs.getFileHandle('db.sqlite');
const sync = await handle.createSyncAccessHandle();
// Sync read / write
const buf = new Uint8Array(1024);
sync.read(buf, { at: 0 });
sync.write(new Uint8Array([1,2,3]), { at: 0 });
sync.flush();
sync.close();
```
→ Async overhead 없음. SQLite 식.
### Streaming write (큰 file)
```ts
const writable = await handle.createWritable();
const ws = writable.getWriter();
for (const chunk of bigData) {
await ws.ready;
await ws.write(chunk);
}
await ws.close();
```
→ Memory efficient.
### File picker option
```ts
await window.showOpenFilePicker({
multiple: true,
types: [
{ description: 'Images', accept: { 'image/*': ['.png', '.jpg'] } },
{ description: 'PDF', accept: { 'application/pdf': ['.pdf'] } },
],
excludeAcceptAllOption: true,
startIn: 'documents', // 'desktop' / 'downloads' / 'pictures' / 'music' / 'videos'
});
```
### Permission
```ts
async function ensureWrite(handle) {
const opts = { mode: 'readwrite' };
if ((await handle.queryPermission(opts)) === 'granted') return true;
if ((await handle.requestPermission(opts)) === 'granted') return true;
return false;
}
```
→ User 가 매번 prompt. Persistent 가능.
### Drag & drop + handle
```ts
window.addEventListener('drop', async (e) => {
e.preventDefault();
for (const item of e.dataTransfer!.items) {
const handle = await item.getAsFileSystemHandle();
if (handle.kind === 'file') {
const file = await handle.getFile();
} else {
// directory
}
}
});
```
### Editor 가 file save
```tsx
function Editor() {
const [handle, setHandle] = useState<FileSystemFileHandle>();
const [content, setContent] = useState('');
const open = async () => {
const [h] = await window.showOpenFilePicker();
setHandle(h);
setContent(await (await h.getFile()).text());
};
const save = async () => {
let h = handle;
if (!h) h = await window.showSaveFilePicker();
const w = await h.createWritable();
await w.write(content);
await w.close();
setHandle(h);
};
// Cmd+S
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.metaKey && e.key === 's') { e.preventDefault(); save(); }
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [save]);
return <textarea value={content} onChange={(e) => setContent(e.target.value)} />;
}
```
### SQLite WASM + OPFS
```ts
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.OpfsDb('app.db');
db.exec('CREATE TABLE IF NOT EXISTS users (id INT, name TEXT)');
db.exec('INSERT INTO users VALUES (1, "Alice")');
const r = db.exec('SELECT * FROM users', { rowMode: 'object' });
```
→ Local DB. Browser 만 (no server).
### 큰 file (Photo editor)
```ts
const [handle] = await window.showOpenFilePicker({
types: [{ accept: { 'image/*': ['.png', '.jpg'] } }],
});
const file = await handle.getFile(); // 100MB
const arr = await file.arrayBuffer(); // memory load — careful
```
→ 큰 file 가 stream:
```ts
const stream = file.stream();
// process chunks
```
### Browser support
```
File System Access API:
- Chrome 86+
- Edge 86+
- Opera 72+
- Safari: showSaveFilePicker, showOpenFilePicker (16.4+, partial)
- Firefox: 안 (no plan)
OPFS:
- 모든 modern (Safari 16, FF 111, Chrome 102)
```
→ Fallback = `<input type="file">` + `URL.createObjectURL`.
### Fallback
```ts
async function openFile(): Promise<File> {
if ('showOpenFilePicker' in window) {
const [h] = await window.showOpenFilePicker();
return h.getFile();
}
// Fallback
const input = document.createElement('input');
input.type = 'file';
input.click();
return new Promise((res) => input.onchange = () => res(input.files![0]));
}
async function saveFile(blob: Blob, name: string) {
if ('showSaveFilePicker' in window) {
const h = await window.showSaveFilePicker({ suggestedName: name });
const w = await h.createWritable();
await w.write(blob);
await w.close();
return;
}
// Fallback
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = name;
a.click();
URL.revokeObjectURL(a.href);
}
```
### Use case
- VS Code Web (Microsoft)
- Photoshop Web (Adobe)
- Figma (web)
- Google Docs (limited)
- IDE / editor (StackBlitz, CodeSandbox)
- DAW / audio editor (BandLab)
### Security
```
- HTTPS only
- User gesture 필수 (click 안 = call 안 됨)
- Domain-scoped (handle 가 다른 site 에서 사용 X)
- System dir block (e.g. /System on macOS)
```
### Permission persistence
```
Chrome: 같은 origin, 같은 file = persisted (refresh OK).
Browser close = revoked.
→ IndexedDB 가 handle 저장 → re-prompt.
```
## 🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| Native-like editor | File System Access API |
| 큰 local DB | OPFS + SQLite |
| 일반 download | Fallback (input + a.click) |
| Photo edit | File handle + stream |
| Cross-browser (FF) | Fallback |
| Cloud sync | Server upload |
| Drag & drop | DataTransferItem |
## ❌ 안티패턴
- **User gesture 없이 호출**: error.
- **Permission 매번 prompt**: persist.
- **큰 file 가 arrayBuffer**: OOM.
- **Safari / FF 가정**: fallback 없음.
- **OPFS 가 user-visible 가정**: sandboxed.
- **Sync access in main thread**: blocked.
- **Origin 변경 + handle**: invalid.
## 🤖 LLM 활용 힌트
- File System Access API 가 Chrome / Edge 만 — fallback.
- OPFS = 큰 sandboxed storage (SQLite WASM 친화).
- Handle = persistent reference (IndexedDB).
- Streaming = 큰 file.
## 🔗 관련 문서
- [[Frontend_Streams_API]]
- [[Web_PWA_Service_Worker]]
- [[DB_SQLite_Patterns]]