-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
110 changed files
with
6,799 additions
and
8,072 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
name: Lint and Unit Test | ||
|
||
on: | ||
push: | ||
branches: | ||
- "**" | ||
|
||
jobs: | ||
lint_and_unit_test: | ||
name: Lint and Unit Test | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
# Checkout the repository | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
# Install pnpm | ||
- name: Install pnpm | ||
uses: pnpm/action-setup@v4 | ||
with: | ||
version: 9 | ||
run_install: true | ||
|
||
# Set up Node.js | ||
- name: Set up Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: "20" | ||
cache: "pnpm" | ||
|
||
# Run build | ||
- name: Install dependencies and build packages | ||
run: | | ||
pnpm install --frozen-lockfile | ||
pnpm --filter @noctaCrdt build | ||
pnpm --filter server build | ||
# Run lint | ||
- name: Run Lint | ||
run: pnpm eslint . | ||
|
||
# Run Unit tests | ||
- name: Run Unit Tests | ||
run: pnpm test | ||
env: | ||
JWT_SECRET: ${{ secrets.JWT_SECRET }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,7 @@ | |
*/dist | ||
/build | ||
.DS_Store | ||
.env | ||
.env | ||
|
||
# Jest globalConfig file | ||
../globalConfig.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,129 +1,111 @@ | ||
import { LinkedList } from "./LinkedList"; | ||
import { NodeId, Node } from "./Node"; | ||
import { RemoteInsertOperation, RemoteDeleteOperation, SerializedProps } from "./Interfaces"; | ||
import { CharId, BlockId, NodeId } from "./NodeId"; | ||
import { Node, Char, Block } from "./Node"; | ||
import { RemoteDeleteOperation, RemoteInsertOperation, SerializedProps } from "./Interfaces"; | ||
|
||
export class CRDT { | ||
export class CRDT<T extends Node<NodeId>> { | ||
clock: number; | ||
client: number; | ||
textLinkedList: LinkedList; | ||
LinkedList: LinkedList<T>; | ||
|
||
constructor(client: number) { | ||
this.clock = 0; // ์ด CRDT์ ๋ ผ๋ฆฌ์ ์๊ฐ ์ค์ | ||
this.clock = 0; | ||
this.client = client; | ||
this.textLinkedList = new LinkedList(); | ||
this.LinkedList = new LinkedList<T>(); | ||
} | ||
|
||
/** | ||
* ๋ก์ปฌ์์ ์ฝ์ ์ฐ์ฐ์ ์ํํ๊ณ , ์๊ฒฉ์ ์ ํํ ์ฐ์ฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. | ||
* @param index ์ฝ์ ํ ์ธ๋ฑ์ค | ||
* @param value ์ฝ์ ํ ๊ฐ | ||
* @returns ์๊ฒฉ์ ์ ํํ ์ฝ์ ์ฐ์ฐ ๊ฐ์ฒด | ||
*/ | ||
localInsert(index: number, value: string): RemoteInsertOperation { | ||
const id = new NodeId((this.clock += 1), this.client); | ||
const remoteInsertion = this.textLinkedList.insertAtIndex(index, value, id); | ||
const id = | ||
this instanceof BlockCRDT | ||
? new CharId(this.clock + 1, this.client) | ||
: new BlockId(this.clock + 1, this.client); | ||
|
||
const remoteInsertion = this.LinkedList.insertAtIndex(index, value, id); | ||
this.clock += 1; | ||
return { node: remoteInsertion.node }; | ||
} | ||
|
||
/** | ||
* ๋ก์ปฌ์์ ์ญ์ ์ฐ์ฐ์ ์ํํ๊ณ , ์๊ฒฉ์ ์ ํํ ์ฐ์ฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. | ||
* @param index ์ญ์ ํ ์ธ๋ฑ์ค | ||
* @returns ์๊ฒฉ์ ์ ํํ ์ญ์ ์ฐ์ฐ ๊ฐ์ฒด | ||
*/ | ||
localDelete(index: number): RemoteDeleteOperation { | ||
// ์ ํจํ ์ธ๋ฑ์ค์ธ์ง ํ์ธ | ||
if (index < 0 || index >= this.textLinkedList.spread().length) { | ||
throw new Error(`์ ํจํ์ง ์์ ์ธ๋ฑ์ค์ ๋๋ค: ${index}`); | ||
if (index < 0 || index >= this.LinkedList.spread().length) { | ||
throw new Error(`Invalid index: ${index}`); | ||
} | ||
|
||
// ์ญ์ ํ ๋ ธ๋ ์ฐพ๊ธฐ | ||
const nodeToDelete = this.textLinkedList.findByIndex(index); | ||
const nodeToDelete = this.LinkedList.findByIndex(index); | ||
if (!nodeToDelete) { | ||
throw new Error(`์ญ์ ํ ๋ ธ๋๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ์ธ๋ฑ์ค: ${index}`); | ||
throw new Error(`Node not found at index: ${index}`); | ||
} | ||
|
||
// ์ญ์ ์ฐ์ฐ ๊ฐ์ฒด ์์ฑ | ||
const operation: RemoteDeleteOperation = { | ||
targetId: nodeToDelete.id, | ||
clock: this.clock + 1, | ||
}; | ||
|
||
// ๋ก์ปฌ ์ญ์ ์ํ | ||
this.textLinkedList.deleteNode(nodeToDelete.id); | ||
|
||
// ํด๋ก ์ ๋ฐ์ดํธ | ||
this.LinkedList.deleteNode(nodeToDelete.id); | ||
this.clock += 1; | ||
|
||
return operation; | ||
} | ||
|
||
/** | ||
* ์๊ฒฉ์์ ์ฝ์ ์ฐ์ฐ์ ์์ ํ์ ๋ ์ฒ๋ฆฌํฉ๋๋ค. | ||
* @param operation ์๊ฒฉ ์ฝ์ ์ฐ์ฐ ๊ฐ์ฒด | ||
*/ | ||
remoteInsert(operation: RemoteInsertOperation): void { | ||
const newNodeId = new NodeId(operation.node.id.clock, operation.node.id.client); | ||
const newNode = new Node(operation.node.value, newNodeId); | ||
const NodeIdClass = this instanceof BlockCRDT ? CharId : BlockId; | ||
const NodeClass = this instanceof BlockCRDT ? Char : Block; | ||
|
||
const newNodeId = new NodeIdClass(operation.node.id.clock, operation.node.id.client); | ||
const newNode = new NodeClass(operation.node.value, newNodeId) as T; | ||
newNode.next = operation.node.next; | ||
newNode.prev = operation.node.prev; | ||
this.textLinkedList.insertById(newNode); | ||
// ๋๊ธฐํ ๋ ผ๋ฆฌ์ ์๊ฐ | ||
|
||
this.LinkedList.insertById(newNode); | ||
|
||
if (this.clock <= newNode.id.clock) { | ||
this.clock = newNode.id.clock + 1; | ||
} | ||
} | ||
|
||
/** | ||
* ์๊ฒฉ์์ ์ญ์ ์ฐ์ฐ์ ์์ ํ์๋ ์ฒ๋ฆฌํฉ๋๋ค. | ||
* @param operation ์๊ฒฉ ์ญ์ ์ฐ์ฐ ๊ฐ์ฒด | ||
*/ | ||
remoteDelete(operation: RemoteDeleteOperation): void { | ||
const { targetId, clock } = operation; | ||
if (targetId) { | ||
this.textLinkedList.deleteNode(targetId); | ||
this.LinkedList.deleteNode(targetId); | ||
} | ||
// ๋๊ธฐํ ๋ ผ๋ฆฌ์ ์๊ฐ | ||
if (this.clock <= clock) { | ||
this.clock = clock + 1; | ||
} | ||
} | ||
|
||
/** | ||
* ํ์ฌ ํ ์คํธ๋ฅผ ๋ฌธ์์ด๋ก ๋ฐํํฉ๋๋ค. | ||
* @returns ํ์ฌ ํ ์คํธ | ||
*/ | ||
read(): string { | ||
return this.textLinkedList.stringify(); | ||
} | ||
|
||
/** | ||
* ํ์ฌ ํ ์คํธ๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํํฉ๋๋ค. | ||
* @returns ํ์ฌ ํ ์คํธ ๋ฐฐ์ด | ||
*/ | ||
spread(): string[] { | ||
return this.textLinkedList.spread(); | ||
return this.LinkedList.stringify(); | ||
} | ||
|
||
/** | ||
* textLinkedList๋ฅผ ๋ฐํํ๋ getter ๋ฉ์๋ | ||
* @returns LinkedList ์ธ์คํด์ค | ||
*/ | ||
public getTextLinkedList(): LinkedList { | ||
return this.textLinkedList; | ||
spread(): T[] { | ||
return this.LinkedList.spread(); | ||
} | ||
|
||
/** | ||
* CRDT์ ์ํ๋ฅผ ์ง๋ ฌํ ๊ฐ๋ฅํ ๊ฐ์ฒด๋ก ๋ฐํํฉ๋๋ค. | ||
* @returns ์ง๋ ฌํ ๊ฐ๋ฅํ CRDT ์ํ | ||
*/ | ||
serialize(): SerializedProps { | ||
serialize(): SerializedProps<T> { | ||
return { | ||
clock: this.clock, | ||
client: this.client, | ||
textLinkedList: { | ||
head: this.textLinkedList.head, | ||
nodeMap: this.textLinkedList.nodeMap, | ||
LinkedList: { | ||
head: this.LinkedList.head, | ||
nodeMap: this.LinkedList.nodeMap, | ||
}, | ||
}; | ||
} | ||
} | ||
|
||
export class EditorCRDT extends CRDT<Block> { | ||
currentBlock: Block | null; | ||
|
||
constructor(client: number) { | ||
super(client); | ||
this.currentBlock = null; | ||
} | ||
} | ||
|
||
export class BlockCRDT extends CRDT<Char> { | ||
currentCaret: number; | ||
|
||
constructor(client: number) { | ||
super(client); | ||
this.currentCaret = 0; | ||
} | ||
} |
Oops, something went wrong.