Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/site/src/generated/repo-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export const repoCatalog = {
"version": "1.0.0",
"description": "Headless JSON editing primitives guarded by Zod schemas.",
"license": "MIT",
"summary": "json-document는 Zod schema가 있는 JSON 문서를 편집하기 위한 headless document\nengine입니다.",
"summary": "Zod schema로 보호되는 JSON state를 읽고, 바꾸고, 선택하고, 복사하고,\n붙여넣고, 되돌리기 위한 headless document layer입니다.",
"guidance": null,
"publicExports": [
"ClipboardCopyError",
Expand Down
2 changes: 1 addition & 1 deletion docs/generated/repo-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@
"version": "1.0.0",
"description": "Headless JSON editing primitives guarded by Zod schemas.",
"license": "MIT",
"summary": "json-document는 Zod schema가 있는 JSON 문서를 편집하기 위한 headless document\nengine입니다.",
"summary": "Zod schema로 보호되는 JSON state를 읽고, 바꾸고, 선택하고, 복사하고,\n붙여넣고, 되돌리기 위한 headless document layer입니다.",
"guidance": null,
"publicExports": [
"ClipboardCopyError",
Expand Down
190 changes: 154 additions & 36 deletions packages/json-document/README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,46 @@
# json-document

json-document는 Zod schema가 있는 JSON 문서를 편집하기 위한 headless document
engine입니다.
Zod schema로 보호되는 JSON state를 읽고, 바꾸고, 선택하고, 복사하고,
붙여넣고, 되돌리기 위한 headless document layer입니다.

UI component, admin CRUD framework, app state manager가 아닙니다. json-document는
편집 툴들이 공유하는 schema-safe document layer를 제공합니다: JSON Pointer
주소, JSON Patch 변경, JSONPath 검색, schema validation, selection, clipboard
payload, undo/redo history, reasoned `can*` checks.
`json-document`는 UI component library, CRUD framework, app state manager가
아닙니다. Form, CMS block, kanban board, outliner, settings editor처럼 서로
다른 제품이 반복해서 구현하는 document editing core를 제공합니다.

공식 사이트와 데모: https://developer-1px.github.io/json-document/
```txt
schema -> document -> pointer/query -> can* -> change -> result
```

## 왜 json-document인가
- 공식 사이트: https://developer-1px.github.io/json-document/
- GitHub Wiki: https://github.com/developer-1px/json-document/wiki

폼, CMS block, kanban board, outliner, settings editor는 UI가 달라도 같은 일을 합니다. schema가 있는 JSON document를 읽고, 바꾸고, 선택하고, 복사하고, 붙여넣고, 되돌립니다.
## 왜 json-document인가

json-document는 이 공통 규칙을 UI component 밖으로 빼서 하나의 document engine으로 제공합니다.
JSON을 단순 data blob으로만 다루면 값 하나를 바꾸는 일은 쉽습니다. 하지만 실제
제품에서는 곧 더 많은 질문이 생깁니다.

```txt
schema -> document -> pointer/query -> can* -> change -> result
```
- 이 변경이 schema를 통과하는가?
- 실행 전에 버튼이나 command palette에서 가능 여부를 알 수 있는가?
- 실패하면 어떤 `code`, `pointer`, `violations`로 설명할 수 있는가?
- 선택된 항목을 삭제, 이동, 복제할 때 source는 어떻게 정해지는가?
- 여러 항목을 copy/cut/paste하면 array insertion target에서 어떻게 들어가는가?
- undo/redo는 value뿐 아니라 selection도 함께 복원하는가?
- React 밖에서도 같은 document behavior를 쓸 수 있는가?

처음 쓰는 사람은 내부 폴더를 몰라도 됩니다. 필요한 것은 schema, JSON value, Pointer, change, Result입니다.
`json-document`는 이 질문들을 UI 밖의 stable core로 옮깁니다. 앱은 rendering,
focus, keyboard, drag/drop, network sync를 계속 소유하고, `json-document`는
schema-safe JSON document semantics를 소유합니다.

## 설치

```sh
npm install @interactive-os/json-document zod
```

`zod`는 peer dependency입니다. React를 쓰는 앱만 `@interactive-os/json-document/react`를 import합니다.
`zod`는 peer dependency입니다. React 앱에서만
`@interactive-os/json-document/react`를 import합니다.

## 시작 예제
## 60초 시작

```ts
import { z } from "zod";
Expand All @@ -51,10 +61,24 @@ const doc = createJSONDocument(Card, {
selection: true,
});

const patch = [{ op: "replace", path: "/status", value: "doing" }] as const;
const canChange = doc.canReplace("/status", "doing");

if (doc.canPatch(patch).ok) {
doc.commit(patch, { label: "change status" });
if (canChange.ok) {
doc.replace("/status", "doing");
}
```

모든 실행 API는 성공 또는 실패 result를 반환합니다. UI는 같은 `can*` result를
사용해 button disabled, command availability, validation message를 만들 수
있습니다.

```ts
const result = doc.canReplace("/title", "");

if (!result.ok) {
result.code;
result.reason;
result.violations;
}
```

Expand All @@ -77,16 +101,110 @@ if (doc.canPatch(patch).ok) {
| undo, redo | `doc.undo()`, `doc.redo()`, `doc.history` |
| 위치별 schema 확인 | `doc.schema.at`, `doc.schema.kind`, `doc.schema.describe`, `doc.schema.accepts` |

## 1.0 public contract

1.x에서 고정되는 것은 export 이름만이 아닙니다. 사용자 코드가 의존하는 result
shape, error code, signature/call shape, selection/history semantics도 public
contract입니다.

- public import는 `@interactive-os/json-document`와
`@interactive-os/json-document/react`입니다.
- 실패 분기는 `reason` 문구가 아니라 stable `code`와 구조로 합니다.
- 기본값은 `strict: false`입니다. 실패는 throw가 아니라 result로 돌아옵니다.
- `doc.undo()`와 `doc.redo()`는 top-level command이며
`JSONCapabilityResult`를 반환합니다.
- `createJSONDocument`와 `useJSONDocument`는 trusted/untrusted initial overload를
유지합니다.
- `doc.insert(value)`, `doc.insert(target, value, options)`,
`doc.move(source, target)`, `doc.move(target)`,
`doc.paste(target, options)` call shape는 public API입니다.
- `commit(..., { selectionAfter })`는 patch와 final selection을 같은 history
entry에 기록합니다.

상세 API와 method reference는 GitHub Wiki에 있습니다.

- Core API Reference: https://github.com/developer-1px/json-document/wiki/Core-API-Reference
- JSONDocument Method Reference: https://github.com/developer-1px/json-document/wiki/JSONDocument-Method-Reference
- 1.0 Semantic Contract: https://github.com/developer-1px/json-document/wiki/1.0-Semantic-Contract

## 핵심 규칙

- Patch path와 source는 JSON Pointer입니다. `insert`와 `move` target은 exact Pointer 또는 `{ before }`, `{ after }`, `{ into }`를 받습니다.
- Patch path와 source는 JSON Pointer입니다.
- JSONPath는 값을 찾는 언어이며 직접 변경하지 않습니다.
- `doc.at(pointer)`는 raw value가 아니라 `ReadResult`를 반환합니다.
- `can*`는 boolean이 아니라 이유 있는 capability result입니다.
- `doc.duplicate`, `doc.cut`, `doc.paste`는 성공하면 즉시 적용됩니다. 성공 결과의 `applied`는 다시 `commit`하지 않습니다.
- `doc.duplicate`, `doc.cut`, `doc.paste`는 성공하면 즉시 적용됩니다.
- 성공 결과의 `applied` patch는 이미 적용된 record이므로 다시 `commit`하지 않습니다.
- Pointer 배열을 copy/cut하면 clipboard payload도 배열입니다.
- Multi-source clipboard payload를 array insertion target에 paste하면 기본적으로
item별 sibling insert가 됩니다.
- Tree semantics는 app-owned입니다. json-document는 JSON을 검증하고 mutate합니다.

## Selection, clipboard, history

Selection은 DOM focus가 아니라 JSON-safe document state입니다.

```ts
doc.selection?.selectRanges([
"/lists/0/cards/0",
"/lists/0/cards/1",
]);

doc.copy(doc.selection?.selectedPointers ?? []);
doc.paste("/lists/1/cards/-");
```

Clipboard는 browser clipboard가 아니라 document instance 안의 headless JSON
payload buffer입니다. Browser system clipboard는
`@interactive-os/json-document-clipboard-web` extension에서 조립합니다.

History는 사용자 의도 단위로 묶을 수 있습니다.

```ts
doc.history.transaction({ label: "bulk status change" }, () => {
doc.replace("/status", "doing");
doc.replace("/title", "In progress");
});

doc.undo();
doc.redo();
```

구조 편집 후 다음 selection을 command가 정확히 알고 있으면 `selectionAfter`를
명시합니다.

```ts
doc.commit([
{ op: "replace", path: "/status", value: "done" },
], {
label: "complete card",
selectionAfter: "/status",
});
```

## Placement targets

삽입/이동 위치를 이미 알고 있으면 `/items/-`나 `/lists/1/cards/-` 같은 exact
Pointer를 그대로 넘깁니다.

```ts
doc.insert("/items/-", item);
doc.move("/items/0", "/items/2");
```

Array container 안에 append할 때는 `{ into }`, array item 기준으로 배치할 때는
`{ before }`, `{ after }`를 사용합니다. 기존 값을 바꾸는 작업은 `replace`이고,
document clipboard paste만 `{ replace: pointer }`를 추가로 지원합니다.

```ts
doc.insert({ into: "/items" }, item);
doc.move("/items/0", { after: "/items/2" });
doc.paste({ replace: "/items/0" });
```

삽입 위치를 직접 지정하려면 placement object를 만들지 말고 pointer 문자열을
그대로 넘깁니다.

## React — `useJSONDocument`

```tsx
Expand Down Expand Up @@ -124,23 +242,12 @@ export function App() {
}
```

## 클립보드

Core clipboard는 `navigator.clipboard`가 아니라 headless JSON payload buffer입니다. Browser system clipboard는 `@interactive-os/json-document-clipboard-web` extension에서 조립합니다.

```ts
const copied = doc.copy(["/lists/0/cards/0"]);

if (copied.ok) {
doc.paste("/lists/1/cards/-");
}
```

삽입/이동 위치를 이미 알고 있으면 `/items/-`나 `/lists/1/cards/-` 같은 exact Pointer를 그대로 넘깁니다. Array container 안에 append할 때는 `{ into: "/items" }`, array item 기준으로 배치할 때는 `{ before: "/items/0" }`, `{ after: "/items/0" }`를 사용합니다. Object member 추가는 `/record/key` 같은 exact Pointer를 씁니다. 기존 값을 바꿀 때는 `doc.replace(...)`를 쓰고, document clipboard paste만 `{ replace: pointer }`를 추가로 지원합니다.
Root package는 React-free입니다. React hook은 별도 entrypoint에만 있습니다.

## Official extensions

공식 extension은 core에 plugin 등록하지 않고 public `JSONDocument` surface를 함수로 조립합니다.
공식 extension은 core에 plugin 등록하지 않고 public `JSONDocument` surface를
함수로 조립합니다.

```ts
import { createCollection } from "@interactive-os/json-document-collection";
Expand All @@ -162,7 +269,10 @@ import { createComments } from "@interactive-os/json-document-comments";
import { createWebClipboard } from "@interactive-os/json-document-clipboard-web";
```

공식 package는 현재 `packages/*`에 있는 extension만 뜻합니다. `labs/extensions/*`는 후보이며 public API로 약속하지 않습니다.
공식 package는 현재 `packages/*`에 있는 extension만 뜻합니다.
`labs/extensions/*`는 후보이며 public API로 약속하지 않습니다.

Extension guide: https://github.com/developer-1px/json-document/wiki/Labs-and-Extensions

## 순수 core

Expand Down Expand Up @@ -204,3 +314,11 @@ const body = JSON.stringify(operations);

body satisfies string;
```

## 문서

- Overview: https://developer-1px.github.io/json-document/docs
- Tutorial: https://developer-1px.github.io/json-document/docs/tutorial
- API reference: https://developer-1px.github.io/json-document/docs/api
- Recipes: https://developer-1px.github.io/json-document/docs/recipes
- Wiki: https://github.com/developer-1px/json-document/wiki