Compare commits

..

No commits in common. "ef10264e8a1e2c5a93ba25754f45bc2b3207101c" and "f0c67b8bc4a142181f6598558a28f5e1499b8b79" have entirely different histories.

2 changed files with 33 additions and 274 deletions

View File

@ -6,31 +6,22 @@
```mermaid ```mermaid
classDiagram classDiagram
class IPermissionDescriptor { class PermissionDescriptor {
+canRead(path: string): boolean +canRead(path: string): boolean
+canWrite(path: string): boolean +canWrite(path: string): boolean
+canCustom(path: string, options: any): boolean +canCustom(path: string, options: any): boolean
+toJSON(): any
} }
<<Interface>> IPermissionDescriptor <<Interface>> PermissionDescriptor
IPermissionDescriptor <|.. AdminPermission PermissionDescriptor <|.. PermissionImpl
class AdminPermission {
+canRead(): boolean
+canWrite(): boolean
+canCustom(): boolean
+toJSON(): Inline
}
IPermissionDescriptor <|.. PermissionImpl
class PermissionImpl { class PermissionImpl {
+basePath: string +basePath: string
+writable: boolean +writable: boolean
+toJSON(): Inline
+canRead(path: string): boolean +canRead(path: string): boolean
+canWrite(path: string): boolean +canWrite(path: string): boolean
+canCustom(_path: string, _options: any): boolean +canCustom(_path: string, _options: any): boolean
} }
SessionStore o-- UserSession SessionStore o-- UserSession
UserSession *-- IUser UserSession *-- PermissionDescriptor
class SessionStore~T~ { class SessionStore~T~ {
+sessions: Record<string, T> +sessions: Record<string, T>
+get(id: string): T +get(id: string): T
@ -43,64 +34,9 @@ class UserSession {
+id: string +id: string
+superuser: boolean +superuser: boolean
+expiredAt: number +expiredAt: number
+permissionSet: IPermissionDescriptor +permissionSet: PermissionDescriptor
} }
<<Interface>> UserSession <<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 ```mermaid
classDiagram classDiagram
@ -144,20 +80,11 @@ Router <|.. FsRouter
classDiagram classDiagram
class ResponseBuilder { class ResponseBuilder {
+status: Status +status: Status
+headers: Headers +headers: Record<string, string>
+body?: BodyInit +body?: BodyInit
+#response: Response
+#resolved: boolean
+resolved: boolean
+setStatus(status: Status): this +setStatus(status: Status): this
+setHeader(key: string, value: string): this +setHeader(key: string, value: string): this
+setHeaders(headers: Record<string, string>): this
+setBody(body: BodyInit): 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 +redirect(location: string): this
+build(): Response +build(): Response
} }
@ -206,10 +133,8 @@ class ChunkMoveAction {
classDiagram classDiagram
class Participant { class Participant {
+id: string +id: string
+user: IUser +user: UserSession
+send(data: string): void +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 +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
+removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
+close(): void +close(): void
@ -218,10 +143,8 @@ class Participant {
Participant <|.. Connection Participant <|.. Connection
class Connection { class Connection {
+id: string +id: string
+user: IUser +user: UserSession
+socket: WebSocket +socket: WebSocket
+sendNotification(notification: RPC.RPCNotification): void
+responseWith(res: RPC.RPCResponse): void
+send(data: string): void +send(data: string): void
+addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
+removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
@ -233,50 +156,33 @@ class ParticipantList {
+get(id: string): any +get(id: string): any
+remove(id: string): void +remove(id: string): void
+unicast(id: string, message: string): void +unicast(id: string, message: string): void
+unicastNotification(id: string, notification: RPC.RPCNotification): void +broadcast(message: string): void
+broadcastNotification(notification: RPC.RPCNotification): void
} }
ParticipantList o-- Participant ParticipantList o-- Participant
``` ```
```mermaid ```mermaid
classDiagram classDiagram
class ISubscriptable { DocumentObject <|.. FileDocumentObject
+join(participant: Participant): void class FileDocumentObject {
+leave(participant: Participant): void +docPath: string
+broadcastChunkMethod(method: ChunkNotificationParam, updatedAt: number, exclude?: Participant): void +chunks: Chunk[]
+participantsCount: number +tags: string[]
+participants: Participant[] +updatedAt: number
+tagsUpdatedAt: number
+open(): Promise<void>
+parse(content: unknown[]): void
+save(): Promise<void>
} }
<<Interface>> ISubscriptable FileDocumentObject <|-- ActiveDocumentObject
ISubscriptable <|.. ActiveDocumentObject
class ActiveDocumentObject { class ActiveDocumentObject {
-conns: RefCountSet<Participant> +conns: Set<Participant>
-disposeHandlers: (() => void)[]
+history: DocHistory[] +history: DocHistory[]
+maxHistory: number +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 +join(conn: Participant): void
+joined(conn: Participant): boolean
+leave(conn: Participant): void +leave(conn: Participant): void
+open(): Promise<void>
+updateDocHistory(method: ChunkMethodHistory): void +updateDocHistory(method: ChunkMethodHistory): void
+broadcastChunkMethod(method: ChunkNotificationParam, updatedAt: number, exclude?: Participant): void +broadcastMethod(method: ChunkMethod, updatedAt: number, exclude?: Participant): void
+broadcastTagsNotification(exclude?: Participant): void
} }
IDisposable <|.. ActiveDocumentObject
DocumentObject <|.. ActiveDocumentObject
class DocumentStore { class DocumentStore {
+documents: Inline +documents: Inline
+open(conn: Participant, docPath: string): Promise<ActiveDocumentObject> +open(conn: Participant, docPath: string): Promise<ActiveDocumentObject>
@ -287,91 +193,16 @@ DocumentStore o-- ActiveDocumentObject
``` ```
```mermaid ```mermaid
classDiagram classDiagram
class IDisposable { class IFsWatcher{
+dispose(): void addEventListener(): void
onNofity(e: FileWatchEvent): void
} }
<<Interface>> IDisposable <<interface>> IFsWatcher
IDisposable <|.. RefCountDisposable class FsWatcherImpl{
class RefCountDisposable { onNotify(e: FileWatchEvent): void
-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 ### 5.1.2 Client Side UML
```mermaid ```mermaid
@ -777,7 +608,6 @@ fn apply(this, mutator, updatedAt, seq){
### 5.2.3 다른 작업들 ### 5.2.3 다른 작업들
다음은 청크 컴포넌트에 관한 의사코드이다.
``` ```
module chunk { module chunk {
type mode = Read | Write type mode = Read | Write
@ -818,7 +648,7 @@ module chunk {
} }
} }
``` ```
다음은 검색 기능에 관한 의사코드이다.
``` ```
module search { module search {
searchWord(chunks, word) { searchWord(chunks, word) {
@ -840,7 +670,7 @@ module search {
when Ctrl-F is pressed { searchWordPrompt() } when Ctrl-F is pressed { searchWordPrompt() }
} }
``` ```
다음은 문서 컴포넌트에 관한 의사코드이다.
``` ```
module document { module document {
struct Document { struct Document {
@ -873,7 +703,7 @@ module document {
} }
} }
``` ```
다음은 파일리스트 컴포넌트에 관한 의사코드이다.
``` ```
module filelist { module filelist {
fileList(dir : Directory, open: (File) => void) : Component { fileList(dir : Directory, open: (File) => void) : Component {
@ -891,7 +721,7 @@ module filelist {
} }
} }
``` ```
다음은 설정에 관한 의사코드이다.
``` ```
module settings { module settings {
settings() : Component { settings() : Component {
@ -912,7 +742,6 @@ module settings {
} }
} }
``` ```
다음은 프론트엔드를 요약하는 의사코드이다.
``` ```
module frontend { module frontend {
main() : Component { main() : Component {
@ -935,69 +764,3 @@ module frontend {
} }
} }
``` ```
## 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번째로 조정하고 적용한다.

View File

@ -47,10 +47,6 @@
1. [Server Side UML](./architecture.md#511-server-side-uml) 1. [Server Side UML](./architecture.md#511-server-side-uml)
2. [Client Side UML](./architecture.md#512-client-side-uml) 2. [Client Side UML](./architecture.md#512-client-side-uml)
2. [의사코드(Pseudo Code)](./architecture.md#52-의사코드pseudo-code) 2. [의사코드(Pseudo Code)](./architecture.md#52-의사코드pseudo-code)
1. [서버 RPC 메세지 처리](./architecture.md#521-서버-rpc-메세지-처리)
2. [클라이언트의 메세지 처리 동기화](./architecture.md#522-클라이언트의-메세지-처리-동기화)
3. [다른 작업들](./architecture.md#523-다른-작업들)
3. [동기화 정책(Synchronization Policy)](./architecture.md#53-동기화-정책synchronization-policy)
6. [시험(Testing)](./testing.md) 6. [시험(Testing)](./testing.md)
1. [유닛 테스트(Unit test)](./testing.md#61-유닛-테스트unit-test) 1. [유닛 테스트(Unit test)](./testing.md#61-유닛-테스트unit-test)
2. [기능 테스트(Functional Test)](./testing.md#62-기능-테스트functional-test) 2. [기능 테스트(Functional Test)](./testing.md#62-기능-테스트functional-test)