본문으로 건너뛰기

Clepit

한눈에 보기

분류
개발자 플랫폼
웹사이트
clepit.com
콘솔
app.clepit.com
문서
clepit.com/en/docs
GraphQL API
api.clepit.com/graphql
실시간
ws.clepit.com
MCP 인터페이스
api.clepit.com/mcp
게시된 페이지
clepit.space

서식 있는 텍스트는 대개 HTML 덩어리로 데이터베이스에 들어갑니다. 다시 보여주는 것 외에 다른 일을 할 생각이 없다면 그것으로 충분합니다. 그러나 특정 고객이 언급된 모든 페이지를 찾고 싶거나, 같은 문서를 웹 페이지와 이메일과 모바일 앱에 함께 내보내고 싶거나, 두 사람이 동시에 편집해도 문단이 사라지지 않게 하고 싶어지면 사정이 달라집니다. 그때쯤이면 글과 서식이 엉켜 있고, 그 문서를 확실히 읽어낼 수 있는 것은 그것을 쓴 편집기뿐입니다.

Clepit은 그 둘을 떼어 놓습니다. 페이지는 블록의 목록이고, 블록은 각각 타입이 정해진 작은 객체이며, 문서 자체는 JSON입니다. 해석해야 할 마크업도 없고, 읽기 위해 편집기가 필요하지도 않습니다. 편집기가 홀로 설 수 있는 이유도 여기에 있습니다. @clepit/core는 MIT 라이선스로 npm에 공개되어 있으며 호스팅되는 쪽에 대해서는 아무것도 알지 못합니다. 반면 app.clepit.com의 작업 공간은, 같은 문서에 공동 작업자와 권한과 이력, 그리고 공개 웹 주소가 주어졌을 때의 모습입니다.

페이지는 블록의 목록입니다

블록은 네 개의 필드입니다. id, 타입, 그 타입이 정의하는 데이터, 그리고 블록에 적용된 조정값입니다. 문단의 데이터는 표의 데이터와 형태가 다르고, 어떤 형태를 기대해야 하는지 알려주는 것이 바로 타입입니다. 그래서 저장된 문서는 그저 해석되는 데 그치지 않고 검증될 수 있습니다. 페이지 전체는 시각과 버전, 그리고 순서대로 놓인 블록들이며, 눈으로 읽어 내려갈 만큼, 풀 리퀘스트에서 살펴볼 만큼 작습니다. 그 안에는 페이지가 어떻게 보여야 하는지에 관한 서술이 하나도 없습니다. 그것은 그리는 쪽의 몫이고, 덕분에 하나의 문서가 사본 세 벌 없이도 웹 페이지가 되고 이메일이 되고 모바일 화면이 됩니다.

브라우저 없이 문서를 그리기

렌더러는 브라우저에 직접 쓰지 않습니다. 얇은 층을 거쳐 그리며, 그 뒤에는 두 개의 받침이 있습니다. 하나는 실제 페이지 노드를 만들고 다른 하나는 문자열을 만들며, 같은 블록 코드가 양쪽에서 그대로 돌아갑니다. 게시된 페이지가 브라우저가 아예 없는 서버에서 그려지는 방식이 이것이고, 서버가 내놓는 결과가 편집기가 보여주었을 바로 그 문서인 이유도 이것입니다. 시간이 지나며 어긋나 버릴 두 번째 구현이 아닙니다. 문자열 받침은 새니타이저를 필수 인자로 받으며 기본값을 일부러 두지 않았습니다. 이 패키지가 평소 쓰는 새니타이저는 해석하기 위해 먼저 페이지를 만들기 때문에 그곳에서는 돌아갈 수 없고, 조용히 이스케이프로 물러서면 모든 문서의 본문 서식을 아무 말 없이 벗겨 냈을 것입니다. 그래서 이 빈틈은 기본값 속에 숨기지 않고 쓰이는 자리에 그대로 드러내 두었습니다.

읽는 데에는 JavaScript가 들지 않습니다

React 어댑터는 두 절반을 따로 내놓습니다. 서로 정반대의 것을 원하기 때문입니다. 콘텐츠 컴포넌트는 서버에서 돌면서 페이지가 만들어지는 동안 완성된 마크업을 내보내므로, 읽는 사람은 첫 응답에서 문서를 받습니다. 편집기 컴포넌트는 클라이언트 전용입니다. 편집기의 수명 주기를 떠맡는 쪽이고, 브라우저가 있기 전에는 떠맡을 것 자체가 없기 때문입니다. 그러므로 Clepit 문서를 읽는 데에는 JavaScript가 전혀 필요하지 않습니다. 런타임이 등장하는 곳은 쓰는 자리입니다.

페이지가 담을 수 있는 것

패키지에는 스물여섯 가지 블록이 함께 들어 있습니다. 대부분은 어떤 편집기에나 필요한 것들입니다. 제목, 문단, 목록, 체크리스트, 인용, 코드, 표, 이미지, 오디오, 비디오, 파일, 강조 상자, 구분선. 나머지가 있는 이유는, 문서를 쓰는 일이 글쓰기 도구가 대개 지나치는 것들을 요구하기 때문입니다. 문서의 제목들로부터 스스로 만들어지고 각각으로 연결되는 목차. 접히는 절과 단. 다른 페이지를 대신하는 카드. 손으로 그린 스케치. 그리고 어떤 문서를 지켜볼지를 저장할 뿐 그 활동의 사본은 담지 않는 활동 블록. 그래서 삽입된 날에 얼어붙지 않고 지금 일어나는 일을 계속 보여 줍니다. 블록 안의 서식은 굵게, 기울임, 밑줄, 취소선, 본문 코드, 형광펜, 링크 같은 평범한 표시에 더해 툴팁과 상태 표식과 멘션까지 아우릅니다. 패키지 자체에는 런타임 의존성이 하나도 없습니다.

수식과 다이어그램, 패키지 안에서 그립니다

그 블록들 가운데 둘이 LaTeX와 Mermaid를 그리며, 둘 다 일의 전부를 패키지 안에서 해냅니다. 원본을 해석하고, 배치를 계산하고, 결과를 그립니다. 그 아래에 렌더링 라이브러리는 없고, 수식이나 흐름도를 그림으로 바꾸려고 어떤 서비스를 부르지도 않습니다. 이는 의존성에 대한 취향이라기보다 문자열 받침에서 따라 나온 결과입니다. 브라우저만이 주는 무언가에, 혹은 네트워크에 손을 뻗는 블록은 게시된 페이지를 내보내는 서버에서 그려질 수 없고, 그러면 같은 페이지가 누가 요청했느냐에 따라 다르게 보이게 됩니다.

페이지 안에 사는 API 레퍼런스

OpenAPI 블록에 명세를 주십시오. 붙여 넣어도 되고 주소로 가리켜도 됩니다. 그러면 그 명세가 서술하는 것을 그립니다. 오퍼레이션들, 그 경로와 매개변수, 요청과 응답의 스키마, 그리고 인증 방식입니다. 오퍼레이션마다 cURL, TypeScript, Dart, Python으로 된 요청 예제도 만들어 냅니다. 명세에서 생성된 것이지, 갱신을 잊어버릴 작성자가 손으로 친 것이 아닙니다. 임베드 블록도 바깥 세계를 같은 눈으로 봅니다. 실제로 보여 줄 수 있는 몇 안 되는 서비스를 알아보고, 나머지 전부에 대해서는 평범한 링크를 실패가 아니라 온당한 결과로 다루며, 믿지 못할 주소는 아예 그리기를 거부합니다.

같은 문단에 있는 두 사람

지금 편집 중인 페이지는 서버의 단일 작업이 붙들고 있습니다. 페이지마다 하나이며, 모든 갱신이 순서대로 그곳을 지나갑니다. 동시 편집을 그래도 따져 볼 수 있는 이유가 여기에 있습니다. 앞선 작성자와 경쟁하는 두 번째 작성자가 아예 없습니다. 문서 자체가 CRDT이므로, 같은 문단에 타이핑하는 두 사람은 서로를 덮어쓰지 않고 병합되며, 뒤처진 클라이언트는 양쪽에 없는 것을 주고받아 따라잡습니다. 모든 갱신은 누구에게 전파되기 전에 먼저 선행 기록 로그에 덧붙여집니다. 그래서 문서에 있는 다른 사람들이 보는 것은 그저 중계된 것이 아니라 이미 지속적으로 기록된 것입니다. 권한은 화면이 아니라 서버가 강제합니다. 편집 권한 없이 들어온 상대는 읽기로 내려가고, 문서가 열려 있는 동안 세션이 그 권한을 주기적으로 다시 확인합니다. 그래서 회수된 접근 권한은 새로 고침을 기다리지 않고, 지금 타이핑하고 있는 사람에게 곧바로 미칩니다.

모든 쓰기는 하나의 문을 지납니다

문서는 그 안에 타이핑하는 사람도 바꿀 수 있고 API를 호출하는 프로그램도 바꿀 수 있으며, 예전에는 그 두 경로가 같은 페이지에 따로따로 쓸 수 있었습니다. 이제는 그럴 수 없습니다. API를 통한 쓰기는 살아 있는 문서를 붙들고 있는 바로 그 세션으로 넘겨져, 진행 중인 편집과 나란히 하나의 트랜잭션으로 적용됩니다. 그래서 사건의 순서는 하나뿐이며, 페이지의 내용을 두고 서로 다른 견해를 가진 작성자가 둘 있는 상황이 생기지 않습니다. 문서를 되쓸 때 블록 id는 그대로 보존됩니다. 댓글이 거기에 닻을 내리고 있기 때문이며, 새 id를 찍어 내는 재조정은 모든 댓글이 아무것도 아닌 곳을 가리키게 남겨 둘 것입니다. 그리고 만들어진 블록 집합이 이미 저장된 것과 똑같다면, 아무것도 쓰지 않습니다.

기계 키가 닿지 못하는 단 하나의 전송로

개인 API 키는 REST, GraphQL, GraphQL 구독 소켓, 그리고 MCP에서 통합니다. 협업 소켓에서는 통하지 않으며, 이는 의도한 것입니다. 협업으로 이루어진 모든 갱신에는 그것을 만든 사람의 도장이 찍히고, 그 도장들이 페이지의 이력에 기록되는 저작자가 됩니다. 기계 주체가 그곳에서 편집한다면 어떤 사람도 쓴 적 없는 저자를 적어 넣는 셈이고, 그것을 나중에 되돌린다는 것은 한 행을 지우는 일이 아니라 이력을 다시 쓰는 일입니다. 경계는 웹소켓과 HTTP 사이에 있지 않습니다. 그 전송로가 저작자가 붙은 이력을 쓰느냐에 있습니다. 이 규칙은 기억이 아니라 코드의 형태가 강제합니다. 키를 받아들이려면 다른 인증 호출로 일부러 옮겨 가야 하고, 어떤 전송로가 그렇게 하면 테스트가 실패하기 때문입니다.

자기 주소를 가진 작업 공간

모든 작업 공간은 만들어지는 순간부터 자기 서브도메인을 가진 테넌트이며, 테넌트는 요청이 도착한 주소에서 결정됩니다. 따라서 당신이 어느 테넌트에 있는지는 당신의 데이터가 하나라도 읽히기 전에 정해집니다. 나중에 덧붙이는, 누군가 잊어버릴 수도 있는 필터로 정해지지 않습니다. 그 아래에서는 데이터베이스가 행 수준 보안으로 경계를 직접 강제합니다. 요청마다 연결을 빌려 호출자의 신원을 그 위에 찍고, 연결이 반납될 때 풀이 그 상태를 지웁니다. 그래서 한 요청의 신원이 다음 요청의 질의로 새어 들어갈 수 없습니다.

도메인을 주장하는 것과 증명하는 것은 다릅니다

엔터프라이즈 요금제의 작업 공간은 자기 도메인에서 페이지를 내보낼 수 있습니다. 도메인을 주장하는 일과 그 도메인에서 내보내는 일은 일부러 별개의 단계로 나뉘어 있습니다. 도메인은 미검증 상태로 저장되고, DNS에 검증 레코드가 나타나기 전까지 리졸버는 그것을 완전히 무시합니다. 남의 회사 주소를 양식에 타이핑하는 일은 누구나 할 수 있습니다. 그것을 살아나게 하는 레코드를 게시할 수 있는 사람은 그 도메인을 실제로 통제하는 사람뿐입니다.

그 페이지가 거쳐 온 모든 판

Clepit은 하나의 현재 상태가 아니라 판본을 보관합니다. 사람들이 작업하는 동안 스냅샷이 자동으로 찍히되, 평범한 타이핑이 수백 개를 만들어 내지 않도록 조절됩니다. 십 분이 지나거나 열 개의 블록이 바뀌면, 둘 중 먼저 오는 쪽에서 새 스냅샷이 기록됩니다. 복원은 하나의 트랜잭션입니다. 옛 스냅샷을 적용하고, 블록을 맞추고, 복원 자체를 새로운 판본으로 기록합니다. 그래서 되돌아가는 일은 조용히 지워지지 않고 남습니다. 맞추는 과정은 원본의 블록 id를 일부러 보존합니다. 댓글이 블록에 닻을 내리고 있어서, 새 id로 페이지를 되돌리면 그 위의 모든 댓글이 닻을 잃기 때문입니다.

다시 찾아내기

검색은 페이지들의 투영 위에서 돌아가며, 질의는 손으로 SQL을 이어 붙이는 대신 Postgres 자체의 웹 검색 파서를 거칩니다. 그래서 따옴표든 빼기 기호든 마음대로 입력할 수 있고, 그중 무엇도 주입의 통로가 되지 않습니다. 다만 공유 작업 공간에서 정작 중요한 것은 권한 확인이 어디에 놓여 있느냐입니다. 검색은 페이지 테이블과 조인하고, 그 테이블의 행 수준 보안이 바로 그 조인에 적용됩니다. 그래서 결과는 이미 묻는 사람이 볼 수 있는 페이지로 한정되어 있습니다. 필터, 곧 페이지 트리의 한 갈래, 마지막으로 고친 사람, 마지막으로 편집된 시점은 그 위에 추가 조건으로 얹힙니다. 하나같이 범위를 좁힙니다. 넓힐 수 있는 것은 하나도 없습니다. 모두가 같은 확인 뒤에 놓여 있기 때문입니다.

게시는 얼리고, 공유는 얼리지 않습니다

이 둘은 서로 다른 일이고, Clepit은 일부러 다르게 다룹니다. 페이지를 게시하면 지금의 문서가 하나의 판본으로 얼어붙고, 페이지가 그것을 가리키게 되며, 공개됩니다. 방문자가 clepit.space에서, acme.clepit.space/handbook 같은 주소로 읽는 것은 바로 그 얼어붙은 판본이지, 그 뒤에 이루어진 편집이 아닙니다. 게시를 내리면 그 가리킴은 지워지지만 공개 주소는 남습니다. 그래서 나중에 다시 게시하면 같은 URL로 돌아오고, 그곳을 가리키던 링크를 전부 끊어 놓지 않습니다. 공유 링크는 그 반대입니다. 살아 있는 문서를 내보내므로, 받은 사람이 보는 내용은 페이지가 바뀌는 대로 함께 바뀝니다.

거두어들일 수 있는 링크

공유 링크는 취소할 수 있는 토큰이며, 만들 때 만료 시점을 줄 수도 있습니다. 저장되는 것은 토큰의 해시뿐입니다. 그래서 링크는 만들어질 때 한 번만 보이고, 그 뒤에는 데이터베이스에서 되살릴 수 없습니다. 우리도, 그 데이터에 닿은 누구도 마찬가지입니다. 이 링크가 주는 것은 읽기이지 댓글 달기가 아닙니다. 댓글에는 작성자가 필요한데, 링크를 쥔 사람은 작성자가 아니기 때문입니다.

자기 디렉터리에서 로그인하기

작업 공간은 인증을 자기 아이덴티티 제공자에게 맡길 수 있습니다. 비즈니스 요금제에서는 OpenID Connect, 엔터프라이즈에서는 SAML, 그 곁에 디렉터리 동기화를 위한 SCIM이 있습니다. SCIM은 Okta와 Entra가 실제로 다루는 사용자 리소스를 담당하며, 표준의 가장 평이한 해석에서 한 가지를 일부러 벗어납니다. 삭제는 구성원을 지우는 대신 비활성화합니다. 명세가 그것을 허용하고, 그러지 않는 쪽은 누군가를 그룹에서 빼냈다는 이유만으로 작업 공간의 내용을 파괴할 수 있는 디렉터리 동기화입니다.

그것과 대화하는 네 가지 방법

REST는 /v1 아래 마흔네 개의 오퍼레이션을 담당하며, 그것들을 서술하는 것은 라우트 자체에서 생성되는 OpenAPI 문서입니다. 옆에 따로 적어 두는 문서가 아닙니다. 지속적 통합이 그 산출물을 저장소에 커밋된 사본과 대조하므로, 등록을 건너뛴 라우트 변경이 조용히 들어올 수 없습니다. GraphQL은 애플리케이션 모델을 담당하고, 구독은 자체 소켓으로 실어 나릅니다. 실시간은 별도의 프로세스입니다. 그래서 게이트웨이를 다시 시작해도 요청 표면까지 함께 내려앉지 않습니다. 또한 방 단위가 아니라 소켓 단위로 인가합니다. 이벤트마다 테넌트 안의 모든 연결이 한 번의 묶음 확인으로 평가되고, 볼 자격이 있는 쪽만 받습니다. MCP는 같은 오퍼레이션을 AI 에이전트에게 도구로 내어 주며, 어떤 도구도 자신이 어느 작업 공간에서 움직이는지 정하는 데 호출자가 준 id를 믿지 않습니다. 쓰기는 화면이 쓰는 것과 같은 서비스를 지나므로, 권한 확인도 감사 기록도 같은 것입니다.

이런 분께 맞습니다

직접 통제할 수 있는 에디터가 필요한 팀, 그리고 구조화된 콘텐츠를 자사 제품에 넣으려는 개발자에게 맞습니다.

바로가기 Clepit: clepit.com