jerry 3 years ago
commit
ec3f317ca9
65 changed files with 2575 additions and 0 deletions
  1. 95 0
      .gitignore
  2. 6 0
      lerna.json
  3. 17 0
      package.json
  4. 5 0
      packages/ot-demo/.babelrc
  5. 11 0
      packages/ot-demo/.editorconfig
  6. 9 0
      packages/ot-demo/.eslintignore
  7. 63 0
      packages/ot-demo/.eslintrc.js
  8. 95 0
      packages/ot-demo/.gitignore
  9. 1 0
      packages/ot-demo/README.md
  10. 100 0
      packages/ot-demo/build/webpack.config.js
  11. 47 0
      packages/ot-demo/build/webpack.dev.js
  12. 41 0
      packages/ot-demo/build/webpack.prod.js
  13. 11 0
      packages/ot-demo/index.html
  14. 6 0
      packages/ot-demo/jest.config.js
  15. 63 0
      packages/ot-demo/package.json
  16. 18 0
      packages/ot-demo/server.js
  17. 34 0
      packages/ot-demo/src/basics/conflict.ts
  18. 11 0
      packages/ot-demo/src/client/DomClient.ts
  19. 1 0
      packages/ot-demo/src/index.ts
  20. 6 0
      packages/ot-demo/src/internal.ts
  21. 74 0
      packages/ot-demo/src/operation/InsertColumnOperation.ts
  22. 78 0
      packages/ot-demo/src/operation/InsertRowOperation.ts
  23. 79 0
      packages/ot-demo/src/operation/RemoveColumnOperation.ts
  24. 79 0
      packages/ot-demo/src/operation/RemoveRowOperation.ts
  25. 8 0
      packages/ot-demo/src/server/DomeServer.ts
  26. 23 0
      packages/ot-demo/tsconfig.json
  27. 5 0
      packages/ot-js/.babelrc
  28. 11 0
      packages/ot-js/.editorconfig
  29. 9 0
      packages/ot-js/.eslintignore
  30. 64 0
      packages/ot-js/.eslintrc.js
  31. 95 0
      packages/ot-js/.gitignore
  32. 1 0
      packages/ot-js/README.md
  33. 100 0
      packages/ot-js/build/webpack.config.js
  34. 47 0
      packages/ot-js/build/webpack.dev.js
  35. 41 0
      packages/ot-js/build/webpack.prod.js
  36. 11 0
      packages/ot-js/index.html
  37. 6 0
      packages/ot-js/jest.config.js
  38. 61 0
      packages/ot-js/package.json
  39. 62 0
      packages/ot-js/src/basics/Command.ts
  40. 58 0
      packages/ot-js/src/basics/Conflict.ts
  41. 43 0
      packages/ot-js/src/basics/DataBuffer.ts
  42. 104 0
      packages/ot-js/src/basics/Deserialize.ts
  43. 15 0
      packages/ot-js/src/basics/Operation.ts
  44. 49 0
      packages/ot-js/src/basics/Transform.ts
  45. 33 0
      packages/ot-js/src/client/AwaitingConfirm.ts
  46. 38 0
      packages/ot-js/src/client/AwaitingWithBuffer.ts
  47. 57 0
      packages/ot-js/src/client/Client.ts
  48. 72 0
      packages/ot-js/src/client/ClientSocket.ts
  49. 27 0
      packages/ot-js/src/client/Synchronized.ts
  50. 123 0
      packages/ot-js/src/client/UndoManager.ts
  51. 129 0
      packages/ot-js/src/client/UndoManagerObserver.ts
  52. 6 0
      packages/ot-js/src/config/ClientSocketConfig.ts
  53. 1 0
      packages/ot-js/src/index.ts
  54. 41 0
      packages/ot-js/src/internal.ts
  55. 45 0
      packages/ot-js/src/library/ArrayList.ts
  56. 33 0
      packages/ot-js/src/server/Server.ts
  57. 49 0
      packages/ot-js/src/server/ServerSocket.ts
  58. 3 0
      packages/ot-js/src/server/User.ts
  59. 37 0
      packages/ot-js/src/typed/GlobalType.ts
  60. 3 0
      packages/ot-js/src/utils/ArrayUtils.ts
  61. 85 0
      packages/ot-js/src/utils/TransformUtils.ts
  62. 3 0
      packages/ot-js/test/bootstrap.js
  63. 1 0
      packages/ot-js/test/bootstrap.js.map
  64. 3 0
      packages/ot-js/test/bootstrap.ts
  65. 23 0
      packages/ot-js/tsconfig.json

+ 95 - 0
.gitignore

@@ -0,0 +1,95 @@
+#ide
+*.DS_Store
+.idea
+.vscode
+
+#npm
+package-lock.json
+
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+#c++
+CMakeFiles
+cmake_install.cmake
+CMakeCache.txt
+Makefile
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented library generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages/
+
+# C++ build debug directories
+cmake-build-debug
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL snapshot
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# next.js build output
+.next
+
+#lerna-changelog
+.changelog
+
+#webpack build
+.dist/
+
+#editor workspace file
+.idea/
+
+
+.DS_Store
+
+.history
+examples

+ 6 - 0
lerna.json

@@ -0,0 +1,6 @@
+{
+  "packages": [
+    "packages/*"
+  ],
+  "version": "1.0.0"
+}

+ 17 - 0
package.json

@@ -0,0 +1,17 @@
+{
+  "name": "x-draw-framework",
+  "version": "1.0.0",
+  "description": "x-draw-framework app package",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "gitee"
+  },
+  "keywords": [],
+  "author": "jerry",
+  "devDependencies": {
+    "lerna": "^4.0.0"
+  },
+  "dependencies": {}
+}

+ 5 - 0
packages/ot-demo/.babelrc

@@ -0,0 +1,5 @@
+{
+    "presets": [
+        "@babel/env"
+    ]
+}

+ 11 - 0
packages/ot-demo/.editorconfig

@@ -0,0 +1,11 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = crlf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+max_line_length = 120
+tab_width = 4
+trim_trailing_whitespace = true

+ 9 - 0
packages/ot-demo/.eslintignore

@@ -0,0 +1,9 @@
+dist
+sakura
+
+.eslintrc.js
+webpack.config.js
+webpack.dev.js
+webpack.prod.js
+package.json
+tsconfig.json

+ 63 - 0
packages/ot-demo/.eslintrc.js

@@ -0,0 +1,63 @@
+const path = require('path');
+
+const resolve = (url) => path.resolve(__dirname, url);
+
+module.exports = {
+    "extends": [
+        'airbnb-base',
+        'airbnb-typescript/base'
+    ],
+    "env": {
+        "browser": true,
+        "es2021": true
+    },
+    "rules": {
+        "no-cond-assign": "off",
+        "prefer-regex-literals": "off",
+        "@typescript-eslint/no-empty-function": "off",
+        "no-restricted-globals": "off",
+        "prefer-object-spread": "off",
+        "guard-for-in":"off",
+        "no-multi-assign": "off",
+        "no-lonely-if": "off",
+        "@typescript-eslint/lines-between-class-members": "off",
+        "prefer-exponentiation-operator":"off",
+        "no-restricted-properties": "off",
+        "function-paren-newline": "off",
+        "no-labels": "off",
+        "@typescript-eslint/no-shadow": "off",
+        "no-nested-ternary": "off",
+        "no-debugger": "off",
+        "no-mixed-operators": "off",
+        "prefer-destructuring": "off",
+        "no-console": "off",
+        "import/export": "off",
+        "@typescript-eslint/quotes": "off",
+        "prefer-const": "off",
+        'linebreak-style': ["off", "windows"],
+        "prefer-rest-params": "off",
+        "max-classes-per-file": "off",
+        "max-len": "off",
+        "new-parens": "off",
+        "no-continue": "off",
+        "no-plusplus": "off",
+        "import/prefer-default-export": "off",
+        "@typescript-eslint/naming-convention": "off",
+        "@typescript-eslint/no-unused-vars": "off",
+        "@typescript-eslint/no-use-before-define": "off",
+        "import/no-cycle": "off",
+        "no-bitwise": 'off',
+        "default-case": "off",
+        "no-param-reassign": "off",
+        "class-methods-use-this": "off",
+        "consistent-return": "off",
+        "no-underscore-dangle": "off",
+        "no-restricted-syntax": "off",
+    },
+    "parserOptions": {
+        "warnOnUnsupportedTypeScriptVersion": false,
+        "ecmaVersion": 12,
+        "sourceType": "module",
+        "project": resolve("tsconfig.json")
+    }
+}

+ 95 - 0
packages/ot-demo/.gitignore

@@ -0,0 +1,95 @@
+#ide
+*.DS_Store
+.idea
+.vscode
+
+#npm
+package-lock.json
+
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+#c++
+CMakeFiles
+cmake_install.cmake
+CMakeCache.txt
+Makefile
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented library generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages/
+
+# C++ build debug directories
+cmake-build-debug
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL snapshot
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# next.js build output
+.next
+
+#lerna-changelog
+.changelog
+
+#webpack build
+.dist/
+
+#editor workspace file
+.idea/
+
+
+.DS_Store
+
+.history
+examples

+ 1 - 0
packages/ot-demo/README.md

@@ -0,0 +1 @@
+### OperationalTransformation

+ 100 - 0
packages/ot-demo/build/webpack.config.js

@@ -0,0 +1,100 @@
+const path = require('path');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+const resolve = (url) => path.resolve(__dirname, "..", url);
+
+module.exports = {
+    entry: {
+        'ot-demo': resolve("src/index.ts"),
+    },
+    module: {
+        rules: [
+            {
+                test: /\.tsx?$/,
+                use: [
+                    {
+                        loader: 'babel-loader',
+                        options: {
+                            cacheDirectory: true,
+                        },
+                    },
+                    {
+                        loader: 'ts-loader'
+                    }
+                ]
+            },
+            {
+                test: /\.css$/,
+                use: [
+                    {
+                        loader: MiniCssExtractPlugin.loader,
+                        options: {
+                            publicPath: '../',
+                        },
+                    },
+                    {
+                        loader: 'css-loader',
+                    },
+                ],
+            },
+            {
+                test: /\.less$/,
+                use: [
+                    {
+                        loader: MiniCssExtractPlugin.loader,
+                        options: {
+                            publicPath: '../',
+                        },
+                    },
+                    {
+                        loader: 'css-loader',
+                    },
+                    {
+                        loader: 'less-loader',
+                    },
+                ],
+            },
+            {
+                test: /\.(png|svg|jpe?g|gif)$/i,
+                use: [
+                    {
+                        loader: 'url-loader',
+                        options: {
+                            limit: 18192,
+                            outputPath: 'img',
+                            name: '[name].[ext]?[hash]',
+                            esModule: false,
+                        },
+                    },
+                ],
+            },
+            {
+                test: /\.(woff|woff2|eot|ttf|otf)$/i,
+                use: [
+                    {
+                        loader: 'file-loader',
+                        options: {
+                            outputPath: 'font',
+                            esModule: false,
+                        },
+                    },
+                ],
+            },
+            {
+                test: /\.wasm$/,
+                type: 'webassembly/async',
+            },
+        ],
+    },
+    resolve: {
+        extensions: ['.ts', '.tsx', '.js', '.json'],
+        alias: {
+            '@': resolve('src'),
+        },
+    },
+    experiments: {
+        syncWebAssembly: true,
+        asyncWebAssembly: true,
+        topLevelAwait: true,
+    }
+};

+ 47 - 0
packages/ot-demo/build/webpack.dev.js

@@ -0,0 +1,47 @@
+const { merge } = require('webpack-merge');
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ESLintPlugin = require('eslint-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const ProgressBarWebpackPlugin = require('progress-bar-webpack-plugin');
+const common = require('./webpack.config.js');
+
+const resolve = (url) => path.resolve(__dirname, "..", url);
+
+module.exports = merge(common, {
+    mode: 'development',
+    devtool: 'inline-source-map',
+    plugins: [
+        new ESLintPlugin({
+            overrideConfigFile: resolve(".eslintrc.js"),
+            context: resolve("src"),
+            extensions: ['ts', 'js'],
+            fix: true,
+        }),
+        new ProgressBarWebpackPlugin(),
+        new HtmlWebpackPlugin({
+            template: './index.html',
+            title: 'ot-demo',
+            scriptLoading: 'blocking',
+        }),
+        new MiniCssExtractPlugin({
+            filename: 'css/[name].[contenthash].css',
+        }),
+    ],
+    output: {
+        filename: 'js/[name].[contenthash].js',
+        library: 'ot-demo',
+        libraryTarget: 'umd',
+    },
+    devServer: {
+        host: '127.0.0.1',
+        port: 'auto',
+        static: './',
+        hot: true,
+        bonjour: true,
+        client: {
+            progress: true,
+            overlay: true,
+        },
+    },
+});

+ 41 - 0
packages/ot-demo/build/webpack.prod.js

@@ -0,0 +1,41 @@
+const path = require('path');
+const { merge } = require('webpack-merge');
+const CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin;
+const WebpackObfuscator = require('webpack-obfuscator');
+const ESLintPlugin = require('eslint-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ProgressBarWebpackPlugin = require('progress-bar-webpack-plugin');
+const common = require('./webpack.config.js');
+
+const resolve = (url) => path.resolve(__dirname, "..", url);
+
+module.exports = merge(common, {
+    mode: 'production',
+    devtool: 'source-map',
+    plugins: [
+        new CleanWebpackPlugin(),
+        new ESLintPlugin({
+            overrideConfigFile: resolve(".eslintrc.js"),
+            context: resolve("src"),
+            extensions: ['ts', 'js'],
+            fix: true,
+        }),
+        new WebpackObfuscator(),
+        new ProgressBarWebpackPlugin(),
+        new HtmlWebpackPlugin({
+            template: './index.html',
+            title: 'ot-demo',
+            scriptLoading: 'blocking',
+        }),
+        new MiniCssExtractPlugin({
+            filename: 'css/[name].css',
+        }),
+    ],
+    output: {
+        filename: 'js/[name].js',
+        libraryTarget: 'umd',
+        library: 'ot-demo',
+        path: resolve('dist'),
+    },
+});

+ 11 - 0
packages/ot-demo/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+</head>
+<body></body>
+<script>
+
+</script>
+</html>

+ 6 - 0
packages/ot-demo/jest.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+  testEnvironment: 'jsdom',
+  collectCoverage: true,
+  preset: 'ts-jest',
+  coverageReporters: ['json', 'html'],
+};

+ 63 - 0
packages/ot-demo/package.json

@@ -0,0 +1,63 @@
+{
+	"name": "ot-demo",
+	"version": "1.0.0",
+	"description": "",
+	"main": "./src/index.ts",
+	"author": "",
+	"license": "ISC",
+    "scripts": {
+        "dev-ot-demo": "webpack-dev-server --open --config build/webpack.dev.js",
+        "build-ot-demo": "webpack --config build/webpack.prod.js"
+    },
+    "dependencies": {
+        "ot-js": "1.0.0",
+        "socket.io": "4.3.2",
+        "express": "^4.16.2",
+        "socket.io-client": "4.3.2"
+    },
+	"devDependencies": {
+		"typescript": "4.5.2",
+
+        "@types/socket.io": "3.0.2",
+        "@types/socket.io-client": "3.0.0",
+
+		"assert":"2.0.0",
+		"buffer": "6.0.3",
+
+		"@babel/core": "7.16.0",
+		"@babel/preset-env": "7.16.4",
+        "@babel/preset-typescript": "7.16.5",
+
+        "eslint": "8.4.1",
+        "eslint-config-airbnb-base": "15.0.0",
+        "eslint-plugin-import": "2.25.3",
+        "eslint-config-airbnb-typescript": "16.1.0",
+        "@typescript-eslint/eslint-plugin": "5.6.0",
+        "@typescript-eslint/parser": "5.6.0",
+
+        "jest": "27.0.6",
+        "ts-jest": "27.1.0",
+        "@types/jest": "27.0.3",
+
+		"webpack": "5.65.0",
+		"webpack-cli": "4.9.1",
+		"webpack-dev-server": "4.6.0",
+
+		"babel-loader": "8.2.3",
+		"css-loader": "6.5.1",
+		"file-loader": "6.2.0",
+		"less-loader": "10.2.0",
+		"ts-loader": "9.2.6",
+		"url-loader": "4.1.1",
+
+		"clean-webpack-plugin": "4.0.0",
+		"eslint-webpack-plugin": "3.1.1",
+		"copy-webpack-plugin": "10.0.0",
+		"mini-css-extract-plugin": "2.4.5",
+		"webpack-merge": "5.8.0",
+        "webpack-javascript-obfuscator-plugin": "1.0.9",
+        "webpack-obfuscator": "3.5.1",
+		"html-webpack-plugin": "5.5.0",
+		"progress-bar-webpack-plugin": "2.1.0"
+	}
+}

+ 18 - 0
packages/ot-demo/server.js

@@ -0,0 +1,18 @@
+let express = require('express');
+let socket = require('socket.io');
+let http = require('http');
+
+let app = express();
+let io = socket(http.Server(app));
+
+app.get('/', (request, response) => {
+  response.sendFile(`${__dirname}/index.html`);
+});
+
+http.listen(3000, () => {
+  console.log('listening on *:3000');
+});
+
+io.on('connection', (socket) => {
+
+});

+ 34 - 0
packages/ot-demo/src/basics/conflict.ts

@@ -0,0 +1,34 @@
+import {
+  Conflict,
+} from "ot-js";
+
+import {
+  InsertColumnOperation,
+  InsertRowOperation,
+  RemoveColumnOperation,
+  RemoveRowOperation,
+} from "../internal";
+
+export function createConflict(): Conflict {
+  const conflict = new Conflict();
+  conflict.add(InsertRowOperation, InsertRowOperation, (tc, sc) => (to, so) => {
+    /**
+       * 服务端插入的行在当前行的下方
+       */
+    if (to.getHeight() < so.getIndex()) {
+      tc.addOperation(to);
+      sc.addOperation(so);
+      return;
+    }
+
+    /**
+       * 服务端插入的行在当前行的上方
+       */
+    if (to.getIndex() > so.getHeight()) {
+      to.setIndex(to.getIndex() + so.getCount());
+      tc.addOperation(to);
+      sc.addOperation(so);
+    }
+  });
+  return conflict;
+}

+ 11 - 0
packages/ot-demo/src/client/DomClient.ts

@@ -0,0 +1,11 @@
+import { ClientSocket } from "ot-js";
+import { createConflict } from "../internal";
+
+export class DomClient extends ClientSocket {
+  public constructor(post: string) {
+    super({
+      url: post,
+      conflict: createConflict(),
+    });
+  }
+}

+ 1 - 0
packages/ot-demo/src/index.ts

@@ -0,0 +1 @@
+export * from './internal';

+ 6 - 0
packages/ot-demo/src/internal.ts

@@ -0,0 +1,6 @@
+export * from './operation/InsertColumnOperation';
+export * from './operation/InsertRowOperation';
+export * from './operation/RemoveColumnOperation';
+export * from './operation/RemoveRowOperation';
+
+export * from './basics/conflict';

+ 74 - 0
packages/ot-demo/src/operation/InsertColumnOperation.ts

@@ -0,0 +1,74 @@
+import {
+  DataBuffer,
+  Deserialize,
+  Operation,
+  TransformUtils,
+} from "ot-js";
+
+export class InsertColumnOperation implements Operation {
+  protected _describe: string = 'InsertColumnOperation';
+  protected _index: number;
+  protected _count: number;
+  protected _buffer: DataBuffer;
+
+  public constructor()
+  public constructor(serialize: InsertColumnOperation.Serialize)
+  public constructor(...parameter: any) {
+    if (TransformUtils.hasLength(parameter, 0)) {
+      this._index = 0;
+      this._count = 0;
+      this._buffer = new DataBuffer();
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const serialize = parameter[0];
+      this._index = serialize.index;
+      this._count = serialize.count;
+      this._buffer = Deserialize.getInstance().formObject(serialize.buffer, new DataBuffer());
+    }
+  }
+
+  public apply(resource: unknown): void {
+  }
+
+  public setIndex(index: number): void {
+    this._index = index;
+  }
+
+  public setCount(count: number): void {
+    this._count = count;
+  }
+
+  public getIndex(): number {
+    return this._index;
+  }
+
+  public getCount(): number {
+    return this._count;
+  }
+
+  public clone(): InsertColumnOperation {
+    const clone = new InsertColumnOperation();
+    clone._index = this._index;
+    clone._count = this._count;
+    clone._buffer = this._buffer.clone();
+    return clone;
+  }
+
+  public toJSON(): InsertColumnOperation.Serialize {
+    return {
+      describe: this._describe,
+      index: this._index,
+      count: this._count,
+      buffer: this._buffer,
+    };
+  }
+}
+
+export namespace InsertColumnOperation {
+  export interface Serialize extends Operation.Serialize {
+    index: number;
+    count: number;
+    buffer: DataBuffer
+  }
+}

+ 78 - 0
packages/ot-demo/src/operation/InsertRowOperation.ts

@@ -0,0 +1,78 @@
+import {
+  DataBuffer,
+  Deserialize,
+  Operation,
+  TransformUtils,
+} from "ot-js";
+
+export class InsertRowOperation implements Operation {
+  protected _describe: string = 'InsertRowOperation';
+  protected _index: number;
+  protected _count: number;
+  protected _buffer: DataBuffer;
+
+  public constructor()
+  public constructor(serialize: InsertRowOperation.Serialize)
+  public constructor(...parameter: any) {
+    if (TransformUtils.hasLength(parameter, 0)) {
+      this._index = 0;
+      this._count = 0;
+      this._buffer = new DataBuffer();
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const serialize = parameter[0];
+      this._index = serialize.index;
+      this._count = serialize.count;
+      this._buffer = Deserialize.getInstance().formObject(serialize.buffer, new DataBuffer());
+    }
+  }
+
+  public apply(resource: unknown): void {
+  }
+
+  public setIndex(index: number): void {
+    this._index = index;
+  }
+
+  public setCount(count: number): void {
+    this._count = count;
+  }
+
+  public getIndex(): number {
+    return this._index;
+  }
+
+  public getCount(): number {
+    return this._count;
+  }
+
+  public getHeight(): number {
+    return this._index + this._count;
+  }
+
+  public clone(): InsertRowOperation {
+    const clone = new InsertRowOperation();
+    clone._index = this._index;
+    clone._count = this._count;
+    clone._buffer = this._buffer.clone();
+    return clone;
+  }
+
+  public toJSON(): InsertRowOperation.Serialize {
+    return {
+      describe: this._describe,
+      index: this._index,
+      count: this._count,
+      buffer: this._buffer,
+    };
+  }
+}
+
+export namespace InsertRowOperation {
+  export interface Serialize extends Operation.Serialize {
+    index: number;
+    count: number;
+    buffer?: DataBuffer
+  }
+}

+ 79 - 0
packages/ot-demo/src/operation/RemoveColumnOperation.ts

@@ -0,0 +1,79 @@
+import {
+  DataBuffer,
+  Operation,
+  TransformUtils,
+} from "ot-js";
+
+export class RemoveColumnOperation implements Operation {
+  protected _describe: string = 'RemoveColumnOperation';
+  protected _index: number;
+  protected _count: number;
+  protected _buffer: DataBuffer<unknown>;
+
+  public constructor()
+  public constructor(serialize: RemoveColumnOperation.Serialize)
+  public constructor(serialize: RemoveColumnOperation.Serialize, buffer: DataBuffer)
+  public constructor(...parameter: any) {
+    if (TransformUtils.hasLength(parameter, 0)) {
+      this._index = 0;
+      this._count = 0;
+      this._buffer = new DataBuffer();
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const serialize = parameter[0];
+      this._index = serialize.index;
+      this._count = serialize.count;
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 2)) {
+      const serialize = parameter[0];
+      const buffer = parameter[1];
+      this._index = serialize.index;
+      this._count = serialize.count;
+      this._buffer = buffer;
+    }
+  }
+
+  public apply(resource: unknown): void {
+  }
+
+  public setIndex(index: number): void {
+    this._index = index;
+  }
+
+  public setCount(count: number): void {
+    this._count = count;
+  }
+
+  public getIndex(): number {
+    return this._index;
+  }
+
+  public getCount(): number {
+    return this._count;
+  }
+
+  public clone(): RemoveColumnOperation {
+    const clone = new RemoveColumnOperation();
+    clone._index = this._index;
+    clone._count = this._count;
+    clone._buffer = this._buffer.clone();
+    return clone;
+  }
+
+  public toJSON(): RemoveColumnOperation.Serialize {
+    return {
+      describe: this._describe,
+      index: this._index,
+      count: this._count,
+    };
+  }
+}
+
+export namespace RemoveColumnOperation {
+  export interface Serialize extends Operation.Serialize {
+    index: number;
+    count: number;
+  }
+}

+ 79 - 0
packages/ot-demo/src/operation/RemoveRowOperation.ts

@@ -0,0 +1,79 @@
+import {
+  DataBuffer,
+  Operation,
+  TransformUtils,
+} from "ot-js";
+
+export class RemoveRowOperation implements Operation {
+  protected _describe: string = 'RemoveRowOperation';
+  protected _index: number;
+  protected _count: number;
+  protected _buffer: DataBuffer<unknown>;
+
+  public constructor()
+  public constructor(serialize: RemoveRowOperation.Serialize)
+  public constructor(serialize: RemoveRowOperation.Serialize, buffer: DataBuffer)
+  public constructor(...parameter: any) {
+    if (TransformUtils.hasLength(parameter, 0)) {
+      this._index = 0;
+      this._count = 0;
+      this._buffer = new DataBuffer();
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const serialize = parameter[0];
+      this._index = serialize.index;
+      this._count = serialize.count;
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 2)) {
+      const serialize = parameter[0];
+      const buffer = parameter[1];
+      this._index = serialize.index;
+      this._count = serialize.count;
+      this._buffer = buffer;
+    }
+  }
+
+  public apply(resource: unknown): void {
+  }
+
+  public setIndex(index: number): void {
+    this._index = index;
+  }
+
+  public setCount(count: number): void {
+    this._count = count;
+  }
+
+  public getIndex(): number {
+    return this._index;
+  }
+
+  public getCount(): number {
+    return this._count;
+  }
+
+  public clone(): RemoveRowOperation {
+    const clone = new RemoveRowOperation();
+    clone._index = this._index;
+    clone._count = this._count;
+    clone._buffer = this._buffer.clone();
+    return clone;
+  }
+
+  public toJSON(): RemoveRowOperation.Serialize {
+    return {
+      describe: this._describe,
+      index: this._index,
+      count: this._count,
+    };
+  }
+}
+
+export namespace RemoveRowOperation {
+  export interface Serialize extends Operation.Serialize {
+    index: number;
+    count: number;
+  }
+}

+ 8 - 0
packages/ot-demo/src/server/DomeServer.ts

@@ -0,0 +1,8 @@
+import { ServerSocket } from "ot-js";
+import { createConflict } from "../internal";
+
+export class DomeServer extends ServerSocket {
+  public constructor() {
+    super(createConflict());
+  }
+}

+ 23 - 0
packages/ot-demo/tsconfig.json

@@ -0,0 +1,23 @@
+{
+    "compilerOptions": {
+        "module": "commonjs",
+        "target": "ES2015",
+        "lib": [
+            "ScriptHost",
+            "DOM",
+            "ES2015",
+            "ES2016",
+            "ES2017",
+            "ES2018",
+            "ES2019",
+            "ES2020",
+            "ES2021",
+            "ESNext"
+        ],
+        "types": ["jest", "node", "@types/jest", "@types/offscreencanvas"],
+        "sourceMap": true,
+        "typeRoots" : ["./src/define"],
+        "downlevelIteration": true,
+        "experimentalDecorators": true
+    }
+}

+ 5 - 0
packages/ot-js/.babelrc

@@ -0,0 +1,5 @@
+{
+    "presets": [
+        "@babel/env"
+    ]
+}

+ 11 - 0
packages/ot-js/.editorconfig

@@ -0,0 +1,11 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = crlf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+max_line_length = 120
+tab_width = 4
+trim_trailing_whitespace = true

+ 9 - 0
packages/ot-js/.eslintignore

@@ -0,0 +1,9 @@
+dist
+sakura
+
+.eslintrc.js
+webpack.config.js
+webpack.dev.js
+webpack.prod.js
+package.json
+tsconfig.json

+ 64 - 0
packages/ot-js/.eslintrc.js

@@ -0,0 +1,64 @@
+const path = require('path');
+
+const resolve = (url) => path.resolve(__dirname, url);
+
+module.exports = {
+    "extends": [
+        'airbnb-base',
+        'airbnb-typescript/base'
+    ],
+    "env": {
+        "browser": true,
+        "es2021": true
+    },
+    "rules": {
+        "no-cond-assign": "off",
+        "prefer-regex-literals": "off",
+        "@typescript-eslint/no-empty-function": "off",
+        "no-restricted-globals": "off",
+        "prefer-object-spread": "off",
+        "new-cap":"off",
+        "guard-for-in":"off",
+        "no-multi-assign": "off",
+        "no-lonely-if": "off",
+        "@typescript-eslint/lines-between-class-members": "off",
+        "prefer-exponentiation-operator":"off",
+        "no-restricted-properties": "off",
+        "function-paren-newline": "off",
+        "no-labels": "off",
+        "@typescript-eslint/no-shadow": "off",
+        "no-nested-ternary": "off",
+        "no-debugger": "off",
+        "no-mixed-operators": "off",
+        "prefer-destructuring": "off",
+        "no-console": "off",
+        "import/export": "off",
+        "@typescript-eslint/quotes": "off",
+        "prefer-const": "off",
+        'linebreak-style': ["off", "windows"],
+        "prefer-rest-params": "off",
+        "max-classes-per-file": "off",
+        "max-len": "off",
+        "new-parens": "off",
+        "no-continue": "off",
+        "no-plusplus": "off",
+        "import/prefer-default-export": "off",
+        "@typescript-eslint/naming-convention": "off",
+        "@typescript-eslint/no-unused-vars": "off",
+        "@typescript-eslint/no-use-before-define": "off",
+        "import/no-cycle": "off",
+        "no-bitwise": 'off',
+        "default-case": "off",
+        "no-param-reassign": "off",
+        "class-methods-use-this": "off",
+        "consistent-return": "off",
+        "no-underscore-dangle": "off",
+        "no-restricted-syntax": "off",
+    },
+    "parserOptions": {
+        "warnOnUnsupportedTypeScriptVersion": false,
+        "ecmaVersion": 12,
+        "sourceType": "module",
+        "project": resolve("tsconfig.json")
+    }
+}

+ 95 - 0
packages/ot-js/.gitignore

@@ -0,0 +1,95 @@
+#ide
+*.DS_Store
+.idea
+.vscode
+
+#npm
+package-lock.json
+
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+#c++
+CMakeFiles
+cmake_install.cmake
+CMakeCache.txt
+Makefile
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented library generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages/
+
+# C++ build debug directories
+cmake-build-debug
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL snapshot
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# next.js build output
+.next
+
+#lerna-changelog
+.changelog
+
+#webpack build
+.dist/
+
+#editor workspace file
+.idea/
+
+
+.DS_Store
+
+.history
+examples

+ 1 - 0
packages/ot-js/README.md

@@ -0,0 +1 @@
+### OperationalTransformation

+ 100 - 0
packages/ot-js/build/webpack.config.js

@@ -0,0 +1,100 @@
+const path = require('path');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+const resolve = (url) => path.resolve(__dirname, "..", url);
+
+module.exports = {
+    entry: {
+        'ot-js': resolve("src/index.ts"),
+    },
+    module: {
+        rules: [
+            {
+                test: /\.tsx?$/,
+                use: [
+                    {
+                        loader: 'babel-loader',
+                        options: {
+                            cacheDirectory: true,
+                        },
+                    },
+                    {
+                        loader: 'ts-loader'
+                    }
+                ]
+            },
+            {
+                test: /\.css$/,
+                use: [
+                    {
+                        loader: MiniCssExtractPlugin.loader,
+                        options: {
+                            publicPath: '../',
+                        },
+                    },
+                    {
+                        loader: 'css-loader',
+                    },
+                ],
+            },
+            {
+                test: /\.less$/,
+                use: [
+                    {
+                        loader: MiniCssExtractPlugin.loader,
+                        options: {
+                            publicPath: '../',
+                        },
+                    },
+                    {
+                        loader: 'css-loader',
+                    },
+                    {
+                        loader: 'less-loader',
+                    },
+                ],
+            },
+            {
+                test: /\.(png|svg|jpe?g|gif)$/i,
+                use: [
+                    {
+                        loader: 'url-loader',
+                        options: {
+                            limit: 18192,
+                            outputPath: 'img',
+                            name: '[name].[ext]?[hash]',
+                            esModule: false,
+                        },
+                    },
+                ],
+            },
+            {
+                test: /\.(woff|woff2|eot|ttf|otf)$/i,
+                use: [
+                    {
+                        loader: 'file-loader',
+                        options: {
+                            outputPath: 'font',
+                            esModule: false,
+                        },
+                    },
+                ],
+            },
+            {
+                test: /\.wasm$/,
+                type: 'webassembly/async',
+            },
+        ],
+    },
+    resolve: {
+        extensions: ['.ts', '.tsx', '.js', '.json'],
+        alias: {
+            '@': resolve('src'),
+        },
+    },
+    experiments: {
+        syncWebAssembly: true,
+        asyncWebAssembly: true,
+        topLevelAwait: true,
+    }
+};

+ 47 - 0
packages/ot-js/build/webpack.dev.js

@@ -0,0 +1,47 @@
+const { merge } = require('webpack-merge');
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ESLintPlugin = require('eslint-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const ProgressBarWebpackPlugin = require('progress-bar-webpack-plugin');
+const common = require('./webpack.config.js');
+
+const resolve = (url) => path.resolve(__dirname, "..", url);
+
+module.exports = merge(common, {
+    mode: 'development',
+    devtool: 'inline-source-map',
+    plugins: [
+        new ESLintPlugin({
+            overrideConfigFile: resolve(".eslintrc.js"),
+            context: resolve("src"),
+            extensions: ['ts', 'js'],
+            fix: true,
+        }),
+        new ProgressBarWebpackPlugin(),
+        new HtmlWebpackPlugin({
+            template: './index.html',
+            title: 'ot-js',
+            scriptLoading: 'blocking',
+        }),
+        new MiniCssExtractPlugin({
+            filename: 'css/[name].[contenthash].css',
+        }),
+    ],
+    output: {
+        filename: 'js/[name].[contenthash].js',
+        library: 'ot-js',
+        libraryTarget: 'umd',
+    },
+    devServer: {
+        host: '127.0.0.1',
+        port: 'auto',
+        static: './',
+        hot: true,
+        bonjour: true,
+        client: {
+            progress: true,
+            overlay: true,
+        },
+    },
+});

+ 41 - 0
packages/ot-js/build/webpack.prod.js

@@ -0,0 +1,41 @@
+const path = require('path');
+const { merge } = require('webpack-merge');
+const CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin;
+const WebpackObfuscator = require('webpack-obfuscator');
+const ESLintPlugin = require('eslint-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ProgressBarWebpackPlugin = require('progress-bar-webpack-plugin');
+const common = require('./webpack.config.js');
+
+const resolve = (url) => path.resolve(__dirname, "..", url);
+
+module.exports = merge(common, {
+    mode: 'production',
+    devtool: 'source-map',
+    plugins: [
+        new CleanWebpackPlugin(),
+        new ESLintPlugin({
+            overrideConfigFile: resolve(".eslintrc.js"),
+            context: resolve("src"),
+            extensions: ['ts', 'js'],
+            fix: true,
+        }),
+        new WebpackObfuscator(),
+        new ProgressBarWebpackPlugin(),
+        new HtmlWebpackPlugin({
+            template: './index.html',
+            title: 'ot-js',
+            scriptLoading: 'blocking',
+        }),
+        new MiniCssExtractPlugin({
+            filename: 'css/[name].css',
+        }),
+    ],
+    output: {
+        filename: 'js/[name].js',
+        libraryTarget: 'umd',
+        library: 'ot-js',
+        path: resolve('dist'),
+    },
+});

+ 11 - 0
packages/ot-js/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+</head>
+<body></body>
+<script>
+
+</script>
+</html>

+ 6 - 0
packages/ot-js/jest.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+  testEnvironment: 'jsdom',
+  collectCoverage: true,
+  preset: 'ts-jest',
+  coverageReporters: ['json', 'html'],
+};

+ 61 - 0
packages/ot-js/package.json

@@ -0,0 +1,61 @@
+{
+	"name": "ot-js",
+	"version": "1.0.0",
+	"description": "",
+	"main": "./src/index.ts",
+	"author": "",
+	"license": "ISC",
+    "scripts": {
+        "dev-ot-js": "webpack-dev-server --open --config build/webpack.dev.js",
+        "build-ot-js": "webpack --config build/webpack.prod.js"
+    },
+    "dependencies": {
+        "socket.io": "4.3.2",
+        "socket.io-client": "4.3.2"
+    },
+	"devDependencies": {
+		"typescript": "4.5.2",
+
+        "@types/socket.io": "3.0.2",
+        "@types/socket.io-client": "3.0.0",
+
+		"assert":"2.0.0",
+		"buffer": "6.0.3",
+
+		"@babel/core": "7.16.0",
+		"@babel/preset-env": "7.16.4",
+        "@babel/preset-typescript": "7.16.5",
+
+        "eslint": "8.4.1",
+        "eslint-config-airbnb-base": "15.0.0",
+        "eslint-plugin-import": "2.25.3",
+        "eslint-config-airbnb-typescript": "16.1.0",
+        "@typescript-eslint/eslint-plugin": "5.6.0",
+        "@typescript-eslint/parser": "5.6.0",
+
+        "jest": "27.0.6",
+        "ts-jest": "27.1.0",
+        "@types/jest": "27.0.3",
+
+		"webpack": "5.65.0",
+		"webpack-cli": "4.9.1",
+		"webpack-dev-server": "4.6.0",
+
+		"babel-loader": "8.2.3",
+		"css-loader": "6.5.1",
+		"file-loader": "6.2.0",
+		"less-loader": "10.2.0",
+		"ts-loader": "9.2.6",
+		"url-loader": "4.1.1",
+
+		"clean-webpack-plugin": "4.0.0",
+		"eslint-webpack-plugin": "3.1.1",
+		"copy-webpack-plugin": "10.0.0",
+		"mini-css-extract-plugin": "2.4.5",
+		"webpack-merge": "5.8.0",
+        "webpack-javascript-obfuscator-plugin": "1.0.9",
+        "webpack-obfuscator": "3.5.1",
+		"html-webpack-plugin": "5.5.0",
+		"progress-bar-webpack-plugin": "2.1.0"
+	}
+}

+ 62 - 0
packages/ot-js/src/basics/Command.ts

@@ -0,0 +1,62 @@
+import {
+  Deserialize,
+  Describe,
+  Operation,
+} from "../internal";
+
+export class Command {
+  protected _describe: string = 'Command';
+  protected _groupId: string;
+  protected _operation: Array<Operation>;
+
+  public constructor(serialize: Command.Serialize) {
+    this._groupId = serialize.groupId;
+    this._operation = Deserialize.getInstance().formArray(serialize.operation);
+  }
+
+  public concat(target: Command): Command {
+    const result = new Command({
+      groupId: this._groupId,
+      describe: this._describe,
+      operation: [],
+    });
+    result._operation = this._operation.concat(target._operation);
+    return result;
+  }
+
+  public apply(resource: unknown): void {
+    const operations = this._operation;
+    const sizeof = operations.length;
+    for (let i = 0; i < sizeof; i++) {
+      const action = operations[i];
+      action.apply(resource);
+    }
+  }
+
+  public getOperations(): Array<Operation> {
+    return this._operation;
+  }
+
+  public getGroupId(): string {
+    return this._groupId;
+  }
+
+  public addOperation(operation: Operation): void {
+    this._operation.push(operation);
+  }
+
+  public toJSON(): Command.Serialize {
+    return {
+      describe: this._describe,
+      groupId: this._groupId,
+      operation: this._operation.map((o) => o.toJSON()),
+    };
+  }
+}
+
+export namespace Command {
+  export interface Serialize extends Describe {
+    operation: Array<Operation.Serialize>,
+    groupId: string
+  }
+}

+ 58 - 0
packages/ot-js/src/basics/Conflict.ts

@@ -0,0 +1,58 @@
+import {
+  BiConsumer,
+  Operation,
+  Class,
+  TransformUtils,
+  BiFunction,
+  Command,
+} from "../internal";
+
+export class Conflict {
+  protected _conflict: Array<Conflict.Wrapped>;
+
+  public constructor() {
+    this._conflict = new Array<Conflict.Wrapped>();
+  }
+
+  public get(targetOperation: Operation, savedOperation: Operation): Conflict.Wrapped {
+    const _conflict = this._conflict;
+    const sizeof = _conflict.length;
+    for (let i = 0; i < sizeof; i++) {
+      const wrapped = _conflict[i];
+      const targetFrom = TransformUtils.isAssignableFrom(targetOperation, wrapped.targetClass);
+      const savedFrom = TransformUtils.isAssignableFrom(savedOperation, wrapped.savedClass);
+      if (targetFrom && savedFrom) {
+        return wrapped;
+      }
+    }
+    return null;
+  }
+
+  public add<T extends Operation, S extends Operation >(wrapped: Conflict.Wrapped<T, S>): void
+  public add<T extends Operation, S extends Operation >(targetClass: Class<T>, savedClass: Class<S>, conflict: Conflict.Process<Command, Command, T, S>): void
+  public add<T extends Operation, S extends Operation >(...parameter: any): void {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const wrapped = parameter[0];
+      this._conflict.push(wrapped);
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 3)) {
+      const targetClass = parameter[0];
+      const savedClass = parameter[1];
+      const process = parameter[3];
+      this._conflict.push({ targetClass, savedClass, process });
+    }
+  }
+}
+
+export namespace Conflict {
+  export type Transfer<TO extends Operation, SO extends Operation> = BiConsumer<TO, SO>;
+
+  export type Process<TC extends Command, SC extends Command, TO extends Operation, SO extends Operation > = BiFunction<TC, SC, Transfer<TO, SO>>;
+
+  export interface Wrapped<T extends Operation = Operation, S extends Operation = Operation> {
+    targetClass: Class<T>;
+    savedClass: Class<S>;
+    process: Process<Command, Command, T, S>;
+  }
+}

+ 43 - 0
packages/ot-js/src/basics/DataBuffer.ts

@@ -0,0 +1,43 @@
+import { Describe, TransformUtils } from "../internal";
+
+export class DataBuffer<T = unknown> {
+  protected _describe: string = 'DataBuffer';
+  protected _data: T;
+
+  public constructor()
+  public constructor(serialize: DataBuffer.Serialize<T>)
+  public constructor(...parameter: any) {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      this._data = parameter[0].data;
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 0)) {
+      this._data = null;
+    }
+  }
+
+  public setData(_data: T): void {
+    this._data = _data;
+  }
+
+  public getData(): T {
+    return this._data;
+  }
+
+  public clone(): DataBuffer<T> {
+    return this;
+  }
+
+  public toJSON(): DataBuffer.Serialize<T> {
+    return {
+      describe: this._describe,
+      data: this._data,
+    };
+  }
+}
+
+export namespace DataBuffer {
+  export interface Serialize<T> extends Describe{
+    data: T
+  }
+}

+ 104 - 0
packages/ot-js/src/basics/Deserialize.ts

@@ -0,0 +1,104 @@
+import {
+  ImplementClass,
+  TransformUtils,
+} from "../internal";
+
+export interface Describe {
+  describe: string
+}
+
+let singleton: Deserialize;
+
+export class Deserialize {
+  public static getInstance(): Deserialize {
+    if (!TransformUtils.isDefine(singleton)) {
+      singleton = new Deserialize();
+    }
+    return singleton;
+  }
+
+  protected _storage: Map<string, ImplementClass>;
+
+  private constructor() {
+    this._storage = new Map<string, ImplementClass>();
+  }
+
+  public formArray<T>(object: Array<object>)
+  public formArray<T>(object: Array<object>, defaultValue: T)
+  public formArray<T>(...parameter: any) {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const object = parameter[0];
+      const length = object.length;
+      const result = new Array<T>();
+      for (let i = 0; i < length; i++) {
+        result.push(this.convert(<Describe><unknown>object[i]));
+      }
+      return result;
+    }
+    if (TransformUtils.hasLength(parameter, 2)) {
+      const object = parameter[0];
+      const defaultValue = parameter[1];
+      if (TransformUtils.isDefine(object)) {
+        const length = object.length;
+        const result = new Array<T>();
+        for (let i = 0; i < length; i++) {
+          result.push(this.convert(<Describe><unknown>object[i]));
+        }
+        return result;
+      }
+      return defaultValue;
+    }
+  }
+
+  public formObject<T>(object: object)
+  public formObject<T>(object: object, defaultValue: T)
+  public formObject<T>(...parameter: any) {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const object = parameter[0];
+      return this.convert(<Describe><unknown>object);
+    }
+    if (TransformUtils.hasLength(parameter, 2)) {
+      const object = parameter[0];
+      const defaultValue = parameter[1];
+      if (TransformUtils.isDefine(object)) {
+        return this.convert(<Describe><unknown>object);
+      }
+      return defaultValue;
+    }
+  }
+
+  public convert<T>(value: Describe): T {
+    if (TransformUtils.isDefine(value.describe)) {
+      return this.newInstance(value.describe, value);
+    }
+    return <T><unknown>value;
+  }
+
+  public addClass(clazz: ImplementClass)
+  public addClass(name: string, clazz: ImplementClass)
+  public addClass(...parameter: any) {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const clazz = parameter[0];
+      this._storage.set(clazz.name, clazz);
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 2)) {
+      const name = parameter[0];
+      const clazz = parameter[1];
+      this._storage.set(name, clazz);
+    }
+  }
+
+  public newInstance<T>(name: string, desc: Describe): T {
+    const constructor = this.getClass(name);
+    return <T> <unknown> new constructor(desc);
+  }
+
+  public getClass(name: string): ImplementClass {
+    const element = this._storage.get(name);
+    if (TransformUtils.isDefine(element)) {
+      return element;
+    }
+    throw new Error(`not find ${name} Class`);
+  }
+}

+ 15 - 0
packages/ot-js/src/basics/Operation.ts

@@ -0,0 +1,15 @@
+import {
+  Describe,
+} from "../internal";
+
+export interface Operation {
+  apply(resource: unknown): void
+
+  clone(): Operation;
+
+  toJSON(): Operation.Serialize;
+}
+
+export namespace Operation {
+  export interface Serialize extends Describe{}
+}

+ 49 - 0
packages/ot-js/src/basics/Transform.ts

@@ -0,0 +1,49 @@
+import {
+  Command,
+  Conflict,
+} from "../internal";
+
+export class Transform {
+  protected _conflict: Conflict;
+
+  public constructor(_conflict: Conflict) {
+    this._conflict = _conflict;
+  }
+
+  public transfer(targetCommand: Command, savedCommand: Command): Array<Command> {
+    const conflict = this._conflict;
+
+    const targetOperations = targetCommand.getOperations();
+    const savedOperations = savedCommand.getOperations();
+
+    const sizeof1 = targetOperations.length;
+    const sizeof2 = savedOperations.length;
+
+    const newTargetCommand = new Command({
+      groupId: targetCommand.getGroupId(),
+      operation: [],
+      describe: 'Command',
+    });
+    const newSavedCommand = new Command({
+      groupId: targetCommand.getGroupId(),
+      operation: [],
+      describe: 'Command',
+    });
+
+    for (let i = 0; i < sizeof1; i++) {
+      const targetOperation = targetOperations[i];
+      for (let j = 0; j < sizeof2; j++) {
+        const savedOperation = savedOperations[j];
+        const wrapped = conflict.get(targetOperation, savedOperation);
+        if (wrapped != null) {
+          const transfer = wrapped.process(newTargetCommand, newSavedCommand);
+          transfer(targetOperation.clone(), savedOperation.clone());
+          continue;
+        }
+        throw new Error('not found _conflict process');
+      }
+    }
+
+    return [newTargetCommand, newSavedCommand];
+  }
+}

+ 33 - 0
packages/ot-js/src/client/AwaitingConfirm.ts

@@ -0,0 +1,33 @@
+import {
+  Synchronized,
+  Command,
+  AwaitingWithBuffer,
+  Client,
+} from "../internal";
+
+export class AwaitingConfirm {
+  protected _outstanding: Command;
+
+  public constructor(outstanding: Command) {
+    this._outstanding = outstanding;
+  }
+
+  public applyClient(client: Client, command: Command): AwaitingWithBuffer {
+    return new AwaitingWithBuffer(this._outstanding, command);
+  }
+
+  public serverAck(client: Client): Synchronized {
+    return Synchronized.DEFAULT;
+  }
+
+  public applyServer(client: Client, command: Command): AwaitingConfirm {
+    const outstanding = this._outstanding;
+    const pair = client.getTransform().transfer(outstanding, command);
+    client.applyCommand(pair[1]);
+    return new AwaitingConfirm(pair[0]);
+  }
+
+  public resend(client: Client) {
+    client.sendCommand(client.getRevision(), this._outstanding);
+  }
+}

+ 38 - 0
packages/ot-js/src/client/AwaitingWithBuffer.ts

@@ -0,0 +1,38 @@
+import {
+  AwaitingConfirm,
+  Client,
+  Command,
+} from "../internal";
+
+export class AwaitingWithBuffer {
+  protected _outstanding: Command;
+  protected _buffer: Command;
+
+  public constructor(outstanding: Command, buffer: Command) {
+    this._outstanding = outstanding;
+    this._buffer = buffer;
+  }
+
+  public applyClient(client: Client, command: Command): AwaitingWithBuffer {
+    let newBuffer = this._buffer.concat(command);
+    return new AwaitingWithBuffer(this._outstanding, newBuffer);
+  }
+
+  public serverAck(client: Client): AwaitingConfirm {
+    client.sendCommand(client.getRevision(), this._buffer);
+    return new AwaitingConfirm(this._buffer);
+  }
+
+  public applyServer(client: Client, command: Command): AwaitingWithBuffer {
+    const outstanding = this._outstanding;
+    const buffer = this._buffer;
+    const pair1 = client.getTransform().transfer(outstanding, command);
+    const pair2 = client.getTransform().transfer(buffer, pair1[1]);
+    client.applyCommand(pair2[1]);
+    return new AwaitingWithBuffer(pair1[0], pair2[0]);
+  }
+
+  public resend(client: Client) {
+    client.sendCommand(client.getRevision(), this._outstanding);
+  }
+}

+ 57 - 0
packages/ot-js/src/client/Client.ts

@@ -0,0 +1,57 @@
+import {
+  AwaitingConfirm,
+  AwaitingWithBuffer,
+  Command,
+  Conflict,
+  Synchronized,
+  Transform,
+  UndoManager,
+} from "../internal";
+
+export abstract class Client {
+  protected _status: Synchronized | AwaitingConfirm | AwaitingWithBuffer;
+  protected _undoManager: UndoManager;
+  protected _revision: number;
+  protected _transform: Transform;
+
+  protected constructor(conflict: Conflict) {
+    this._revision = 0;
+    this._transform = new Transform(conflict);
+    this._status = Synchronized.DEFAULT;
+    this._undoManager = new UndoManager(this._transform);
+  }
+
+  public abstract applyCommand(command: Command): void;
+
+  public abstract sendCommand(revision: number, command: Command): void ;
+
+  public applyClient(command: Command): void {
+    this._status = this._status.applyClient(this, command);
+  }
+
+  public serverAck(): void {
+    this._revision++;
+    this._status = this._status.serverAck(this);
+  }
+
+  public applyServer(command: Command): void {
+    this._revision++;
+    this._status = this._status.applyServer(this, command);
+  }
+
+  public serverReconnect() {
+    this._status.resend(this);
+  }
+
+  public getTransform(): Transform {
+    return this._transform;
+  }
+
+  public getRevision(): number {
+    return this._revision;
+  }
+
+  public getUndoManager(): UndoManager {
+    return this._undoManager;
+  }
+}

+ 72 - 0
packages/ot-js/src/client/ClientSocket.ts

@@ -0,0 +1,72 @@
+import {
+  Socket,
+  io,
+} from "socket.io-client";
+
+import {
+  UndoManager,
+  User,
+  Command,
+  Client,
+  ClientSocketConfig,
+  Deserialize,
+} from "../internal";
+
+export class ClientSocket extends Client {
+  protected _source: unknown;
+  protected _socket: Socket;
+  protected _clients: Array<User>;
+  protected _config: ClientSocketConfig;
+
+  protected _initializeListener(): void {
+    const observer = this._undoManager.getObserver();
+    observer.addOnAddRedoListener({
+      onAddRedo: (wrapped: UndoManager.Wrapped): void => {
+        wrapped.redo.apply(this._source);
+        this.applyClient(wrapped.redo);
+      },
+    });
+    observer.addOnAddUndoListener({
+      onAddUndo: (wrapped: UndoManager.Wrapped): void => {
+        wrapped.undo.apply(this._source);
+        this.applyClient(wrapped.undo);
+      },
+    });
+  }
+
+  protected _initializeSocket(): void {
+    this._socket.on('command', (command: string) => {
+      const object = JSON.parse(command);
+      const deserialize = Deserialize.getInstance().formObject<Command>(object);
+      this.applyServer(deserialize);
+    });
+    this._socket.on('open', (response: string) => {
+      const data = JSON.parse(response);
+      this._source = data.source;
+      this._clients = data.clients;
+    });
+    this._socket.on('ack', () => {
+      this.serverAck();
+    });
+    this._socket.on('reconnect', () => {
+      this.serverReconnect();
+    });
+  }
+
+  public constructor(config: ClientSocketConfig) {
+    super(config.conflict);
+    this._config = config;
+    this._socket = io(config.url);
+    this._initializeListener();
+    this._initializeSocket();
+  }
+
+  public sendCommand(revision: number, command: Command): void {
+    this._socket.emit('command', revision, command.toJSON());
+  }
+
+  public applyCommand(command: Command): void {
+    command.apply(this._source);
+    this._undoManager.transform(command);
+  }
+}

+ 27 - 0
packages/ot-js/src/client/Synchronized.ts

@@ -0,0 +1,27 @@
+import {
+  AwaitingConfirm,
+  Client,
+  Command,
+} from "../internal";
+
+export class Synchronized {
+  public static DEFAULT = new Synchronized();
+
+  public applyClient(client: Client, command: Command): AwaitingConfirm {
+    client.sendCommand(client.getRevision(), command);
+    return new AwaitingConfirm(command);
+  }
+
+  public serverAck(client: Client): Synchronized {
+    throw new Error("There is no pending operation.");
+  }
+
+  public applyServer(client: Client, command: Command): Synchronized {
+    client.applyCommand(command);
+    return this;
+  }
+
+  public resend(client: Client): void {
+
+  }
+}

+ 123 - 0
packages/ot-js/src/client/UndoManager.ts

@@ -0,0 +1,123 @@
+import {
+  TransformUtils,
+  Command,
+  Transform,
+  UndoManagerObserver,
+} from "../internal";
+
+export class UndoManager {
+  protected _redoStack: Array<UndoManager.Wrapped>;
+  protected _undoStack: Array<UndoManager.Wrapped>;
+  protected _transform: Transform;
+  protected _observer: UndoManagerObserver;
+
+  protected _transformStack(stack: Array<UndoManager.Wrapped>, command: Command): void {
+    const transform = this._transform;
+    const sizeof = stack.length - 1;
+    for (let i = sizeof; i >= 0; i--) {
+      const redo = transform.transfer(stack[i].redo, command)[1];
+      const undo = transform.transfer(stack[i].undo, command)[1];
+      stack[i].redo = redo;
+      stack[i].undo = undo;
+    }
+  }
+
+  public constructor(transform: Transform) {
+    this._redoStack = [];
+    this._undoStack = [];
+    this._transform = transform;
+    this._observer = new UndoManagerObserver();
+  }
+
+  public transform(command: Command): void {
+    this._transformStack(this._undoStack, command);
+    this._transformStack(this._redoStack, command);
+  }
+
+  public canUndo(): boolean {
+    return this._undoStack.length !== 0;
+  }
+
+  public canRedo(): boolean {
+    return this._redoStack.length !== 0;
+  }
+
+  public addRedo(redo: Command, undo: Command): void
+  public addRedo(wrapped: UndoManager.Wrapped): void
+  public addRedo(...parameter: any): void {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const wrapped = parameter[0];
+      this._redoStack.push(wrapped);
+      this._observer.dispatchOnAddRedo(wrapped);
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 2)) {
+      const redo = parameter[0];
+      const undo = parameter[1];
+      const wrapped = { redo, undo };
+      this._redoStack.push(wrapped);
+      this._observer.dispatchOnAddRedo(wrapped);
+    }
+  }
+
+  public addUndo(redo: Command, undo: Command): void
+  public addUndo(wrapped: UndoManager.Wrapped): void
+  public addUndo(...parameter: any): void {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const wrapped = parameter[0];
+      this._undoStack.push(wrapped);
+      this._observer.dispatchOnAddUndo(wrapped);
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 2)) {
+      const redo = parameter[0];
+      const undo = parameter[1];
+      const wrapped = { redo, undo };
+      this._undoStack.push(wrapped);
+      this._observer.dispatchOnAddUndo(wrapped);
+    }
+  }
+
+  public performCommand(redo: Command, undo: Command): void
+  public performCommand(wrapped: UndoManager.Wrapped): void
+  public performCommand(...parameter: any): void {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      const wrapped = parameter[0];
+      this.addRedo(wrapped);
+      return;
+    }
+    if (TransformUtils.hasLength(parameter, 2)) {
+      const redo = parameter[0];
+      const undo = parameter[1];
+      const wrapped = { redo, undo };
+      this.addRedo(wrapped);
+    }
+  }
+
+  public performRedo(): void {
+    if (this.canRedo()) {
+      const wrapped = this._redoStack.pop();
+      this.addUndo(wrapped);
+      this._observer.dispatchOnRedo(wrapped);
+    }
+  }
+
+  public performUndo(): void {
+    if (this.canUndo()) {
+      const wrapped = this._undoStack.pop();
+      this.addRedo(wrapped);
+      this._observer.dispatchOnUndo(wrapped);
+    }
+  }
+
+  public getObserver(): UndoManagerObserver {
+    return this._observer;
+  }
+}
+
+export namespace UndoManager {
+  export interface Wrapped{
+    undo: Command,
+    redo: Command,
+  }
+}

+ 129 - 0
packages/ot-js/src/client/UndoManagerObserver.ts

@@ -0,0 +1,129 @@
+import {
+  UndoManager,
+  ArrayList,
+} from "../internal";
+
+export class UndoManagerObserver {
+  protected _onAddRedoListeners: ArrayList<UndoManagerObserver.OnAddRedoListener>;
+  protected _onAddUndoListeners: ArrayList<UndoManagerObserver.OnAddUndoListener>;
+  protected _onRedoListeners: ArrayList<UndoManagerObserver.OnRedoListener>;
+  protected _onUndoListeners: ArrayList<UndoManagerObserver.OnUndoListener>;
+
+  public dispatchOnAddRedo(wrapped: UndoManager.Wrapped): void {
+    let listeners = this._onAddRedoListeners;
+    if (listeners != null) {
+      const sizeof = listeners.sizeof();
+      for (let i = 0; i < sizeof; i++) {
+        const listener = listeners.get(i);
+        listener.onAddRedo(wrapped);
+      }
+    }
+  }
+
+  public dispatchOnAddUndo(wrapped: UndoManager.Wrapped): void {
+    let listeners = this._onAddUndoListeners;
+    if (listeners != null) {
+      const sizeof = listeners.sizeof();
+      for (let i = 0; i < sizeof; i++) {
+        const listener = listeners.get(i);
+        listener.onAddUndo(wrapped);
+      }
+    }
+  }
+
+  public dispatchOnRedo(wrapped: UndoManager.Wrapped): void {
+    let listeners = this._onRedoListeners;
+    if (listeners != null) {
+      const sizeof = listeners.sizeof();
+      for (let i = 0; i < sizeof; i++) {
+        const listener = listeners.get(i);
+        listener.onRedo(wrapped);
+      }
+    }
+  }
+
+  public dispatchOnUndo(wrapped: UndoManager.Wrapped): void {
+    let listeners = this._onUndoListeners;
+    if (listeners != null) {
+      const sizeof = listeners.sizeof();
+      for (let i = 0; i < sizeof; i++) {
+        const listener = listeners.get(i);
+        listener.onUndo(wrapped);
+      }
+    }
+  }
+
+  public addOnAddRedoListener(listener: UndoManagerObserver.OnAddRedoListener): void {
+    if (this._onAddRedoListeners == null) {
+      this._onAddRedoListeners = new ArrayList<UndoManagerObserver.OnAddRedoListener>();
+    }
+    this._onAddRedoListeners.add(listener);
+  }
+
+  public addOnAddUndoListener(listener: UndoManagerObserver.OnAddUndoListener): void {
+    if (this._onAddUndoListeners == null) {
+      this._onAddUndoListeners = new ArrayList<UndoManagerObserver.OnAddUndoListener>();
+    }
+    this._onAddUndoListeners.add(listener);
+  }
+
+  public addOnRedoListener(listener: UndoManagerObserver.OnRedoListener): void {
+    if (this._onRedoListeners == null) {
+      this._onRedoListeners = new ArrayList<UndoManagerObserver.OnRedoListener>();
+    }
+    this._onRedoListeners.add(listener);
+  }
+
+  public addOnUndoListener(listener: UndoManagerObserver.OnUndoListener): void {
+    if (this._onUndoListeners == null) {
+      this._onUndoListeners = new ArrayList<UndoManagerObserver.OnUndoListener>();
+    }
+    this._onUndoListeners.add(listener);
+  }
+
+  public removeOnAddRedoListener(listener: UndoManagerObserver.OnAddRedoListener): void {
+    if (this._onAddRedoListeners == null) {
+      return;
+    }
+    this._onAddRedoListeners.remove(listener);
+  }
+
+  public removeOnAddUndoListener(listener: UndoManagerObserver.OnAddUndoListener): void {
+    if (this._onAddUndoListeners == null) {
+      return;
+    }
+    this._onAddUndoListeners.remove(listener);
+  }
+
+  public removeOnUndoListener(listener: UndoManagerObserver.OnUndoListener): void {
+    if (this._onUndoListeners == null) {
+      return;
+    }
+    this._onUndoListeners.remove(listener);
+  }
+
+  public removeOnRedoListener(listener: UndoManagerObserver.OnRedoListener): void {
+    if (this._onRedoListeners == null) {
+      return;
+    }
+    this._onRedoListeners.remove(listener);
+  }
+}
+
+export namespace UndoManagerObserver {
+  export interface OnAddRedoListener {
+    onAddRedo(wrapped: UndoManager.Wrapped): void
+  }
+
+  export interface OnAddUndoListener {
+    onAddUndo(wrapped: UndoManager.Wrapped): void
+  }
+
+  export interface OnRedoListener {
+    onRedo(wrapped: UndoManager.Wrapped): void
+  }
+
+  export interface OnUndoListener {
+    onUndo(wrapped: UndoManager.Wrapped): void
+  }
+}

+ 6 - 0
packages/ot-js/src/config/ClientSocketConfig.ts

@@ -0,0 +1,6 @@
+import { Conflict } from "../internal";
+
+export class ClientSocketConfig {
+  url: string;
+  conflict: Conflict;
+}

+ 1 - 0
packages/ot-js/src/index.ts

@@ -0,0 +1 @@
+export * from "./internal";

+ 41 - 0
packages/ot-js/src/internal.ts

@@ -0,0 +1,41 @@
+// ================================================= typed
+
+export * from './typed/GlobalType';
+
+// ================================================= utils
+
+export * from './utils/ArrayUtils';
+export * from './utils/TransformUtils';
+
+// ================================================= config
+
+export * from './config/ClientSocketConfig';
+
+// ================================================= library
+
+export * from './library/ArrayList';
+
+// ================================================= basics
+
+export * from './basics/Command';
+export * from './basics/Operation';
+export * from './basics/Conflict';
+export * from './basics/Transform';
+export * from './basics/DataBuffer';
+export * from './basics/Deserialize';
+
+// ================================================= client
+
+export * from './client/Client';
+export * from './client/ClientSocket';
+export * from './client/AwaitingConfirm';
+export * from './client/Synchronized';
+export * from './client/AwaitingWithBuffer';
+export * from './client/UndoManager';
+export * from './client/UndoManagerObserver';
+
+// ================================================= server
+
+export * from './server/ServerSocket';
+export * from './server/User';
+export * from './server/Server';

+ 45 - 0
packages/ot-js/src/library/ArrayList.ts

@@ -0,0 +1,45 @@
+import { TransformUtils } from "../utils/TransformUtils";
+
+export class ArrayList<T> {
+  protected _arraylist: Array<T>;
+
+  public constructor() {
+    this._arraylist = new Array<T>();
+  }
+
+  public get(index: number): T {
+    return this._arraylist[index];
+  }
+
+  public sizeof(): number {
+    return this._arraylist.length;
+  }
+
+  public add(value: T): void {
+    this._arraylist.push(value);
+  }
+
+  public remove(index: number): void
+  public remove(value: T): void
+  public remove(...parameter: any): void {
+    if (TransformUtils.hasLength(parameter, 1)) {
+      if (TransformUtils.isNumber(parameter[0])) {
+        const index = parameter[0];
+        this._arraylist.splice(index, 1);
+        return;
+      }
+      if (TransformUtils.isObject(parameter[0])) {
+        const value = parameter[0];
+        const arraylist = this._arraylist;
+        const sizeof = arraylist.length;
+        for (let i = 0; i < sizeof; i++) {
+          const item = arraylist[i];
+          if (item === <T><unknown>value) {
+            this._arraylist.splice(i, 1);
+            return;
+          }
+        }
+      }
+    }
+  }
+}

+ 33 - 0
packages/ot-js/src/server/Server.ts

@@ -0,0 +1,33 @@
+import {
+  Command,
+  Conflict,
+  Transform,
+} from "../internal";
+
+export class Server {
+  protected _chronicle: Array<Command>;
+  protected _transform: Transform;
+
+  public constructor(conflict: Conflict) {
+    this._chronicle = new Array<Command>();
+    this._transform = new Transform(conflict);
+  }
+
+  public receiveCommand(revision: number, command: Command): void {
+    if (revision < 0 || this._chronicle.length < revision) {
+      throw new Error("command revision not in history");
+    }
+    let history = this._chronicle.slice(revision);
+    for (let i = 0; i < history.length; i++) {
+      const saved = history[i];
+      if (command.getGroupId() === saved.getGroupId()) {
+        command = this._transform.transfer(command, saved)[0];
+      }
+    }
+    this._chronicle.push(command);
+  }
+
+  public getRevision(): number {
+    return this._chronicle.length;
+  }
+}

+ 49 - 0
packages/ot-js/src/server/ServerSocket.ts

@@ -0,0 +1,49 @@
+import {
+  Socket,
+} from "socket.io";
+
+import {
+  Command,
+  User,
+  Server,
+  Conflict,
+  Deserialize,
+} from "../internal";
+
+export class ServerSocket extends Server {
+  protected _roomId: string;
+  protected _users: Array<User>;
+  protected _source: unknown;
+
+  public constructor(conflict: Conflict) {
+    super(conflict);
+  }
+
+  public receiveCommand(revision: number, command: Command) {
+    super.receiveCommand(revision, command);
+    command.apply(this._source);
+  }
+
+  public addSocketClient(client: Socket): void {
+    client.join(this._roomId);
+    client.on('command', () => {});
+    client.on('disconnect', () => {});
+    client.emit('open', {
+      revision: this.getRevision(),
+      clients: this._users,
+      source: this._source,
+    });
+  }
+
+  public onCommand(client: Socket, revision: number, command: string): void {
+    const object = JSON.parse(command);
+    const deserialize = Deserialize.getInstance().formObject(object);
+    this.receiveCommand(revision, deserialize);
+    client.emit('ack');
+    client.broadcast.in(this._roomId).emit('command', this._roomId, deserialize.toJSON());
+  }
+
+  public onDisconnect(client: Socket): void {
+
+  }
+}

+ 3 - 0
packages/ot-js/src/server/User.ts

@@ -0,0 +1,3 @@
+export class User {
+
+}

+ 37 - 0
packages/ot-js/src/typed/GlobalType.ts

@@ -0,0 +1,37 @@
+export type AbstractClass<T = unknown> = abstract new (...args: any) => T;
+
+export type ImplementClass<T = unknown> = new (...args: any) => T;
+
+export type Constructor<T = unknown> = Function & { prototype: T };
+
+export type Class<T = unknown> = AbstractClass<T> | ImplementClass<T> | Constructor<T>;
+
+export type InterfaceDescription = boolean;
+
+export type UnknownType = any;
+
+export type Nullable<T = unknown> = T | null | undefined | void;
+
+export type Consumer<T = void> = (value: T) => void;
+
+export type Predicate<T = unknown> = (value: T) => boolean;
+
+export type Influence<T = void, R = void> = (value: T) => R;
+
+export type UnaryOperator<T = void> = (value: T) => T;
+
+export type Execute = () => void;
+
+export type Supplier<T = void> = () => T;
+
+export type BiConsumer<T = void, U = void> = (value1: T, value2: U) => void;
+
+export type BinaryOperator<T = void> = (value1: T, value2: T) => T;
+
+export type BiPredicate<T = void, U = void> = (value1: T, value2: U) => boolean;
+
+export type BiFunction<T = void, U = void, R = void> = (value1: T, value2: U) => R;
+
+export type CancelAnimationFrame = Influence<number>;
+
+export type RequestAnimationFrame = Influence<Consumer<number>, number>;

+ 3 - 0
packages/ot-js/src/utils/ArrayUtils.ts

@@ -0,0 +1,3 @@
+export class ArrayUtils {
+
+}

+ 85 - 0
packages/ot-js/src/utils/TransformUtils.ts

@@ -0,0 +1,85 @@
+import {
+  Class,
+  ImplementClass,
+} from "../internal";
+
+export class TransformUtils {
+  public static getValueType(value: any): string {
+    return Object.prototype.toString.apply(value);
+  }
+
+  public static isBoolean(value: any): value is boolean {
+    return this.getValueType(value) === '[object Boolean]';
+  }
+
+  public static isString(value: any): value is string {
+    return this.getValueType(value) === '[object String]';
+  }
+
+  public static isFunction(value: any): value is Function {
+    return this.getValueType(value) === '[object Function]';
+  }
+
+  public static isNumber(value: any): value is number {
+    return this.getValueType(value) === '[object Number]';
+  }
+
+  public static isDefine<T>(value: T | void): value is T {
+    return value !== undefined && value !== null;
+  }
+
+  public static unDefine(value: any): value is undefined | null {
+    return value === undefined || value === null;
+  }
+
+  public static isBlank(value: any): boolean {
+    if (!this.isDefine(value)) {
+      return true;
+    }
+    if (this.isString(value)) {
+      return value.trim() === '';
+    }
+    return false;
+  }
+
+  public static isConstructor<T>(clazz: Class<T>): clazz is ImplementClass<T> {
+    return clazz.prototype.constructor === clazz;
+  }
+
+  public static isAssignableFrom<T>(object: any, clazz: Class<T>): object is T {
+    return object instanceof clazz;
+  }
+
+  public static isObject(value?: any): value is object {
+    return this.getValueType(value) === '[object Object]';
+  }
+
+  public static isPlainObject(value: any): value is object {
+    if (!this.isDefine(value)) {
+      return false;
+    }
+    return Object.getPrototypeOf(value) === Object.getPrototypeOf({});
+  }
+
+  public static isArray<T>(value?: any, clazz?: Class<T>): value is Array<T> {
+    let type = this.getValueType(value) === '[object Array]';
+    if (type) {
+      if (TransformUtils.isDefine(clazz)) {
+        value = <Array<T>>value;
+        if (value.length) {
+          return value[0] instanceof clazz;
+        }
+      }
+      return true;
+    }
+    return false;
+  }
+
+  public static copy(src: object, target: object): void {
+    Object.assign(src, target);
+  }
+
+  public static hasLength(target: Array<any>, length: number): boolean {
+    return target.length === length;
+  }
+}

+ 3 - 0
packages/ot-js/test/bootstrap.js

@@ -0,0 +1,3 @@
+test('bootstrap', () => {
+});
+//# sourceMappingURL=bootstrap.js.map

+ 1 - 0
packages/ot-js/test/bootstrap.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["bootstrap.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;AAEvB,CAAC,CAAC,CAAC"}

+ 3 - 0
packages/ot-js/test/bootstrap.ts

@@ -0,0 +1,3 @@
+test('bootstrap', () => {
+
+});

+ 23 - 0
packages/ot-js/tsconfig.json

@@ -0,0 +1,23 @@
+{
+    "compilerOptions": {
+        "module": "commonjs",
+        "target": "ES2015",
+        "lib": [
+            "ScriptHost",
+            "DOM",
+            "ES2015",
+            "ES2016",
+            "ES2017",
+            "ES2018",
+            "ES2019",
+            "ES2020",
+            "ES2021",
+            "ESNext"
+        ],
+        "types": ["jest", "node", "@types/jest", "@types/offscreencanvas"],
+        "sourceMap": true,
+        "typeRoots" : ["./src/define"],
+        "downlevelIteration": true,
+        "experimentalDecorators": true
+    }
+}