# 5. 설계(Architecture)

## 5.1 UML

### 5.1.1 Server Side UML

```mermaid
classDiagram
class IPermissionDescriptor {
    +canRead(path: string): boolean
    +canWrite(path: string): boolean
    +canCustom(path: string, options: any): boolean
    +toJSON(): any
}
<<Interface>> IPermissionDescriptor
IPermissionDescriptor <|.. AdminPermission
class AdminPermission {
    +canRead(): boolean
    +canWrite(): boolean
    +canCustom(): boolean
    +toJSON(): Inline
}
IPermissionDescriptor <|.. PermissionImpl
class PermissionImpl {
    +basePath: string
    +writable: boolean
    +toJSON(): Inline
    +canRead(path: string): boolean
    +canWrite(path: string): boolean
    +canCustom(_path: string, _options: any): boolean
}
SessionStore o-- UserSession
UserSession *-- IUser
class SessionStore~T~ {
    +sessions: Record<string, T>
    +get(id: string): T
    +set(id: string, value: T): void
    +delete(id: string): void
    +saveToFile(path: string): Promise<void>
    +loadFromFile(path: string): Promise<void>
}
class UserSession {
    +id: string
    +superuser: boolean
    +expiredAt: number
    +permissionSet: IPermissionDescriptor
}
<<Interface>> UserSession
IPermissionDescriptor <|.. IUser
class IUser {
    +id: string
    +expiredAt: number
    +basepath: string
    +joinPath(path: string): string
    +relativePath(path: string): string
    +setExpired(seconds: number): void
    +isExpired(): boolean
    +toJSON(): any
}
<<Interface>> IUser
IUser <|.. UserSessionImpl
class UserSessionImpl {
    +id: string
    +expiredAt: number
    +permissionSet: IPermissionDescriptor
    +basepath: string
    +toJSON(): UserSessionImplJSON
    +joinPath(path: string): string
    +relativePath(path: string): string
    +setExpired(seconds: number): void
    +isExpired(): boolean
    +canRead(path: string): boolean
    +canWrite(path: string): boolean
    +canCustom(path: string, options: any): boolean
}
```
```mermaid
classDiagram
class DocumentContent {
    +chunks: RPC.Chunk[]
    +tags: string[]
    +version: number
}
<<Interface>> DocumentContent
class DocReadWriter {
    +read(path: string): Promise<DocumentContent>
    +save(path: string, doc: DocumentContent): Promise<void>
}
<<Interface>> DocReadWriter
DocReadWriter <|.. MemoryDocReadWriterType
class MemoryDocReadWriterType {
    -store: Map<string, DocumentContent>
    +read(path: string): Promise<DocumentContent>
    +save(path: string, doc: DocumentContent): Promise<void>
    +clear(): void
}
DocReadWriter <|.. DocFileReadWriterType
class DocFileReadWriterType {
    +rw: IReadWriter
    +read(path: string): Promise<DocumentContent>
    +save(path: string, doc: DocumentContent): Promise<void>
}
DocumentContent ..> DocReadWriter
```
```mermaid
classDiagram
Router <|.. FileServeRouter
class FileServeRouter {
    +fn: Handler
    +match(path: string, _ctx: MatchContext): any
}
class MethodRouterBuilber {
    +handlers: MethodRouter
    +get(handler: Handler): this
    +post(handler: Handler): this
    +put(handler: Handler): this
    +delete(handler: Handler): this
    +build(): Handler
}
class Router~T~ {
    +match(path: string, ctx: MatchContext): T
}
<<Interface>> Router
Router <|.. TreeRouter
class TreeRouter~T~ {
    -staticNode: Record<string, TreeRouter<T>>
    -simpleParamNode?: SimpleParamNode<T>
    -regexParamNodes: Map<string, RegexNode<T>>
    -fallbackNode?: Router<T>
    -elem?: T
    -findRouter(path: string, ctx: MatchContext): [TreeRouter<T>, string]
    +match(path: string, ctx?: MatchContext): T
    +register(path: string, elem: T): this
    +registerRouter(path: string, router: Router<T>): void
    -setOrMerge(elem: RegElem<T>): void
    -registerPath(path: string, elem: RegElem<T>): void
    -singleRoute(p: string): SingleRouteOutput<T>
}
MethodRouter <-- MethodRouterBuilber: Create
Router <|.. MethodRouter
Router <|.. FsRouter
```
```mermaid
classDiagram
class ResponseBuilder {
    +status: Status
    +headers: Headers
    +body?: BodyInit
    +#response: Response
    +#resolved: boolean
    +resolved: boolean
    +setStatus(status: Status): this
    +setHeader(key: string, value: string): this
    +setHeaders(headers: Record<string, string>): this
    +setBody(body: BodyInit): this
    +setResponse(response: Response, resolved?: boolean): this
    +setCors(origin: string, credentials: boolean): this
    +setCorsMethods(methods: string[]): this
    +setContentType(contentType: string): this
    +setJson(json: unknown): this
    +redirect(location: string): this
    +build(): Response
}
class MatchContext
<<Interface>> MatchContext
Response <-- ResponseBuilder: Create
```
```mermaid
classDiagram
class ChunkMethodAction {
    +action: (doc: ActiveDocumentObject) => ChunkMethodHistory
    +checkConflict: (m: ChunkMethodHistory) => boolean
    +trySolveConflict: (m: ChunkMethodHistory) => boolean
}
<<Interface>> ChunkMethodAction
ChunkMethodAction <|.. ChunkCreateAction
class ChunkCreateAction {
    +params: ChunkCreateMethod
    +action(doc: ActiveDocumentObject): ChunkCreateHistory
    +checkConflict(m: ChunkMethodHistory): boolean
    +trySolveConflict(m: ChunkMethodHistory): boolean
}
ChunkMethodAction <|.. ChunkDeleteAction
class ChunkDeleteAction {
    +params: ChunkDeleteMethod
    +action(doc: ActiveDocumentObject): ChunkRemoveHistory
    +checkConflict(_m: ChunkMethodHistory): boolean
    +trySolveConflict(_m: ChunkMethodHistory): boolean
}
ChunkMethodAction <|.. ChunkModifyAction
class ChunkModifyAction {
    +params: ChunkModifyMethod
    +action(doc: ActiveDocumentObject): ChunkModifyHistory
    +checkConflict(m: ChunkMethodHistory): boolean
    +trySolveConflict(_m: ChunkMethodHistory): boolean
}
ChunkMethodAction <|.. ChunkMoveAction
class ChunkMoveAction {
    +params: ChunkMoveMethod
    +action(doc: ActiveDocumentObject): ChunkMoveHistory
    +checkConflict(_m: ChunkMethodHistory): boolean
    +trySolveConflict(_m: ChunkMethodHistory): boolean
}
```
```mermaid
classDiagram
class Participant {
    +id: string
    +user: IUser
    +send(data: string): void
    +sendNotification(notification: RPC.RPCNotification): void
    +responseWith(data: RPC.RPCResponse): void
    +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
    +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
    +close(): void
}
<<Interface>> Participant
Participant <|.. Connection
class Connection {
    +id: string
    +user: IUser
    +socket: WebSocket
    +sendNotification(notification: RPC.RPCNotification): void
    +responseWith(res: RPC.RPCResponse): void
    +send(data: string): void
    +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
    +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
    +close(code?: number, reason?: string): void
}
class ParticipantList {
    +connections: Map<string, Participant>
    +add(id: string, p: Participant): void
    +get(id: string): any
    +remove(id: string): void
    +unicast(id: string, message: string): void
    +unicastNotification(id: string, notification: RPC.RPCNotification): void
    +broadcastNotification(notification: RPC.RPCNotification): void
}
ParticipantList o-- Participant
```
```mermaid
classDiagram
class ISubscriptable {
    +join(participant: Participant): void
    +leave(participant: Participant): void
    +broadcastChunkMethod(method: ChunkNotificationParam, updatedAt: number, exclude?: Participant): void
    +participantsCount: number
    +participants: Participant[]
}
<<Interface>> ISubscriptable
ISubscriptable <|.. ActiveDocumentObject
class ActiveDocumentObject {
    -conns: RefCountSet<Participant>
    -disposeHandlers: (() => void)[]
    +history: DocHistory[]
    +maxHistory: number
    +docPath: string
    +chunks: RPC.Chunk[]
    +updatedAt: number
    +seq: number
    +#tags: string[]
    +tagsUpdatedAt: number
    +readWriter: DocReadWriter
    +dispose(): void
    +save(): Promise<void>
    +setTags(tags: string[]): void
    +tags: string[]
    +participantsCount: any
    +participants: Participant[]
    +join(conn: Participant): void
    +joined(conn: Participant): boolean
    +leave(conn: Participant): void
    +open(): Promise<void>
    +updateDocHistory(method: ChunkMethodHistory): void
    +broadcastChunkMethod(method: ChunkNotificationParam, updatedAt: number, exclude?: Participant): void
    +broadcastTagsNotification(exclude?: Participant): void
}
IDisposable <|.. ActiveDocumentObject
DocumentObject <|.. ActiveDocumentObject
class DocumentStore {
    +documents: Inline
    +open(conn: Participant, docPath: string): Promise<ActiveDocumentObject>
    +close(conn: Participant, docPath: string): void
    +closeAll(conn: Participant): void
}
DocumentStore o-- ActiveDocumentObject
```
```mermaid
classDiagram
class IDisposable {
    +dispose(): void
}
<<Interface>> IDisposable
IDisposable <|.. RefCountDisposable
class RefCountDisposable {
    -refCount: number
    -handlers: (() => void)[]
    +dispose(): void
    +disposeForced(): void
    +addRef(): void
    +addDisposeHandler(handler: () => void): void
}
class RefCountSet~T~ {
    -items: Map<T, RefCountDisposable>
    +add(item: T, disposeHandler?: () => void): void
    +addDisposeHandler(item: T, handler: () => void): boolean
    +delete(item: T): void
    +deleteForced(item: T): void
    +clear(): void
    +size: number
    +has(item: T): boolean
    +values(): IterableIterator<T>
}
RefCountDisposable --o RefCountSet~T~
```
```mermaid
classDiagram
Event <|-- FsWatcherEvent
class FsWatcherEvent {
    +paths: string[]
}
EventTarget <|-- FsWatcher
class FsWatcher {
    -path: string
    -watcher?: Deno.FsWatcher
    -filterFns: ((path: string, kind: FsWatchEventType) => boolean)[]
    +addFilter(fn: (path: string, kind: FsWatchEventType) => boolean): void
    +removeFilter(fn: (path: string, kind: FsWatchEventType) => boolean): boolean
    +startWatching(): void
    +stopWatching(): void
    +addEventListener(type: FsWatchEventType, handler: (e: FsWatcherEvent) => void): void
}
```
```mermaid
classDiagram
class IReadWriter {
    +read(path: string): Promise<string>
    +write(path: string, content: string): Promise<void>
}
<<Interface>> IReadWriter
class MemoryReadWriter {
    -data: Record<string, string>
    +read(path: string): Promise<string>
    +write(path: string, content: string): Promise<void>
}
IReadWriter <|.. MemoryReadWriter
IReadWriter <|.. AtomicReadWriter
class AtomicReadWriter {
    +read(path: string): Promise<string>
    +write(path: string, content: string): Promise<void>
}
IReadWriter <|.. WatchFilteredReadWriter
class WatchFilteredReadWriter {
    -raw: AtomicReadWriter
    -fsWatcher: FsWatcher
    +read(path: string): Promise<string>
    +write(path: string, content: string): Promise<void>
}
IReadWriter <|.. QueueReadWriter
class QueueReadWriter {
    -queue: Command[]
    +started: boolean
    -waitedResolve: () => void
    +delayCount: number
    +baseReadWriter: IReadWriter
    +read(path: string): Promise<string>
    +write(path: string, content: string): Promise<void>
    +save(path: string, content: string): void
    +startTimer(): void
    +flush(): Promise<void>
    +wait(): Promise<void>
}
```

### 5.1.2 Client Side UML

```mermaid
classDiagram
Error <|-- RPCErrorWrapper
class RPCErrorWrapper {
    +data?: unknown
    +code: RPCErrorCode
    +toJSON(): Inline
}
MessageEvent <|-- RPCNotificationEvent
class RPCNotificationEvent {
    +notification: ChunkNotification
}
```
```mermaid
classDiagram
class IDisposable {
    +dispose(): void
}
<<Interface>> IDisposable
class IDocumentViewModel {
    +docPath: string
    +writable: boolean
    +updateOnNotification(notification: RPCNotification): void
    +useChunks(): [IChunkViewModel[], ChunkListMutator]
    +useTags(): [string[], (tags: string[]) => Promise<void>]
    +useWritable(): [boolean, (writable: boolean) => Promise<void>]
}
<<Interface>> IDocumentViewModel
IDocumentViewModel <|.. DocumentViewModel
IDisposable <|.. DocumentViewModel
class DocumentViewModel {
    +type: "document"
    +docPath: string
    +chunks: ChunkListViewModel
    +tags: string[]
    +tagsUpdatedAt: number
    +manager: IRPCMessageManager
    +writable: boolean
    +updateOnNotification(notification: RPCNotification): void
    +useChunks(): [IChunkViewModel[], ChunkListMutator]
    +updateTags(tags: string[], updatedAt: number): void
    +useTags(): [string[], (tags: string[]) => Promise<void>]
    +useWritable(): [boolean, (writable: boolean) => Promise<void>]
}
ChunkListViewModel ..* DocumentViewModel
```
```mermaid
classDiagram
class IChunkListViewModel {
    +docPath: string
    +updateOnNotification(notification: RPCNotification): void
    +useChunks(): [IChunkViewModel[], ChunkListMutator]
}
<<Interface>> IChunkListViewModel

EventTarget <|-- ChunkListViewModel
IChunkListViewModel <|.. ChunkListViewModel
class ChunkListViewModel {
    +chunks: ChunkViewModel[]
    +history: ChunkListHistory
    +buffer: Inline
    +seq: number
    +updatedAt: number
    +docPath: string
    +manager: IRPCMessageManager
    +nextSeq: number
    +updateOnNotification(notification: RPCNotification): void
    -updateMark(updatedAt: number): void
    +apply(mutator: ChunkListStateMutator, updatedAt: number, seq: number, refresh?: boolean): void
    +refresh(chunks: Chunk[], updatedAt: number): void
    -refreshWith(chunkList: (Chunk | ChunkState)[], currentUpdatedAt: number, refresh?: boolean): void
    -resolveError(e: Error): void
    +useChunks(): [ChunkViewModel[], ChunkListMutator]
}

class ChunkListMutator {
    +add(i?: number, chunkContent?: ChunkContent): void
    +create(i?: number | undefined): void
    +addFromText(i: number, text: string): void
    +del(id: string): void
    +move(id: string, pos: number): void
}
<<interface>> ChunkListMutator
class ChunkListHistory {
    +history: ChunkListHistoryElem[]
    +limit: number
    -applyLast(mutator: ChunkListStateMutator, updatedAt: number): void
    +current: ChunkListState
    +currentUpdatedAt: number
    +revoke(): void
    +apply(mutator: ChunkListStateMutator, updatedAt: number): boolean
}

class ChunkListStateMutator
<<Interface>> ChunkListStateMutator
class ChunkListHistory {
    +history: ChunkListHistoryElem[]
    +limit: number
    -applyLast(mutator: ChunkListStateMutator, updatedAt: number): void
    +current: ChunkListState
    +currentUpdatedAt: number
    +revoke(): void
    +apply(mutator: ChunkListStateMutator, updatedAt: number): boolean
}
ChunkListHistory ..> "1" ChunkListStateMutator
ChunkListHistory ..> "1" ChunkListState
ChunkListViewModel *.. ChunkListHistory
ChunkListViewModel ..> "1" ChunkListStateMutator
IChunkListViewModel ..> "1" ChunkListMutator
ChunkViewModel ..* ChunkListViewModel
```
```mermaid
classDiagram
class ChunkListStateMutator
<<Interface>> ChunkListStateMutator
class ChunkListStateAddMutator
class ChunkListStateDeleteMutator
class ChunkListStateModifyMutator
class ChunkListStateMoveMutator
ChunkListStateMutator <|.. ChunkListStateAddMutator
ChunkListStateMutator <|.. ChunkListStateDeleteMutator
ChunkListStateMutator <|.. ChunkListStateModifyMutator
ChunkListStateMutator <|.. ChunkListStateMoveMutator
```

```mermaid
classDiagram
class ChunkMutator {
    +setType(t: ChunkType): void
    +setContent(s: string): void
}
<<Interface>> ChunkMutator
class IChunkViewModel {
    +parent: IChunkListViewModel
    +id: string
    +focus(): void
    +unfocus(): void
    +useFocus(): boolean
    +useChunk(): [Chunk, ChunkMutator]
    +setState(state: ChunkState): void
}
<<Interface>> IChunkViewModel
EventTarget <|-- ChunkViewModel
IChunkViewModel <|.. ChunkViewModel
class ChunkViewModel {
    +chunk: Chunk
    +updatedAt: number
    +focused: boolean
    +overwrite?: Chunk | undefined
    +parent: ChunkListViewModel
    +manager: IRPCMessageManager
    +id: string
    +focus(): void
    +unfocus(): void
    +useFocus(): boolean
    +content: ChunkContent
    +setState(state: ChunkState): void
    +useChunk(): [Chunk, ChunkMutator]
}
IChunkViewModel ..> "1" ChunkMutator
```

```mermaid
classDiagram
EventTarget <|.. IRPCMessageManager
class IRPCMessageManager {
    +opened: boolean
    +close(): void
    +sendNotification(notification: RPCNotification): void
    +addEventListener(name: "notification", listener: RPCMessageMessagerEventListener): void
    +removeEventListener(name: "notification", listener: RPCMessageMessagerEventListener): void
    +invokeMethod(m: RPCMessageBody): Promise<RPCResponse>
}
<<Interface>> IRPCMessageManager
EventTarget <|-- RPCMessageManager
IRPCMessageManager <|.. RPCMessageManager
class RPCMessageManager {
    -callbackList: Map<number, RPCCallback>
    -curId: number
    -ws?: WebSocket | undefined
    +opened: boolean
    +openedURL: string | undefined
    +open(url: string | URL, protocals?: string | undefined): Promise<void>
    +close(): void
    -genId(): number
    +genHeader(): Inline
    +send(message: RPCMethod): Promise<RPCResponse>
    +sendNotification(message: RPCNotification): void
    +addEventListener(type: "notification", callback: RPCMessageMessagerEventListener): void
    +removeEventListener(type: "notification", callback: RPCMessageMessagerEventListener): void
    +invokeMethod(m: RPCMessageBody): Promise<RPCResponse>
}
```
```mermaid
classDiagram

class FsDirEntry {
    +name: string
    +isDirectory: boolean
    +isFile: boolean
    +isSymlink: boolean
}
<<Interface>> FsDirEntry
class FsStatInfo {
    +isFile: boolean
    +isDirectory: boolean
    +isSymlink: boolean
    +size: number
    +mtime: Date | null
    +atime: Date | null
    +birthtime: Date | null
}
<<Interface>> FsStatInfo
FsStatInfo <|.. FsGetResult
class FsGetResult {
    +entries?: FsDirEntry[] | undefined
}
<<Interface>> FsGetResult
Event <|-- FileEvent
class FileEvent~T~ {
    +kind: T
    +path: string[]
}
class IFsEventMap {
    +modify: FileEvent<"modify">
    +create: FileEvent<"create">
    +remove: FileEvent<"remove">
}
<<Interface>> IFsEventMap
EventTarget <|.. IFsManager
class IFsManager {
    +getURL(filePath: string): URL
    +get(path: string): Promise<Response>
    +getStat(path: string): Promise<FsGetResult>
    +upload(filePath: string, data: BodyInit): Promise<number>
    +delete(filePath: string): Promise<number>
    +rename(oldPath: string, newPath: string): Promise<number>
    +mkdir(path: string): Promise<number>
    +addEventListener(type: T, listener: EventHandler~T~): void
    +removeEventListener(type: T, listener: EventHandler~T~): void
    +dispatchEvent(event: Event): boolean
}
<<Interface>> IFsManager
EventTarget <|-- FsManager
IFsManager <|.. FsManager
class FsManager {
    -manager: IRPCMessageManager
    -url: string
    +addEventListener(type: T, listener: EventHandler~T~): void
    +removeEventListener(type: T, listener: EventHandler~T~): void
    +getURL(filePath: string): URL
    -#fetchRequest(filePath: string | URL, init?: RequestInit | undefined): Promise<Response>
    +get(filePath: string): Promise<Response>
    +getStat(filePath: string): Promise<FsGetResult>
    +upload(filePath: string, data: BodyInit): Promise<number>
    +mkdir(filePath: string): Promise<number>
    +delete(filePath: string): Promise<number>
    +rename(oldPath: string, newPath: string): Promise<number>
}

FsGetResult ..> "*" FsDirEntry
IFsEventMap ..> "1" IFsManager
IFsManager ..> "1" FsGetResult
FsManager ..> "1" RPCMessageManager
FsManager ..> "1" FsGetResult
```

## 5.2 의사코드(Pseudo Code)

의사코드는 다음과 같이 진행된다.

### 5.2.1 서버 RPC 메세지 처리

클라이언트는 RPC를 진행하기 위해 웹소켓을 연결한다. 웹소켓의 주소 `/ws`에 
도달하기 위해서 먼저 라우팅이 진행이 된다. 라우팅은 `TreeRouter` 클래스에서
 진행된다.
```
Input: req Request
Output: Response
fn Route(req){
    for node in HandlerNodes {
        if(req.url prefix is matching){
            node.Handler[matching](req)
        }
    }
    response with 404 Not Found
}
``` 
마침내 엔드포인트에 도달하게 되면 웹소켓을 얻어내고 새로운 연결을 등록한다.
여기서 reqeust session으로 유저를 얻어내서 접속하는 것에 대해 주의하라.
```
Input: req Request
Output: Response
fn RPCHandleEndpoint(req){
    ws, res = upgradeWebSocket(req)
    user = getUserInfo(req.header)
    conn = new Connection(ws, user)    
    registerParticipant(conn)
    res
}
```
이제부터 `RPCMethod` 메세지를 받을 수 있다.
메세지가 오면 함수 `handleMessage` 가 실행이 호출된다.
```
Input: message on send
Output: response
fn Connection.handleMessage(msg: string){
    data = JSON.parse(msg)
    check format of data
    dispatch(data.method, data, this)
}
```

디스패치가 성공적으로 이루어지면 RPC 작업를 처리하는 함수에 도착한다.
청크 작업을 예로 들면 이렇다. 청크 작업에서는 권한을 확인하고
명령의 충돌을 히스토리를 비교하며 다시 적용하며 해결한다.
그리고 요구된 작업을 처리하고 문서의 `updatedAt`을 업데이트하고
문서 업데이트 사실을 이 문서를 보고 있던 참여자에게 전파한다.
```
Input: conn Connection
Input: m RPCChunkMethod
Output: response

fn ChunkOperation(conn, m){
    doc = DocStore.getDocument(m.docPath);
    updatedAt = m.updatedAt;
    action = getAction(m);
    if !conn.userpermissionSet.canDo(Action) {
        response with PermissionError;
    }
    
    appliedList = doc.history.filter(x => x.updatedAt > updatedAt)
    for m in appliedList {
        if action.checkConflict(m) {
            resolvedAction = action.tryResolveConflict(m)
            if resolvedAction == Fail {
                response with ConflictError
            }
            action = resolvedAction
        }
    }
    
    res = action.act(doc);
    doc.updateHistory(res);
    
    subscribers = doc.getSubscribers();
    subscribers.broadcastNotification(m, exclude = conn);
    response with res
}
```

문서 작업도 마찬가지로 이루어진다.

### 5.2.2 클라이언트의 메세지 처리 동기화

클라이언트에서는 다음과 같은 일이 일어난다.

먼저 `notification`을 받는다. 그러면 모든 `DocumentViewModel`에게 이벤트를 전달한다.
그리고 각각의 `DocumentViewModel`은 자기 문서에 일어난 일인지 확인하고 `ChunkListMutator`를 만들어서
문서에 적용한다.
```
Input: e RPCNotification
Input: this document view model

fn updateOnNotification(this,notification){
    { docPath, method, seq, updatedAt } = notification.params;
    if (docPath !== this.docPath) return;
    mutator =  ChunkListMutatorFactory.createFrom(method);
    this.apply(mutator, updatedAt, seq);
}
```

문서의 레디큐에 mutator를 집어넣고 `seq` 번호가 기다리는 것이면 실행하고 업데이트한다.

```
Input: mutator ChunkListMutator
Input: updatedAt Date
Input: seq number
Input: this Document View Model

//readyQueue is priority queue.
fn apply(this, mutator, updatedAt, seq){
    this.readyQueue.push({mutator, updatedAt, seq})

    if(this.readyQueue.lenght < some limit){
        while(!readyQueue.empty() &&
            this.readyQueue.top().seq === this.seq + 1)
        {
            mutator, updatedAt, seq = this.readyQueue.pop();
            mutator(this.chunks);
            this.seq = seq;
            this.updatedAt = updatedAt; 
        }
        this.dispatch(new Event("chunksChange"));
    }
    else {
        document.refresh();
    }
}
```

### 5.2.3 다른 작업들

다음은 청크 컴포넌트에 관한 의사코드이다.
```
module chunk {
  type mode = Read | Write

  struct Chunk {
    id: string
    content: string
    type: string
  }

  newChunk() {
    { id      = uuid()
    ; content = ""
    ; type    = ""
    }
  }

  chunkViewer(chunk : Chunk, focusedChunk : State<string>,
   deleteThis : () => void) : Component {
    var mode = Read

    var c = new Component(
      content { value = chunk.content }
      settypebutton
      editbutton
      deletebutton
    )

    when mode becomes Read { chunk.content = content }
    when mode becomes Write { focusedChunk = chunk.id }
    when focusedChunk is changed { mode = Read }

    when editbutton is clicked { mode = (mode = Read) ? Write : Read }
    when deletebutton is clicked { deleteThis() }
    when settypebutton is clicked { chunk.type = prompt() }

    return c
  }
}
```
다음은 검색 기능에 관한 의사코드이다.
```
module search {
  searchWord(chunks, word) {
      return doc.chunks.concat_map((s) => s.matchAll(word))
  }

  searchWordPrompt(chunks: Chunk.chunk list) {
    var word = prompt()
    var results = searchWord(chunks, word)

    var c = new Component(results)

    when result in results is selected {
      moveto(result.location)
      close()
    }
  }

  when Ctrl-F is pressed { searchWordPrompt() }
}
```
다음은 문서 컴포넌트에 관한 의사코드이다.
```
module document {
  struct Document {
    title: string
    path: Path
    tags: string set
    chunks: chunk.Chunk array
  }

  documentViewer(doc: document) : Component {
    var focusedChunk = null

    var c = new Component(
      taglist { value: tags }
      chunklist
    )

    delete(id) {
      i = doc.chunks.find((c) => c.id = id)
      doc.chunks.remove(i)
    }

    chunklist = doc.chunks.concat_map((c, i) =>
      [ divider(i), chuknViewer(c, focusedChunk, () => delete(c.id)) ])

    when divider(i) clicked { doc.chunks.insert(i, c) }
    when chunkViewer(c) is dropped on divider(i) { doc.chunks.move(c, i) }

    return c
  }
}
```
다음은 파일리스트 컴포넌트에 관한 의사코드이다.
```
module filelist {
  fileList(dir : Directory, open: (File) => void) : Component {
    var c = new Component(
      filelist
    )

    filelist = dir.files().map((f) => button(f))

    when button(f) is clicked {
      open(f)
    }

    return c
  }
}
```
다음은 설정에 관한 의사코드이다.
```
module settings {
  settings() : Component {
    var c = new Component(
      language = select("korean", "english")
      theme = select("light", "dark")
    )

    when language(l) is selected {
      global context.lang = l
    }

    when theme(t) is selected {
      global context.theme = t
    }

    return c
  }
}
```
다음은 프론트엔드를 요약하는 의사코드이다.
```
module frontend {
  main() : Component {
    var docv

    var open = (f) => {
      with doc = openfile(f) {
        docv = document.documentViewer(doc)
      }
    }

    var filelist = filelist.fileList(rootdir, open)

    var c = new Component(
      document = docv
      filelist = filelist
    )

    return c
  }
}
```

## 5.3 동기화 정책(Synchronization Policy)

동기화 정책은 이렇다. 기본적으로 상대문서의 갱신 시간과 나의 갱신 시간을 비교하고 상대문서가 최신이 아니면 거부한다. 메소드가 실행 순서에
무관하면 언제나 실행하고 갱신한다. 메소드가 순서에 따라 조정될 수 있으면 조정한다.

예를 들어, 다음과 같은 상황이 있다.

```mermaid
sequenceDiagram
    participant A as Alice
    participant S as Server
    participant B as Bob
    S ->> A: document.open
    S ->> B: document.open
    A ->> S: chunk.create
    B ->> S: chunk.create
    S -->> A: response
    S -->> B: conflict
    S -) B: chunk.update
```

처음에 Server에서 Alice와 Bob이 문서를 받아왔다. 그리고 Alice가 `chunk.create` 메소드를 보내고 Bob이
`chunk.create`메소드를 보냈다. 그러면 Server는 Alice가 먼저 도착했으므로 Alice의 메소드을 처리하고 최근 갱신 시간을
갱신한다. 그리고 Bob의 메소드를 처리할때 Bob의 문서의 최근 갱신 시간이 Server 문서의 최근 갱신 시간보다 작으므로 거부한다.

별개로 Alice의 갱신사실을 알리기 위해서 Bob에게 `chunk.update`를 보낸다.

다른 예로 다음과 같은 상황에서는 이렇다.

```mermaid
sequenceDiagram
    participant A as Alice
    participant S as Server
    participant B as Bob
    S ->> A: document.open
    A ->> S: chunk.create
    S ->> B: document.open
    S -->> A: response
    S -) B: chunk.update
    B ->> S: chunk.create
    S -->> B: response
    S -) A: chunk.update
```

여기서는 Bob의 문서가 최신이기 때문에(**문서 갱신 시간이 Server와 같기 떄문에**) 거부되지 않고 처리되는 것을 볼 수 있다.
그와 별개로 갱신 사실을 알리기 위해서 `chunk.update`가 Alice와 Bob에게 전달되는 것을 볼 수 있다.

메소드를 조정할 수 있으면 조정한다는 것은 인수를 바꾸어서 실행한다는 것이다. 예를 들어 다음의 예에 대해서 

```mermaid
sequenceDiagram
    participant A as Alice
    participant S as Server
    participant B as Bob
    S ->> A: document.open
    S ->> B: document.open
    A ->> S: chunk.create
    B ->> S: chunk.create
    S -->> A: response
    S -->> B: response
    S -) B: chunk.update
```

A는 1번째 위치에서 `chunk.create`에서 시도하고 B가 3번째 위치에서 `chunk.create`를 시도하면 A가 B보다 먼저 앞의 위치에서
삽입을 했으므로 B의 위치를 4번째로 조정하고 적용한다. 