원문: https://nolanlawson.com/2026/01/31/building-a-browser-api-in-one-shot/

TL;DR 프롬프트 하나로 Claude Code와 Ralph 루프를 사용해 IndexedDB 구현체를 만들었습니다. 목표로 삼은 Web Platform Tests 하위 집합의 95%를 통과했고, 더 엄격한 테스트 하위 집합은 77.4%를 통과했습니다.
정작 놀라웠던 점은 두 프로젝트 모두 Web Platform Tests(WPT)를 제대로 활용하지 않는 듯 보였다는 것입니다. WPT에는 헤아릴 수 없이 많은 사람의 시간이 들어간 전문 지식이 응축되어 있습니다. 브라우저가 어떻게 동작해야 하는지를 가장 기이한 엣지 케이스까지 정밀하게 정의해 둔 결과물이죠. (두 번째 프로젝트는 WPT를 부분적으로 사용하긴 하지만, 주된 테스트 전략으로 보이지는 않습니다.)
LLM은 명확한 명세(또는 PRD)와 인수 테스트를 주면 아주 잘 동작합니다. 웹 표준 커뮤니티가 지난 수십 년간 공들여 쌓아 온 것이 바로 이것입니다. 평범한 영어로 쓰인 HTML 파일 형태의 브라우저 표준 자체, 그리고 WPT 말이죠. 특히 WPT 통과율은 브라우저가 얼마나 "웹 호환적"인지(즉 실제 세상의 웹사이트를 제대로 렌더링할 수 있는지) 가늠하는 좋은 척도입니다. Ladybird나 Servo 같은 최신 브라우저가 WPT에 크게 의존하는 이유입니다.
저는 브라우저 전체를 만들 인내심(이나 현금)은 없지만, 프롬프트 하나로 브라우저 API 하나를 밑바닥부터 만들어 Web Platform Tests를 무시 못 할 비율로 통과시켜 보면 흥미롭겠다고 생각했습니다. IndexedDB를 고른 이유는 제가 아주 잘 아는 명세이기 때문입니다. PouchDB와 fake-indexeddb 양쪽에서 작업했고, 명세 자체에도 작은 PR과 버그를 올린 적이 있습니다.
IndexedDB는 간단한 API가 아닙니다. 여러 키 타입(배열 키와 키로 쓰이는 배열 포함), 커서, 내구성 모드(durability mode), 트랜잭션, 스케줄링 등을 갖춘 완전한 NoSQL 데이터베이스입니다. SQLite 위에 만들면 이런 것 중 일부는 공짜로 얻을 수 있지만(Firefox와 WebKit의 구현이 SQLite를 쓰는 이유도 아마 이 때문일 겁니다), Date나 ArrayBuffer 같은 자바스크립트 객체 타입, 자바스크립트 고유의 마이크로태스크 타이밍, 자동 트랜잭션을 비롯한 온갖 별난 점은 여전히 직접 처리해야 합니다.
실험
실험은 이렇게 진행했습니다.
- Web Platform Tests와 IndexedDB 명세를 모두 서브모듈로 담은 저장소를 만듭니다.
- Claude에게 (플랜 모드로) SQLite 위에 타입스크립트와 Node.js로 동작하는 IndexedDB 구현체를 만들어 테스트의 90% 이상을 통과시키는 계획을 세우라고 시킵니다.
- 그 계획을 Ralph 루프에 넣어 여러 에이전트가 순차적으로 문제 해결을 반복하게 합니다.
- 잠자리에 들었다가 다음 날 아침에 일어납니다.
이른바 Ralph Wiggum 기법이 낯설다면, 아주 단순합니다. 지시 사항을 담은 마크다운 파일과 진행 상황을 기록할 텍스트 파일을 주고 Claude를 Bash 루프로 돌리는 겁니다. (말 그대로 이게 전부입니다.) 완전히 새로운 세션을 자주 시작해 컨텍스트 부패(context rot)를 피하는 것이 핵심입니다. 다시 말해 LLM은 대화가 길어질수록 멍청해지니, 대화를 짧게 가져가라는 겁니다. 저는 Matt Pocock의 구현을 (말 그대로 Bash 24줄입니다) --dangerously-skip-permissions 모드로, 안전을 위해 Podman 컨테이너 안에서 사용했습니다.
프로젝트는 몇 시간 만에 완료됐고, 에이전트는 제 지시를 어기기로 하고는 목표 테스트의 90%를 훌쩍 넘겨 95%까지 통과시켰습니다. (말 안 듣는 로봇!) Node.js 환경에 적합하지 않다고 판단한 테스트 일부를 제외하긴 했지만, 그래도 목표 하위 집합 1,272개 중 1,208개를 통과한 셈입니다.
제가 쓴 프롬프트입니다. 보시면 오타와 문법 오류가 몇 군데 있지만(예를 들어 typeof가 아니라 instanceof를 의도했습니다), 에이전트는 그래도 알아서 이해했습니다.
프롬프트 보기
프로젝트 계획을 도와주세요. IndexedDB 명세 전체와 web-platform-tests가 git 서브모듈로 체크아웃되어 있습니다.
프로젝트는 이렇습니다. SQLite 위에서 순수 자바스크립트(의존성 없음)로 IndexedDB를 구현하는 타입스크립트 기반 프로젝트를 만드세요(그래요, SQLite가 유일한 의존성이긴 합니다). WPT의 IndexedDB 테스트를 최소 90% 통과하도록 시도해야 합니다.
조건은 다음과 같습니다.
- 타입스크립트를 사용하고 네이티브 Node에서 실행하세요(TS를 기본 지원하는 Node v24가 이미 설치되어 있습니다). 다만 린팅에는 tsc를 사용하세요
node:test로 테스트를 작성하세요- WPT 테스트를 Node에서 수정 없이 그대로 실행해야만 합니다. 그러려면 분명 심(shim)을 좀 써야 할 겁니다. 테스트는 Node가 아니라 브라우저에서 실행되도록 설계됐으니까요. 하지만 가능한 한 내장 기능을 우선하세요. Node는 이제 Event나 EventTarget 같은 내장 기능을 많이 지원하니 그리 어렵지 않을 겁니다.
- 먼저 기본 프로젝트 스캐폴딩과 테스트 스캐폴딩을 설정하는 것부터 시작하세요. 시작할 때는 테스트 하나만 통과시켜 보세요. 그러기 위해 IndexedDB의 기본적인 순수 JS 구현("hello world" 수준)을 만들어야 하더라도 괜찮습니다.
- 다음 에이전트를 위해 이런 기본 조건과 프로젝트 구조를 진행하면서 CLAUDE.md에 기록해 두세요. 예를 들어 테스트 실행 방법, 린트 방법 같은 것들 말입니다.
- 구현은 최종적으로 sqlite에 데이터를 저장해야 합니다. 이를 위해 better-sqlite3 패키지를 사용하세요. 다시 말하지만 이것 말고 다른 의존성은 없습니다. (devDependencies는 원하는 만큼 둬도 됩니다. 예를 들어 typescript 같은 것들요)
- 우리는 계획을 세우는 중이고, 이 계획이 대략 90% 테스트 커버리지에 도달하는 데 필요한 모든 것을 담기를 바랍니다. 그러려면 먼저 다루는 게 합리적인 테스트 하위 집합으로 PRD를 나누는 편이 좋겠지만, 순서를 바꾸는 게 낫다면 이후 에이전트에게 맡겨도 됩니다
- 가능한 한 구현을 JS 환경에 구애받지 않도록 만드세요. 우리는 Node에서 실행하겠지만, 언젠가 SQLite-on-WASM 위에서 브라우저로 돌리고 싶어진다면 그게 불가능하지는 않아야 합니다. 테스트 하네스 코드에는 필요하다면 Node 전용 코드가 있어도 되지만, 우리가 만드는 실제 라이브러리는 환경에 구애받지 않도록 애써야 합니다.
- 최종적으로 테스트 스위트에는 어떤 테스트가 통과하고, 실패하고, 타임아웃되는지 등을 담은 매니페스트 파일이 있어야 합니다. 테스트 스위트의 진행 상황을 판단하고 다음 에이전트에게 무엇을 다룰지 안내하는 좋은 방법이 될 겁니다. 이상적으로는 이 매니페스트 파일에 주석이 있어서 특정 테스트가 까다로운지 아니면 아예 불가능한지 에이전트가 알 수 있으면 좋겠습니다(toml이나 yaml이 좋은 형식일 수 있습니다).
- sudo가 되는 샌드박스에서 실행 중이니 도구를 설치해야 한다면 그냥 하세요.
- wpt의 IndexedDB 테스트에서 90% 테스트 커버리지에 도달하면 프로젝트는 완료입니다. 이 수치는 통과한 테스트 파일 수가 아니라 통과한 테스트 수를 기준으로 해야 합니다.
- 테스트 스크립트는 통과/실패 테스트 매니페스트를 출력해야 합니다. 그러면 다음 에이전트가 실제로 테스트를 돌리지 않고도(시간이 걸리니까요) 어떤 테스트가 통과하고 실패하는지 알 수 있습니다. git에 커밋할 때마다 이 매니페스트 파일도 함께 커밋하세요.
- 단순함을 위해 테스트 격리에는 vm 계열 기법 대신 서브프로세스나 워커를 사용하세요. vm 기법은 자바스크립트의 크로스 렐름 문제를 일으킬 수 있습니다(예를 들어
typeof Array가 제대로 나오지 않는 문제요). - 이 프로젝트에서 "하나의 작업"은 단순함을 유지하기 위해 한 번에 테스트 하나(많아야 둘)로 봐야 합니다. IndexedDB의 거대한 기능 전체(예를 들어 커서, 인덱스 등)를 한입에 물려고 하지 말고 작업을 작은 덩어리로 쪼개세요.
- 이 프로젝트의 주된 목표는 명세 준수이지만, 성능이 좋으면 더 좋습니다. 최대 성능을 위해 SQLite 기능을 활용하세요(순수 자바스크립트로 처리하면서 성능이 좋은 척하지 마세요). 작업이 그냥 "성능 개선"이어도 괜찮습니다.
그리고 프로젝트 자체는 여기에 있습니다.
git 히스토리만 봐서는 알기 어렵겠지만, 가장 어려웠던 부분은 그저 루프를 계속 돌리는 일이었습니다. Bash 루프가 집요하게 돌아가는데도 Claude Code는 이따금 이런 오류를 내며 죽곤 했습니다.
Error: No messages returned
at FKB (/$bunfs/root/claude:6151:78)
at processTicksAndRejections (native:7:39)
버그로 보입니다. 성가시긴 해도 치명적이지는 않았습니다. 죽으면 루프를 다시 시작하면 그만이었으니까요. 그래서 "하룻밤 사이"에 끝나지는 않았지만, 아침 식사를 마칠 즈음에는 완성되어 있었습니다.
코드 평가하기
프로젝트 구조를 보면 꽤 단순하고, 파일 이름도 (제게는) 익숙합니다. IDBCursor.ts, IDBFactory.ts 같은 식이죠. 명세의 이름 규칙을 따르고 fake-indexeddb 같은 프로젝트의 패턴도 따르니 놀랍지는 않습니다(그 프로젝트도 분명 LLM 학습 데이터의 일부였을 겁니다). 테스트 하네스는 특정 테스트를 통과시키려고 window.addEventListener나 ImageData 같은 브라우저 API를 심으로 대체해야 하는데, fake-indexeddb에서도 우리가 했던 것과 똑같습니다.
cloc에 따르면 src 디렉터리는 코드 4,395줄입니다. 이벤트 디스패치처럼 까다로우리라 예상했던 부분을 훑어봤습니다. fake-indexeddb와 비슷한 전략을 취해, Node.js 내장 기능에 의존하는 대신 이벤트 디스패치와 리스너 로직을 심으로 대체하고 있더군요. 놀랍지는 않았습니다. (정말 간단한 일이 아니거든요!)
흥미롭게도 fake-indexeddb와 갈라지는 지점도 있습니다. v8.serialize()로 자체 structuredClone 로직을 구현했더군요. fake-indexeddb와 달리 자바스크립트 객체를 메모리에 담아 두는 호사를 누릴 수 없고 대신 SQLite로 직렬화해야 하기 때문이라고 짐작합니다. 그러니 학습 데이터를 베끼는 것이라고 주장할 수도 있겠지만, 이 경우에는 꽤 독자적인 일을 하고 있기도 합니다.
트랜잭션 스케줄러는 fake-indexeddb의 로직과 전혀 닮지 않았지만, 합리적으로 설계됐고 적어도 읽을 만합니다. 물론 sqlite-backend.ts도 있습니다. 이 파일은 제가 아는 유일한 비교 대상 구현체(IndexedDBShim)와 갈라지는데, IndexedDBShim처럼 SQL을 API 안에 섞어 넣는 대신 SQL 로직을 위한 제대로 된 "백엔드"를 두었습니다(IndexedDBShim의 방식은 제 생각에는 좀 편법 같습니다).
코딩 스타일에서 한 가지 거슬리는 점은 실제 명세를 별로 참조하지 않는다는 것입니다. fake-indexeddb나 브라우저 소스 코드(제 경험상 특히 Ladybird와 Servo)를 읽어 보면 명세 문구를 그대로 인용한 주석이 자주 있습니다. 아주 좋은 방식입니다. 명세는 어차피 대개 의사 코드(pseudocode)라서, 브라우저 구현이 실제로 명세와 일치하는지 독자가 따라가는 데 도움이 되니까요. Claude는 이런 주석을 아예 피한 듯합니다. WPT에만 전적으로 의존했거나, 아니면 한 마디 한 마디 옮긴 주석이 그럴 가치가 없다고 봤겠죠.
코드 리뷰 중에 알아차린 또 한 가지는 에이전트가 통과율을 살짝 부풀렸다는 점입니다. 원래 목표로 삼은 테스트 파일 중 9개에서 크래시가 났고, 그래서 분모에 포함되지 않았습니다(아마 테스트가 몇 개나 실행됐을지 몰랐기 때문일 겁니다). 크래시 난 테스트를 모두 실패로 친다면 "진짜" 통과율은 92%, 1313개 중 1208개입니다(진짜 분모는 wpt.fyi로 구했습니다). 공정하게 말하면, 크래시 없이 실행된 테스트 파일에 한해서는 95%가 맞습니다.
마지막 테스트로, fake-indexeddb의 자체 WPT 테스트 스위트에 이 코드를 돌려 봤습니다. 수상한 구석은 없는지, LLM이 자기를 좋아 보이게 하려고 테스트를 골라 담지는 않았는지 확인하려는 것이었습니다. 두 테스트 스위트가 1대 1로 대응하지는 않습니다. 에이전트가 IDL 하네스처럼 크고 까다로운 테스트 일부를 건너뛰기로 했고, 앞서 말한 크래시 난 테스트 9개도 있으니까요. 그러니 fake-indexeddb의 자체 테스트를 쓰면 비교 대상 IndexedDB 구현체와 이 코드를 더 정확하게 견줄 수 있습니다.
더 엄격한 이 테스트에서 구현체는 77.4%를 기록했습니다. fake-indexeddb 자체의 82.8%와 견줘도 나쁘지 않은 성적이죠(약 5% 차이에 불과합니다). 브라우저와도 비교해 볼 수 있습니다.
| 구현체 | 버전 | 통과 | % |
|---|---|---|---|
| Chrome | 144.0.7514.0 | 1651 | 99.9% |
| Firefox | 146.0a1 | 1498 | 90.6% |
| Safari | 231 preview | 1497 | 90.6% |
| Ladybird | 1.0-cde3941d9f | 1426 | 86.3% |
| fake-indexeddb | 6.2.5 | 1369 | 82.8% |
| 원샷(one-shot) | 1279 | 77.4% |
fake-indexeddb가 10년쯤 됐고 기여자가 15명이라는 점을 생각하면 77.4% 대 82.8%는 정말 나쁘지 않습니다. 다만 대략 40%만 넘겨도 대체로 동작하는 구현체가 된다고 생각합니다. WPT 상당수는 코너 케이스이거나 IDL의 별난 점, 예를 들어 프로퍼티가 enumerable인지 configurable인지 같은 것들이니까요.
원샷 구현체는 fake-indexeddb가 실패하는 테스트 30개를 실제로 통과하는데, 대부분 IDL 하네스 테스트 영역입니다. 반대로 fake-indexeddb는 통과하고 원샷은 실패하는 테스트 88개는 대부분 구조화된 복제와 blob 직렬화, IDBCursor 객체의 프로퍼티, 분리된(detached) ArrayBuffer 같은 유효하지 않은 키에 대한 오류, 그 밖의 엣지 케이스입니다.
fake-indexeddb의 WPT 테스트는 49.2초 만에 끝난 반면 원샷 구현체는 125.5초가 걸렸습니다(2.5배 느리고, 3회 반복의 중앙값입니다). 그러니 성능에는 개선의 여지가 분명히 있습니다. 공정하게 말하면 이건 실제로 저장까지 하는 SQLite 구현과 인메모리 구현을 비교한 것입니다. 그리고 제가 fake-indexeddb를 최적화하느라 어지간히 애를 쓰기도 했고요! 또 다른 원인은 작업 큐잉에 기본적인 setTimeout을 택했다는 점이라고 짐작합니다. fake-indexeddb에서 우리는 훨씬 최적화된 전략을 썼거든요.
결론
저는 최근 LLM을, 그리고 LLM이 제 코딩 워크플로우를 어떻게 바꿨는지를 자주 이야기해 왔습니다. 제 독자 상당수는 에너지 사용량, 저작권, 빅테크 기업의 동기 등 LLM을 둘러싼 윤리 문제를 우려합니다. 하지만 제 목표는 그저 이것들이 동작한다는 사실을 보여 주는 것이었습니다. 기술이 그저 과대 포장된 것이었다면 무시하기 쉬웠겠지만, (제게는 다소 서글프게도) 실제로 동작합니다.
이 실험은 Opus 4.5 같은 최신 모델이 얼마나 멀리 왔는지 보여 주는 좋은 예입니다. 명확한 테스트와 명세를 갖춘 충분히 좋은 프롬프트를 주면, 밤에 잠들었다가 다음 날 아침에 동작하는 코드베이스와 함께 깨어날 수 있습니다. LLM 이전에는 실제로 존재하는 독립적인 IndexedDB 구현체의 수를 두 손으로 셀 수 있었을 겁니다(브라우저 벤더 5곳 남짓에 fake-indexeddb와 IndexedDBShim). 그런데 이제는 필요할 때마다 새로 만들 수 있습니다.
비용도 그리 비싸지 않았습니다. 이 프로젝트는 월 100달러짜리 Claude 요금제에서 제 주간 사용량의 20% 정도를 썼으니, 7달러쯤 들었다고 해 두죠. 물론 비용이 보조받고 있고 앞으로 오를 거라고 말하는 사람도 있을 텐데(저도 부정하지 않습니다), 그래도 오늘 지불하는 값은 이 정도입니다. 새 IndexedDB 구현체 하나를 근사한 펍의 감자튀김 사이드 메뉴 값 정도에 얻을 수 있습니다.
그럼 이 프로젝트는 이제 어디로 갈까요? 5년 전이었고 그럭저럭 쓸 만한 IndexedDB 구현체가 제 손에 있었다면, 오픈 소스로 공개하고 npm에 배포하고 PR을 받고 했을 겁니다. 지금 상태로는 그럴 이유를 딱히 못 찾겠습니다. 원샷 대신 투샷으로 만들면 더 나은 버전의 코드를 여러분 스스로 얻을 수 있습니다. 아니면 더 나은 원샷을 궁리해도 되고요. LevelDB나 Rust 등 원하는 무엇 위에든 만들 수 있습니다. 〈'작은' 오픈 소스의 운명〉에서 제가 하려던 이야기가 대략 이런 것이었습니다. 물론 "작은"의 정의는 날마다 커지는 것 같지만요.
제 기분이 어떠냐고요? 솔직히 좋지 않습니다. 저는 지난 1년 동안 AI를 전혀 쓰지 않고(오로지 제 빈약한 영장류 지능만으로) fake-indexeddb에 엄청난 시간을 쏟아부었습니다. 그 경험은 즐거웠고 후회하지도 않지만, 이런 실험은 제가 수년간 기울인 노력을 값싸게 만듭니다. 사물의 가치를 떨어뜨리죠. 우리 중 많은 이가 이 도구들을 반사적으로 거부하는 데는 이런 이유도 일부 있다고 생각합니다. 이 도구들이 동작한다면, 솔직히 모욕적이니까요.
그렇다고 저든 다른 누구든 LLM이 없어지기를 바란다고 없앨 수는 없다고 봅니다. LLM의 능력을 보면 앞으로 소프트웨어를 만드는 일의 핵심이 되리라는 점은 꽤 분명해 보입니다. 좋은 일일 수도, 나쁜 일일 수도 있습니다. 다만 이제 제게 LLM의 지배는 필연으로 보입니다. 그래도 너무 우울해하지 않으려 합니다. Matt Pocock, Simon Willison, Steve Yegge 같은 "AI 인플루언서"들을 지켜보면 엄청나게 즐거워하고 있으니까요. 제 옛 Edge 동료 Kyle Pflug가 최근에 이렇게 말했습니다.
AI 우선 개발 덕분에 누구나 웹에서 무언가를 만드는 일이 다시 즐겁고 충분히 가능해지고 있습니다. View Source를 알아볼 수 없게 된 이후로 줄곧 그리워하던 감각이고, 딱 알맞은 때에 찾아온 한 줄기 희망입니다.
요즘 애들이 무엇에 신이 났는지 이해해 보려는 중년의 고루한 사람으로서, 저도 동의할 수밖에 없습니다. 지금의 제게 바이브 코딩이 딱히 즐겁게 느껴지지는 않더라도, 다른 사람들이 왜 그렇게 좋아하는지는 알 것 같습니다. 엄청난 창작의 힘을 주고 진입 장벽을 극적으로 낮춰 주니까요. Simon Willison은 2029년까지 소규모 팀이 AI로 만든 프로덕션급 웹 브라우저를 보게 되리라고 예측합니다. 저라면 그 반대에 걸지는 않겠습니다.
'Web Frontend Developer' 카테고리의 다른 글
| [번역] 인터랙티브 요소란 무엇일까요? (0) | 2026.09.21 |
|---|---|
| [번역] 프로그레시브 이미지 렌더링의 현재와 잠재적 미래 (1) | 2026.08.09 |
| [번역] Fetch 스트림은 훌륭하지만, 업로드/다운로드 진행 상황 측정에는 적합하지 않습니다 (0) | 2026.08.09 |
| [번역] 컴포넌트가 WCAG를 준수할 수 있을까요? (0) | 2026.06.05 |
| [번역] 타입스크립트 성능 문제 해결하기 — 사례 연구 (0) | 2026.06.05 |