diff --git a/bun.lock b/bun.lock
index 098028ab1c..238a096925 100644
--- a/bun.lock
+++ b/bun.lock
@@ -4,19 +4,19 @@
"": {
"name": "@hcengineering/communication",
"devDependencies": {
- "@eslint/js": "^9.15.0",
- "@types/bun": "^1.1.14",
- "bun-types": "^1.1.38",
- "eslint": "^9.15.0",
+ "@eslint/js": "^9.21.0",
+ "@types/bun": "^1.2.4",
+ "bun-types": "^1.2.4",
+ "eslint": "^9.21.0",
"eslint-config-prettier": "^9.1.0",
- "eslint-plugin-prettier": "^5.2.1",
- "prettier": "^3.3.3",
- "typescript-eslint": "^8.15.0",
+ "eslint-plugin-prettier": "^5.2.3",
+ "prettier": "^3.5.2",
+ "typescript-eslint": "^8.25.0",
},
},
"packages/client-query": {
"name": "@hcengineering/communication-client-query",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
"@hcengineering/communication-query": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
@@ -32,7 +32,7 @@
},
"packages/client-sqlite": {
"name": "@hcengineering/communication-client-sqlite",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-sqlite-wasm": "workspace:*",
@@ -48,8 +48,9 @@
},
"packages/client-ws": {
"name": "@hcengineering/communication-client-ws",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
+ "@hcengineering/communication-client-query": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
"@msgpack/msgpack": "^3.0.0-beta2",
@@ -63,12 +64,11 @@
},
"packages/cockroach": {
"name": "@hcengineering/communication-cockroach",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
- "@hcengineering/communication-core": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
+ "@hcengineering/communication-shared": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
- "pg": "8.12.0",
"postgres": "^3.4.4",
"uuid": "^11.0.3",
},
@@ -79,25 +79,10 @@
"typescript": "^5.6.3",
},
},
- "packages/core": {
- "name": "@hcengineering/communication-core",
- "version": "0.1.9",
- "dependencies": {
- "@hcengineering/communication-types": "workspace:*",
- },
- "devDependencies": {
- "@types/bun": "^1.1.14",
- },
- "peerDependencies": {
- "typescript": "^5.6.3",
- },
- },
"packages/examples": {
"name": "@hcengineering/communication-examples",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
- "@hcengineering/communication-client-query": "workspace:*",
- "@hcengineering/communication-client-sqlite": "workspace:*",
"@hcengineering/communication-client-ws": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
},
@@ -110,9 +95,10 @@
},
"packages/query": {
"name": "@hcengineering/communication-query",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
"@hcengineering/communication-sdk-types": "workspace:*",
+ "@hcengineering/communication-shared": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
"fast-equals": "^5.0.1",
},
@@ -126,7 +112,7 @@
},
"packages/sdk-types": {
"name": "@hcengineering/communication-sdk-types",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
"@hcengineering/communication-types": "workspace:*",
},
@@ -139,7 +125,7 @@
},
"packages/server": {
"name": "@hcengineering/communication-server",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
"@hcengineering/communication-cockroach": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
@@ -156,9 +142,24 @@
"typescript": "^5.6.3",
},
},
+ "packages/shared": {
+ "name": "@hcengineering/communication-shared",
+ "version": "0.1.47",
+ "dependencies": {
+ "@hcengineering/communication-types": "workspace:*",
+ "js-yaml": "^4.1.0",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.1.14",
+ "@types/js-yaml": "^4.0.9",
+ },
+ "peerDependencies": {
+ "typescript": "^5.6.3",
+ },
+ },
"packages/sqlite-wasm": {
"name": "@hcengineering/communication-sqlite-wasm",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
@@ -175,7 +176,7 @@
},
"packages/types": {
"name": "@hcengineering/communication-types",
- "version": "0.1.9",
+ "version": "0.1.47",
"dependencies": {
"@hcengineering/card": "0.7.2",
"@hcengineering/core": "0.7.2",
@@ -199,15 +200,15 @@
"@eslint/config-array": ["@eslint/config-array@0.19.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w=="],
- "@eslint/core": ["@eslint/core@0.11.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA=="],
+ "@eslint/core": ["@eslint/core@0.12.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg=="],
- "@eslint/eslintrc": ["@eslint/eslintrc@3.2.0", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w=="],
+ "@eslint/eslintrc": ["@eslint/eslintrc@3.3.0", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ=="],
- "@eslint/js": ["@eslint/js@9.20.0", "", {}, "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ=="],
+ "@eslint/js": ["@eslint/js@9.21.0", "", {}, "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
- "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.5", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
+ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.7", "", { "dependencies": { "@eslint/core": "^0.12.0", "levn": "^0.4.1" } }, "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g=="],
"@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.3", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.6", "@formatjs/intl-localematcher": "0.6.0", "decimal.js": "10", "tslib": "2" } }, "sha512-pJT1OkhplSmvvr6i3CWTPvC/FGC06MbN5TNBfRO6Ox62AEz90eMq+dVvtX9Bl3jxCEkS0tATzDarRZuOLw7oFg=="],
@@ -231,8 +232,6 @@
"@hcengineering/communication-cockroach": ["@hcengineering/communication-cockroach@workspace:packages/cockroach"],
- "@hcengineering/communication-core": ["@hcengineering/communication-core@workspace:packages/core"],
-
"@hcengineering/communication-examples": ["@hcengineering/communication-examples@workspace:packages/examples"],
"@hcengineering/communication-query": ["@hcengineering/communication-query@workspace:packages/query"],
@@ -241,6 +240,8 @@
"@hcengineering/communication-server": ["@hcengineering/communication-server@workspace:packages/server"],
+ "@hcengineering/communication-shared": ["@hcengineering/communication-shared@workspace:packages/shared"],
+
"@hcengineering/communication-sqlite-wasm": ["@hcengineering/communication-sqlite-wasm@workspace:packages/sqlite-wasm"],
"@hcengineering/communication-types": ["@hcengineering/communication-types@workspace:packages/types"],
@@ -259,7 +260,7 @@
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
- "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.1", "", {}, "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA=="],
+ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.2", "", {}, "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
@@ -285,7 +286,7 @@
"@types/body-parser": ["@types/body-parser@1.19.5", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg=="],
- "@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="],
+ "@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="],
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
@@ -301,6 +302,8 @@
"@types/http-errors": ["@types/http-errors@2.0.4", "", {}, "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="],
+ "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
+
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
@@ -319,21 +322,21 @@
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
- "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.24.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.24.1", "@typescript-eslint/type-utils": "8.24.1", "@typescript-eslint/utils": "8.24.1", "@typescript-eslint/visitor-keys": "8.24.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA=="],
+ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.25.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/type-utils": "8.25.0", "@typescript-eslint/utils": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA=="],
- "@typescript-eslint/parser": ["@typescript-eslint/parser@8.24.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.24.1", "@typescript-eslint/types": "8.24.1", "@typescript-eslint/typescript-estree": "8.24.1", "@typescript-eslint/visitor-keys": "8.24.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ=="],
+ "@typescript-eslint/parser": ["@typescript-eslint/parser@8.25.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/types": "8.25.0", "@typescript-eslint/typescript-estree": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg=="],
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.24.1", "", { "dependencies": { "@typescript-eslint/types": "8.24.1", "@typescript-eslint/visitor-keys": "8.24.1" } }, "sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q=="],
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0" } }, "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg=="],
- "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.24.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.24.1", "@typescript-eslint/utils": "8.24.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw=="],
+ "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.25.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.25.0", "@typescript-eslint/utils": "8.25.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g=="],
- "@typescript-eslint/types": ["@typescript-eslint/types@8.24.1", "", {}, "sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A=="],
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.25.0", "", {}, "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw=="],
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.24.1", "", { "dependencies": { "@typescript-eslint/types": "8.24.1", "@typescript-eslint/visitor-keys": "8.24.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg=="],
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q=="],
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.24.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.24.1", "@typescript-eslint/types": "8.24.1", "@typescript-eslint/typescript-estree": "8.24.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ=="],
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.25.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/types": "8.25.0", "@typescript-eslint/typescript-estree": "8.25.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA=="],
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.24.1", "", { "dependencies": { "@typescript-eslint/types": "8.24.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg=="],
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ=="],
"acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
@@ -357,7 +360,7 @@
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
- "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="],
+ "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
@@ -391,7 +394,7 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
- "eslint": ["eslint@9.20.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.20.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g=="],
+ "eslint": ["eslint@9.21.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.0", "@eslint/js": "9.21.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg=="],
"eslint-config-prettier": ["eslint-config-prettier@9.1.0", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw=="],
@@ -513,37 +516,13 @@
"periscopic": ["periscopic@3.1.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^3.0.0", "is-reference": "^3.0.0" } }, "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw=="],
- "pg": ["pg@8.12.0", "", { "dependencies": { "pg-connection-string": "^2.6.4", "pg-pool": "^3.6.2", "pg-protocol": "^1.6.1", "pg-types": "^2.1.0", "pgpass": "1.x" }, "optionalDependencies": { "pg-cloudflare": "^1.1.1" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ=="],
-
- "pg-cloudflare": ["pg-cloudflare@1.1.1", "", {}, "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q=="],
-
- "pg-connection-string": ["pg-connection-string@2.7.0", "", {}, "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA=="],
-
- "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
-
- "pg-pool": ["pg-pool@3.7.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-xIOsFoh7Vdhojas6q3596mXFsR8nwBQBXX5JiV7p9buEVAGqYL4yFzclON5P9vFrpu1u7Zwl2oriyDa89n0wbw=="],
-
- "pg-protocol": ["pg-protocol@1.7.1", "", {}, "sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ=="],
-
- "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
-
- "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
-
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"postgres": ["postgres@3.4.5", "", {}, "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg=="],
- "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
-
- "postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="],
-
- "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
-
- "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
-
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
- "prettier": ["prettier@3.5.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw=="],
+ "prettier": ["prettier@3.5.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg=="],
"prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
@@ -569,8 +548,6 @@
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
- "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
-
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -589,7 +566,7 @@
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
- "typescript-eslint": ["typescript-eslint@8.24.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.24.1", "@typescript-eslint/parser": "8.24.1", "@typescript-eslint/utils": "8.24.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA=="],
+ "typescript-eslint": ["typescript-eslint@8.25.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.25.0", "@typescript-eslint/parser": "8.25.0", "@typescript-eslint/utils": "8.25.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-TxRdQQLH4g7JkoFlYG3caW5v1S6kEkz8rqt80iQJZUYPq1zD1Ra7HfQBJJ88ABRaMvHAXnwRvRB4V+6sQ9xN5Q=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -603,14 +580,10 @@
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
- "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
-
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
- "@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.10.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw=="],
-
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
diff --git a/package.json b/package.json
index 92c68b7cd1..1d53a75e85 100644
--- a/package.json
+++ b/package.json
@@ -3,18 +3,19 @@
"type": "module",
"workspaces": ["packages/*"],
"scripts": {
+ "publish": "sh scripts/publish.sh",
"build": "sh scripts/build.sh && tsc --noEmit",
"lint": "eslint packages/**/src/*.ts",
"format": "prettier --write packages/**/src/*.ts && bun run lint"
},
"devDependencies": {
- "@eslint/js": "^9.15.0",
- "@types/bun": "^1.1.14",
- "bun-types": "^1.1.38",
- "eslint": "^9.15.0",
+ "@eslint/js": "^9.21.0",
+ "@types/bun": "^1.2.4",
+ "bun-types": "^1.2.4",
+ "eslint": "^9.21.0",
"eslint-config-prettier": "^9.1.0",
- "eslint-plugin-prettier": "^5.2.1",
- "prettier": "^3.3.3",
- "typescript-eslint": "^8.15.0"
+ "eslint-plugin-prettier": "^5.2.3",
+ "prettier": "^3.5.2",
+ "typescript-eslint": "^8.25.0"
}
}
diff --git a/packages/client-query/package.json b/packages/client-query/package.json
index 79c0faedee..df4ca1bc4c 100644
--- a/packages/client-query/package.json
+++ b/packages/client-query/package.json
@@ -1,6 +1,6 @@
{
"name": "@hcengineering/communication-client-query",
- "version": "0.1.9",
+ "version": "0.1.47",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
diff --git a/packages/client-query/src/index.ts b/packages/client-query/src/index.ts
index c48319513f..b1df62dcd8 100644
--- a/packages/client-query/src/index.ts
+++ b/packages/client-query/src/index.ts
@@ -1,5 +1,6 @@
import { LiveQueries } from '@hcengineering/communication-query'
import type { QueryClient } from '@hcengineering/communication-sdk-types'
+import type { WorkspaceID } from '@hcengineering/communication-types'
import { MessagesQuery, NotificationsQuery } from './query'
@@ -13,12 +14,12 @@ export function createNotificationsQuery(): NotificationsQuery {
return new NotificationsQuery(lq)
}
-export function initLiveQueries(client: QueryClient) {
+export function initLiveQueries(client: QueryClient, workspace: WorkspaceID, filesUrl: string): void {
if (lq != null) {
lq.close()
}
- lq = new LiveQueries(client)
+ lq = new LiveQueries(client, workspace, filesUrl)
client.onEvent = (event) => {
void lq.onEvent(event)
diff --git a/packages/client-query/src/query.ts b/packages/client-query/src/query.ts
index 55b2840c90..400e1e4d72 100644
--- a/packages/client-query/src/query.ts
+++ b/packages/client-query/src/query.ts
@@ -44,7 +44,7 @@ class BaseQuery
, C extends QueryCallback> {
}
}
- private needUpdate(params: FindMessagesParams, callback: MessagesQueryCallback): boolean {
+ private needUpdate(params: P, callback: C): boolean {
if (!deepEqual(params, this.oldQuery)) return true
if (!deepEqual(callback.toString(), this.oldCallback?.toString())) return true
return false
diff --git a/packages/client-sqlite/package.json b/packages/client-sqlite/package.json
index 97743c2e9b..ee0fa824cb 100644
--- a/packages/client-sqlite/package.json
+++ b/packages/client-sqlite/package.json
@@ -1,6 +1,6 @@
{
"name": "@hcengineering/communication-client-sqlite",
- "version": "0.1.9",
+ "version": "0.1.47",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
diff --git a/packages/client-sqlite/src/client.ts b/packages/client-sqlite/src/client.ts
index 2dc53627e9..2393490f9f 100644
--- a/packages/client-sqlite/src/client.ts
+++ b/packages/client-sqlite/src/client.ts
@@ -13,7 +13,10 @@ import {
type Notification,
type Attachment,
type Reaction,
- type WorkspaceID
+ type WorkspaceID,
+ type FindMessagesGroupsParams,
+ type MessagesGroup,
+ PatchType
} from '@hcengineering/communication-types'
import {
type Client,
@@ -24,7 +27,8 @@ import {
} from '@hcengineering/communication-sdk-types'
import { createDbAdapter as createSqliteDbAdapter } from '@hcengineering/communication-sqlite-wasm'
-class DbClient implements Client {
+//TODO: FIXME
+class DbClient {
onEvent: (event: ResponseEvent) => void = () => {}
constructor(
@@ -61,10 +65,14 @@ class DbClient implements Client {
this.onEvent({ type: ResponseEventType.MessageRemoved, message, card })
}
- async createPatch(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise {
+ async updateMessage(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise {
const created = new Date()
- await this.db.createPatch(card, message, content, creator, created)
- this.onEvent({ type: ResponseEventType.PatchCreated, card, patch: { message, content, creator, created } })
+ await this.db.createPatch(card, message, PatchType.update, content, creator, created)
+ this.onEvent({
+ type: ResponseEventType.PatchCreated,
+ card,
+ patch: { message, type: PatchType.update, content, creator, created }
+ })
}
async createReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise {
@@ -98,6 +106,10 @@ class DbClient implements Client {
return rawMessages.map((it) => this.toMessage(it))
}
+ async findMessagesGroups(params: FindMessagesGroupsParams): Promise {
+ return await this.db.findMessagesGroups(params)
+ }
+
async findMessage(params: FindMessagesParams): Promise {
return (await this.findMessages({ ...params, limit: 1 }))[0]
}
@@ -158,11 +170,17 @@ class DbClient implements Client {
return await this.db.findContexts(params, [this.personalWorkspace])
}
+ //eslint-disable-next-line @typescript-eslint/no-unused-vars
async findNotifications(params: FindNotificationsParams): Promise {
//TODO: should we filter by workspace?
return await this.db.findNotifications(params, this.personalWorkspace)
}
+ //eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise {
+ //TODO: implement
+ }
+
async unsubscribeQuery() {
//ignore
}
@@ -178,5 +196,5 @@ export async function getSqliteClient(
dbUrl = 'file:communication.sqlite3?vfs=opfs'
): Promise {
const db = await createSqliteDbAdapter(dbUrl)
- return new DbClient(db, workspace, personalWorkspace)
+ return new DbClient(db, workspace, personalWorkspace) as unknown as Client
}
diff --git a/packages/client-ws/package.json b/packages/client-ws/package.json
index e9c75bc5f9..86e4dadcb1 100644
--- a/packages/client-ws/package.json
+++ b/packages/client-ws/package.json
@@ -1,6 +1,6 @@
{
"name": "@hcengineering/communication-client-ws",
- "version": "0.1.9",
+ "version": "0.1.47",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
@@ -14,6 +14,7 @@
},
"dependencies": {
"@hcengineering/communication-types": "workspace:*",
+ "@hcengineering/communication-client-query": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
"@msgpack/msgpack": "^3.0.0-beta2"
},
diff --git a/packages/client-ws/src/client.ts b/packages/client-ws/src/client.ts
index 74d2828437..d11674f83b 100644
--- a/packages/client-ws/src/client.ts
+++ b/packages/client-ws/src/client.ts
@@ -1,18 +1,19 @@
import {
- type Attachment,
type CardID,
type ContextID,
+ type FindMessagesGroupsParams,
type FindMessagesParams,
type FindNotificationContextParams,
type FindNotificationsParams,
type Message,
type MessageID,
+ type MessagesGroup,
type Notification,
type NotificationContext,
type NotificationContextUpdate,
- type Reaction,
type RichText,
- type SocialID
+ type SocialID,
+ type WorkspaceID
} from '@hcengineering/communication-types'
import {
RequestEventType,
@@ -35,6 +36,7 @@ import {
type ResponseEvent,
type UpdateNotificationContextEvent
} from '@hcengineering/communication-sdk-types'
+import { initLiveQueries } from '@hcengineering/communication-client-query'
import { WebSocketConnection } from './connection'
@@ -75,7 +77,7 @@ class WsClient implements Client {
await this.sendEvent(event)
}
- async createPatch(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise {
+ async updateMessage(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise {
const event: CreatePatchEvent = {
type: RequestEventType.CreatePatch,
card,
@@ -130,39 +132,11 @@ class WsClient implements Client {
}
async findMessages(params: FindMessagesParams, queryId?: number): Promise {
- const rawMessages = await this.ws.send('findMessages', [params, queryId])
- return rawMessages.map((it: any) => this.toMessage(it))
+ return await this.ws.send('findMessages', [params, queryId])
}
- toMessage(raw: any): Message {
- return {
- id: raw.id,
- card: raw.card,
- content: raw.content,
- creator: raw.creator,
- created: new Date(raw.created),
- edited: new Date(raw.edited),
- reactions: raw.reactions.map((it: any) => this.toReaction(it)),
- attachments: raw.attachments.map((it: any) => this.toAttachment(it))
- }
- }
-
- toAttachment(raw: any): Attachment {
- return {
- message: raw.message,
- card: raw.card,
- creator: raw.creator,
- created: new Date(raw.created)
- }
- }
-
- toReaction(raw: any): Reaction {
- return {
- message: raw.message,
- reaction: raw.reaction,
- creator: raw.creator,
- created: new Date(raw.created)
- }
+ async findMessagesGroups(params: FindMessagesGroupsParams): Promise {
+ return await this.ws.send('findMessagesGroups', [params])
}
async createNotification(message: MessageID, context: ContextID): Promise {
@@ -229,12 +203,23 @@ class WsClient implements Client {
private async sendEvent(event: RequestEvent): Promise {
return await this.ws.send('event', [event])
}
+ //eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise {
+ //TODO: implement
+ }
close() {
void this.ws.close()
}
}
-export async function getWebsocketClient(url: string, token: string): Promise {
- return new WsClient(url, token)
+export async function getWebsocketClient(
+ url: string,
+ token: string,
+ workspace: WorkspaceID,
+ filesUrl: string
+): Promise {
+ const client = new WsClient(url, token)
+ initLiveQueries(client, workspace, filesUrl)
+ return client
}
diff --git a/packages/client-ws/src/index.ts b/packages/client-ws/src/index.ts
index 83dae7638c..226fb61584 100644
--- a/packages/client-ws/src/index.ts
+++ b/packages/client-ws/src/index.ts
@@ -1 +1,4 @@
+import { createMessagesQuery, createNotificationsQuery } from '@hcengineering/communication-client-query'
+
export * from './client'
+export { createMessagesQuery, createNotificationsQuery }
diff --git a/packages/cockroach/migrations/00_schema.sql b/packages/cockroach/migrations/00_schema.sql
index 0f19be60a3..ed4ea1dc0c 100644
--- a/packages/cockroach/migrations/00_schema.sql
+++ b/packages/cockroach/migrations/00_schema.sql
@@ -1,2 +1 @@
-DROP SCHEMA IF EXISTS communication CASCADE;
CREATE SCHEMA IF NOT EXISTS communication;
diff --git a/packages/cockroach/migrations/01_message.sql b/packages/cockroach/migrations/01_message.sql
index 65ee7b4aab..d113fe2fd3 100644
--- a/packages/cockroach/migrations/01_message.sql
+++ b/packages/cockroach/migrations/01_message.sql
@@ -8,9 +8,11 @@ CREATE TABLE IF NOT EXISTS communication.messages
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL,
- PRIMARY KEY (id, card_id, workspace_id)
+ PRIMARY KEY (workspace_id, card_id, id)
);
+CREATE INDEX IF NOT EXISTS idx_messages_workspace_card ON communication.messages (workspace_id, card_id);
+CREATE INDEX IF NOT EXISTS idx_messages_workspace_card_id ON communication.messages (workspace_id, card_id, id);
CREATE TABLE IF NOT EXISTS communication.messages_groups
(
@@ -18,11 +20,14 @@ CREATE TABLE IF NOT EXISTS communication.messages_groups
card_id VARCHAR(255) NOT NULL,
blob_id UUID NOT NULL,
- from_id INT8 NOT NULL,
- to_id INT8 NOT NULL,
from_date TIMESTAMPTZ NOT NULL,
to_date TIMESTAMPTZ NOT NULL,
+ from_id INT8 NOT NULL,
+ to_id INT8 NOT NULL,
count INT NOT NULL,
PRIMARY KEY (workspace_id, card_id, blob_id)
);
+
+
+CREATE INDEX IF NOT EXISTS idx_messages_groups_workspace_card ON communication.messages_groups (workspace_id, card_id);
\ No newline at end of file
diff --git a/packages/cockroach/migrations/02_patch.sql b/packages/cockroach/migrations/02_patch.sql
index 0513090d97..527df14bf0 100644
--- a/packages/cockroach/migrations/02_patch.sql
+++ b/packages/cockroach/migrations/02_patch.sql
@@ -4,6 +4,7 @@ CREATE TABLE IF NOT EXISTS communication.patch
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
message_id INT8 NOT NULL,
+ type VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL,
@@ -11,4 +12,4 @@ CREATE TABLE IF NOT EXISTS communication.patch
PRIMARY KEY (id)
);
-CREATE INDEX idx_patch_message_id ON communication.patch (message_id);
\ No newline at end of file
+CREATE INDEX IF NOT EXISTS idx_patch_workspace_card_message ON communication.patch (workspace_id, card_id, message_id);
\ No newline at end of file
diff --git a/packages/cockroach/migrations/03_attachment.sql b/packages/cockroach/migrations/03_attachment.sql
index 3a45d1fcb9..780002f335 100644
--- a/packages/cockroach/migrations/03_attachment.sql
+++ b/packages/cockroach/migrations/03_attachment.sql
@@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS communication.attachments
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL DEFAULT now(),
- PRIMARY KEY (message_id, card_id)
+ PRIMARY KEY (card_id, message_id)
);
CREATE INDEX IF NOT EXISTS attachment_message_idx ON communication.attachments (message_id);
diff --git a/packages/cockroach/migrations/04_reaction.sql b/packages/cockroach/migrations/04_reaction.sql
index a8b4fc9496..30e8ce02a7 100644
--- a/packages/cockroach/migrations/04_reaction.sql
+++ b/packages/cockroach/migrations/04_reaction.sql
@@ -7,7 +7,8 @@ CREATE TABLE IF NOT EXISTS communication.reactions
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL DEFAULT now(),
+ FOREIGN KEY (workspace_id, card_id, message_id) REFERENCES communication.messages (workspace_id, card_id, id) ON DELETE CASCADE,
PRIMARY KEY (workspace_id, card_id, message_id, creator, reaction)
);
-CREATE INDEX IF NOT EXISTS reaction_message_idx ON communication.reactions (message_id);
+CREATE INDEX IF NOT EXISTS idx_reactions_workspace_card_message ON communication.reactions (workspace_id, card_id, message_id);
\ No newline at end of file
diff --git a/packages/cockroach/migrations/05_notification_context.sql b/packages/cockroach/migrations/05_notification_context.sql
deleted file mode 100644
index 33fc386649..0000000000
--- a/packages/cockroach/migrations/05_notification_context.sql
+++ /dev/null
@@ -1,15 +0,0 @@
-CREATE TABLE IF NOT EXISTS communication.notification_context
-(
- id UUID NOT NULL DEFAULT gen_random_uuid(),
- workspace_id UUID NOT NULL,
- card_id VARCHAR(255) NOT NULL,
-
- personal_workspace UUID NOT NULL,
-
- archived_from TIMESTAMPTZ,
- last_view TIMESTAMPTZ,
- last_update TIMESTAMPTZ,
-
- PRIMARY KEY (id),
- UNIQUE (workspace_id, card_id, personal_workspace)
-);
diff --git a/packages/cockroach/migrations/05_thread.sql b/packages/cockroach/migrations/05_thread.sql
new file mode 100644
index 0000000000..73d6e50120
--- /dev/null
+++ b/packages/cockroach/migrations/05_thread.sql
@@ -0,0 +1,15 @@
+CREATE TABLE IF NOT EXISTS communication.thread
+(
+ workspace_id UUID NOT NULL,
+ card_id VARCHAR(255) NOT NULL,
+ message_id INT8 NOT NULL,
+ thread_id VARCHAR(255) NOT NULL,
+ replies_count INT NOT NULL,
+ last_reply TIMESTAMPTZ NOT NULL,
+
+ PRIMARY KEY (workspace_id, thread_id),
+ UNIQUE (workspace_id, card_id, message_id)
+);
+
+CREATE INDEX IF NOT EXISTS idx_thread_workspace_card_message ON communication.thread (workspace_id, thread_id);
+CREATE INDEX IF NOT EXISTS idx_thread_workspace_card_message ON communication.thread (workspace_id, card_id, message_id);
\ No newline at end of file
diff --git a/packages/cockroach/migrations/06_notification.sql b/packages/cockroach/migrations/06_notification.sql
index 0bb7b2c946..df565632a1 100644
--- a/packages/cockroach/migrations/06_notification.sql
+++ b/packages/cockroach/migrations/06_notification.sql
@@ -1,3 +1,20 @@
+CREATE TABLE IF NOT EXISTS communication.notification_context
+(
+ id UUID NOT NULL DEFAULT gen_random_uuid(),
+ workspace_id UUID NOT NULL,
+ card_id VARCHAR(255) NOT NULL,
+
+ personal_workspace UUID NOT NULL,
+
+ archived_from TIMESTAMPTZ,
+ last_view TIMESTAMPTZ,
+ last_update TIMESTAMPTZ,
+
+ PRIMARY KEY (id),
+ UNIQUE (workspace_id, card_id, personal_workspace)
+);
+
+
CREATE TABLE IF NOT EXISTS communication.notifications
(
message_id UUID NOT NULL,
diff --git a/packages/cockroach/package.json b/packages/cockroach/package.json
index 619b223cf4..b34725c8ed 100644
--- a/packages/cockroach/package.json
+++ b/packages/cockroach/package.json
@@ -1,6 +1,6 @@
{
"name": "@hcengineering/communication-cockroach",
- "version": "0.1.9",
+ "version": "0.1.47",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
@@ -13,10 +13,9 @@
"@types/bun": "^1.1.14"
},
"dependencies": {
- "@hcengineering/communication-core": "workspace:*",
+ "@hcengineering/communication-shared": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
- "pg": "8.12.0",
"postgres": "^3.4.4",
"uuid": "^11.0.3"
},
diff --git a/packages/cockroach/src/adapter.ts b/packages/cockroach/src/adapter.ts
index 0a805aab03..0ca7ef20b1 100644
--- a/packages/cockroach/src/adapter.ts
+++ b/packages/cockroach/src/adapter.ts
@@ -1,73 +1,88 @@
+import type { ParameterOrJSON, Row } from 'postgres'
import type postgres from 'postgres'
import {
- type Message,
- type FindMessagesParams,
+ type BlobID,
type CardID,
+ type ContextID,
+ type FindMessagesGroupsParams,
+ type FindMessagesParams,
+ type FindNotificationContextParams,
+ type FindNotificationsParams,
+ type Message,
+ type MessageID,
+ type MessagesGroup,
+ type Notification,
+ type NotificationContext,
+ type NotificationContextUpdate,
+ type PatchType,
type RichText,
type SocialID,
- type MessageID,
- type ContextID,
- type NotificationContextUpdate,
- type FindNotificationContextParams,
- type NotificationContext,
- type FindNotificationsParams,
- type Notification,
- type BlobID,
- type MessagesGroup,
- type FindMessagesGroupsParams,
- type WorkspaceID
+ type WorkspaceID,
+ type Thread
} from '@hcengineering/communication-types'
import type { DbAdapter } from '@hcengineering/communication-sdk-types'
+import { retry } from '@hcengineering/communication-shared'
import { MessagesDb } from './db/message'
import { NotificationsDb } from './db/notification'
import { connect, type PostgresClientReference } from './connection'
+import { type Options, type Logger, type SqlClient } from './types'
export class CockroachAdapter implements DbAdapter {
private readonly message: MessagesDb
private readonly notification: NotificationsDb
constructor(
- private readonly db: PostgresClientReference,
- private readonly sqlClient: postgres.Sql,
- private readonly workspace: WorkspaceID
+ private readonly sql: SqlClient,
+ private readonly workspace: WorkspaceID,
+ private readonly logger?: Logger,
+ private readonly options?: Options
) {
- this.message = new MessagesDb(this.sqlClient, this.workspace)
- this.notification = new NotificationsDb(this.sqlClient, this.workspace)
+ this.message = new MessagesDb(this.sql, this.workspace, logger, options)
+ this.notification = new NotificationsDb(this.sql, this.workspace, logger, options)
}
async createMessage(card: CardID, content: RichText, creator: SocialID, created: Date): Promise {
return await this.message.createMessage(card, content, creator, created)
}
+ async removeMessage(card: CardID, message: MessageID, socialIds?: SocialID[]): Promise {
+ await this.message.removeMessage(card, message, socialIds)
+ }
+
+ async removeMessages(card: CardID, fromId: MessageID, toId: MessageID): Promise {
+ await this.message.removeMessages(card, fromId, toId)
+ }
+
async createPatch(
card: CardID,
message: MessageID,
+ type: PatchType,
content: RichText,
creator: SocialID,
created: Date
): Promise {
- return await this.message.createPatch(card, message, content, creator, created)
+ await this.message.createPatch(card, message, type, content, creator, created)
}
- async removeMessage(card: CardID, message: MessageID): Promise {
- return await this.message.removeMessage(card, message)
- }
-
- async removeMessages(card: CardID, ids: MessageID[]): Promise {
- return await this.message.removeMessages(card, ids)
+ async removePatches(card: CardID, fromId: MessageID, toId: MessageID): Promise {
+ await this.message.removePatches(card, fromId, toId)
}
async createMessagesGroup(
card: CardID,
blobId: BlobID,
- from_id: MessageID,
- to_id: MessageID,
- from_date: Date,
- to_date: Date,
+ fromDate: Date,
+ toDate: Date,
+ fromId: MessageID,
+ toId: MessageID,
count: number
): Promise {
- return await this.message.createMessagesGroup(card, blobId, from_id, to_id, from_date, to_date, count)
+ await this.message.createMessagesGroup(card, blobId, fromDate, toDate, fromId, toId, count)
+ }
+
+ async removeMessagesGroup(card: CardID, blobId: BlobID): Promise {
+ await this.message.removeMessagesGroup(card, blobId)
}
async createReaction(
@@ -77,35 +92,35 @@ export class CockroachAdapter implements DbAdapter {
creator: SocialID,
created: Date
): Promise {
- return await this.message.createReaction(card, message, reaction, creator, created)
+ await this.message.createReaction(card, message, reaction, creator, created)
}
async removeReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise {
- return await this.message.removeReaction(card, message, reaction, creator)
+ await this.message.removeReaction(card, message, reaction, creator, new Date())
}
async createAttachment(message: MessageID, attachment: CardID, creator: SocialID, created: Date): Promise {
- return await this.message.createAttachment(message, attachment, creator, created)
+ await this.message.createAttachment(message, attachment, creator, created)
}
async removeAttachment(message: MessageID, attachment: CardID): Promise {
- return await this.message.removeAttachment(message, attachment)
+ await this.message.removeAttachment(message, attachment)
}
- async findMessages(params: FindMessagesParams): Promise {
- return await this.message.find(params)
+ async createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise {
+ await this.message.createThread(card, message, thread, created)
}
- async findMessagesGroups(params: FindMessagesGroupsParams): Promise {
- return await this.message.findGroups(params)
+ async updateThread(thread: CardID, lastReply: Date, op: 'increment' | 'decrement'): Promise {
+ await this.message.updateThread(thread, lastReply, op)
}
async createNotification(message: MessageID, context: ContextID): Promise {
- return await this.notification.createNotification(message, context)
+ await this.notification.createNotification(message, context)
}
async removeNotification(message: MessageID, context: ContextID): Promise {
- return await this.notification.removeNotification(message, context)
+ await this.notification.removeNotification(message, context)
}
async createContext(
@@ -118,11 +133,24 @@ export class CockroachAdapter implements DbAdapter {
}
async updateContext(context: ContextID, update: NotificationContextUpdate): Promise {
- return await this.notification.updateContext(context, update)
+ await this.notification.updateContext(context, update)
}
async removeContext(context: ContextID): Promise {
- return await this.notification.removeContext(context)
+ await this.notification.removeContext(context)
+ }
+
+ // Finds
+ async findMessages(params: FindMessagesParams): Promise {
+ return await this.message.find(params)
+ }
+
+ async findMessagesGroups(params: FindMessagesGroupsParams): Promise {
+ return await this.message.findMessagesGroups(params)
+ }
+
+ async findThread(thread: CardID): Promise {
+ return await this.message.findThread(thread)
}
async findContexts(
@@ -142,13 +170,92 @@ export class CockroachAdapter implements DbAdapter {
}
close(): void {
- this.db.close()
+ this.sql.close()
}
}
-export async function createDbAdapter(connectionString: string, workspace: WorkspaceID): Promise {
- const db = connect(connectionString)
- const sqlClient = await db.getClient()
+export async function createDbAdapter(
+ connectionString: string,
+ workspace: WorkspaceID,
+ logger?: Logger,
+ options?: Options
+): Promise {
+ const greenUrl = process.env.GREEN_URL ?? ''
+ if (greenUrl !== '') {
+ const client = new GreenClient(greenUrl)
+ return new CockroachAdapter(client, workspace, logger, options)
+ } else {
+ const connection = connect(connectionString)
+ const sql = await connection.getClient()
+ const client = new CockroachClient(connection, sql)
- return new CockroachAdapter(db, sqlClient, workspace)
+ return new CockroachAdapter(client, workspace, logger, options)
+ }
+}
+
+class GreenClient implements SqlClient {
+ private readonly url: string
+ private readonly token: string
+ constructor(endpoint: string) {
+ const url = new URL(endpoint)
+ this.token = url.searchParams.get('token') ?? 'secret'
+
+ const compression = url.searchParams.get('compression') ?? ''
+
+ const newHost = url.host
+ const newPathname = url.pathname
+ const newSearchParams = new URLSearchParams()
+
+ if (compression !== '') {
+ newSearchParams.set('compression', compression)
+ }
+
+ this.url = `${url.protocol}//${newHost}${newPathname}${newSearchParams.size > 0 ? '?' + newSearchParams.toString() : ''}`
+ }
+
+ async execute)[]>(query: string, params?: ParameterOrJSON[]): Promise {
+ return await retry(() => this.fetch(query, params), { retries: 5 })
+ }
+
+ private async fetch)[]>(
+ query: string,
+ params?: ParameterOrJSON[]
+ ): Promise {
+ const url = this.url.endsWith('/') ? this.url + 'api/v1/sql' : this.url + '/api/v1/sql'
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer ' + this.token,
+ Connection: 'keep-alive'
+ },
+ body: JSON.stringify({ query, params }, (_, value) => (typeof value === 'bigint' ? value.toString() : value))
+ })
+
+ if (!response.ok) {
+ throw new Error(`Failed to execute sql: ${response.status} ${response.statusText}`)
+ }
+
+ return await response.json()
+ }
+
+ close(): void {
+ // do nothing
+ }
+}
+
+class CockroachClient implements SqlClient {
+ constructor(
+ private readonly db: PostgresClientReference,
+ private readonly sql: postgres.Sql
+ ) {}
+
+ async execute)[]>(query: string, params?: ParameterOrJSON[]): Promise {
+ return await this.sql.unsafe(query, params)
+ }
+
+ close(): void {
+ this.db.close()
+ }
}
diff --git a/packages/cockroach/src/connection.ts b/packages/cockroach/src/connection.ts
index a9aa16e74b..b14941e516 100644
--- a/packages/cockroach/src/connection.ts
+++ b/packages/cockroach/src/connection.ts
@@ -1,4 +1,4 @@
-//Full copy from @hcengineering/postgres
+// Full copy from @hcengineering/postgres
import postgres from 'postgres'
import { v4 as uuid } from 'uuid'
@@ -86,10 +86,9 @@ export function connect(connectionString: string, database?: string): PostgresCl
application_name: 'communication'
},
database,
- max: 10,
- transform: {
- undefined: null
- },
+ max: 5,
+ fetch_types: false,
+ prepare: true,
...extraOptions
})
diff --git a/packages/cockroach/src/db/base.ts b/packages/cockroach/src/db/base.ts
index 1c71ecb734..e81dad2d21 100644
--- a/packages/cockroach/src/db/base.ts
+++ b/packages/cockroach/src/db/base.ts
@@ -1,79 +1,41 @@
-import type postgres from 'postgres'
-import type {WorkspaceID} from "@hcengineering/communication-types";
+import { type ParameterOrJSON, type Row } from 'postgres'
+import type { WorkspaceID } from '@hcengineering/communication-types'
+
+import { type SqlClient, type Logger, type Options } from '../types'
export class BaseDb {
- constructor(
- readonly client: postgres.Sql,
- readonly workspace: WorkspaceID
- ) {
+ constructor (
+ readonly client: SqlClient,
+ readonly workspace: WorkspaceID,
+ readonly logger?: Logger,
+ readonly options?: Options
+ ) {}
+
+ async execute)[]>(
+ sql: string,
+ params?: ParameterOrJSON[],
+ name?: string
+ ): Promise {
+ if (this.options?.withLogs === true && this.logger !== undefined) {
+ return await this.executeWithLogs(name ?? 'execute sql', this.logger, sql, params)
}
- async insert(table: string, data: Record): Promise {
- const keys = Object.keys(data)
- const values = Object.values(data)
- const sql = `
- INSERT INTO ${table} (${keys.map((k) => `"${k}"`).join(', ')})
- VALUES (${keys.map((_, idx) => `$${idx + 1}`).join(', ')});
- `
- await this.client.unsafe(sql, values)
- }
-
- async insertWithReturn(table: string, data: Record, returnField: string): Promise {
- const keys = Object.keys(data)
- const values = Object.values(data)
- const sql = `
- INSERT INTO ${table} (${keys.map((k) => `"${k}"`).join(', ')})
- VALUES (${keys.map((_, idx) => `$${idx + 1}`).join(', ')})
- RETURNING ${returnField};`
- const result = await this.client.unsafe(sql, values)
-
- return result[0][returnField]
- }
-
- async remove(table: string, where: Record): Promise {
- const keys = Object.keys(where)
- const values = Object.values(where)
-
- if (keys.length === 0) {
- throw new Error("WHERE condition cannot be empty");
- }
-
- const sql = `
- DELETE
- FROM ${table}
- WHERE ${keys.map((k, idx) => `"${k}" = $${idx + 1}`).join(' AND ')};`
-
- await this.client.unsafe(sql, values)
- }
-
- async removeWithReturn(table: string, where: Record, returnField: string): Promise {
- const keys = Object.keys(where);
- const values: any[] = [];
-
- if (keys.length === 0) {
- throw new Error("WHERE condition cannot be empty");
- }
-
- const whereClause = keys.map((key) => {
- const value = where[key];
- if (Array.isArray(value)) {
- const placeholders = value.map((_, i) => `$${values.length + i + 1}`).join(", ");
- values.push(...value);
- return `"${key}" IN (${placeholders})`;
- } else {
- values.push(value);
- return `"${key}" = $${values.length}`;
- }
- }).join(" AND ");
-
- const sql = `
- DELETE FROM ${table}
- WHERE ${whereClause}
- RETURNING ${returnField};
- `;
-
- const result = await this.client.unsafe(sql, values);
-
- return result.map((it: any) => it[returnField]);
+ return await this.client.execute(sql, params)
+ }
+
+ private async executeWithLogs)[]>(
+ name: string,
+ logger: Logger,
+ sql: string,
+ params?: ParameterOrJSON[]
+ ): Promise {
+ const start = performance.now()
+
+ try {
+ return await this.client.execute(sql, params)
+ } finally {
+ const time = performance.now() - start
+ logger.info(name, { time })
}
+ }
}
diff --git a/packages/cockroach/src/db/message.ts b/packages/cockroach/src/db/message.ts
index 79462c4372..1ddad0af65 100644
--- a/packages/cockroach/src/db/message.ts
+++ b/packages/cockroach/src/db/message.ts
@@ -1,302 +1,481 @@
import {
- type Message,
- type MessageID,
- type CardID,
- type FindMessagesParams,
- SortOrder,
- type SocialID,
- type RichText,
- Direction,
- type BlobID,
- type MessagesGroup,
- type FindMessagesGroupsParams
+ type Message,
+ type MessageID,
+ type CardID,
+ type FindMessagesParams,
+ type SocialID,
+ type RichText,
+ SortingOrder,
+ PatchType,
+ type Thread,
+ type BlobID,
+ type FindMessagesGroupsParams,
+ type MessagesGroup
} from '@hcengineering/communication-types'
-import {generateMessageId} from "@hcengineering/communication-core";
+import { generateMessageId } from '@hcengineering/communication-shared'
-import {BaseDb} from './base.ts'
+import { BaseDb } from './base'
import {
- TableName,
- type MessageDb,
- type AttachmentDb,
- type ReactionDb,
- type PatchDb,
- type MessagesGroupDb,
- toMessage,
- toMessagesGroup
-} from './schema.ts'
-import {getCondition} from './utils.ts';
-
+ TableName,
+ type MessageDb,
+ type AttachmentDb,
+ type ReactionDb,
+ type PatchDb,
+ toMessage,
+ type ThreadDb,
+ toThread,
+ type MessagesGroupDb,
+ toMessagesGroup
+} from './schema'
+import { getCondition } from './utils'
export class MessagesDb extends BaseDb {
- //Message
- async createMessage(card: CardID, content: RichText, creator: SocialID, created: Date): Promise {
- const dbData: MessageDb = {
- id: generateMessageId(),
- workspace_id: this.workspace,
- card_id: card,
- content: content,
- creator: creator,
- created: created,
- }
-
- await this.insert(TableName.Message, dbData)
-
- return dbData.id as MessageID
+ // Message
+ async createMessage (card: CardID, content: RichText, creator: SocialID, created: Date): Promise {
+ const id = generateMessageId()
+ const db: MessageDb = {
+ id,
+ workspace_id: this.workspace,
+ card_id: card,
+ content,
+ creator,
+ created
}
- async removeMessage(card: CardID, message: MessageID): Promise {
- const result = await this.removeWithReturn(TableName.Message, {id: message, workspace_id: this.workspace, card_id: card}, "id")
- return result[0] as MessageID | undefined
+ const sql = `INSERT INTO ${TableName.Message} (workspace_id, card_id, id, content, creator, created)
+ VALUES ($1::uuid, $2::varchar, $3::bigint, $4::text, $5::varchar, $6::timestamptz)`
+
+ await this.execute(sql, [db.workspace_id, db.card_id, db.id, db.content, db.creator, db.created], 'insert message')
+
+ return id
+ }
+
+ async removeMessage (card: CardID, message: MessageID, socialIds?: SocialID[]): Promise {
+ if (socialIds === undefined || socialIds.length === 0) {
+ const sql = `DELETE
+ FROM ${TableName.Message}
+ WHERE workspace_id = $1::uuid
+ AND card_id = $2::varchar
+ AND id = $2::bigint;`
+ await this.execute(sql, [this.workspace, card, message], 'remove message')
+ } else if (socialIds.length === 1) {
+ const sql = `DELETE
+ FROM ${TableName.Message}
+ WHERE workspace_id = $1::uuid
+ AND card_id = $2::varchar
+ AND id = $2::bigint
+ AND creator = $3::varchar;`
+ await this.execute(sql, [this.workspace, card, message, socialIds[0]], 'remove message')
+ } else {
+ const sql = `DELETE
+ FROM ${TableName.Message}
+ WHERE workspace_id = $1::uuid
+ AND card_id = $2::varchar
+ AND id = $2::bigint
+ AND creator = ANY ($3::varchar[]);`
+
+ await this.execute(sql, [this.workspace, card, message, socialIds], 'remove message')
+ }
+ }
+
+ async removeMessages (card: CardID, fromId: MessageID, toId: MessageID): Promise {
+ const sql = `DELETE
+ FROM ${TableName.Message}
+ WHERE workspace_id = $1::uuid
+ AND card_id = $2::varchar
+ AND id >= $3::bigint
+ AND id <= $4::bigint;`
+
+ await this.execute(sql, [this.workspace, card, BigInt(fromId), BigInt(toId)], 'remove messages')
+ }
+
+ async createPatch (
+ card: CardID,
+ message: MessageID,
+ type: PatchType,
+ content: string,
+ creator: SocialID,
+ created: Date
+ ): Promise {
+ const db: PatchDb = {
+ workspace_id: this.workspace,
+ card_id: card,
+ message_id: message,
+ type,
+ content,
+ creator,
+ created
}
- async removeMessages(card: CardID, ids: MessageID[]): Promise {
- const result = await this.removeWithReturn(TableName.Message, {
- workspace_id: this.workspace,
- card_id: card,
- id: ids
- }, "id")
- return result.map((it: any) => it.id)
+ const sql = `INSERT INTO ${TableName.Patch} (workspace_id, card_id, message_id, type, content, creator, created)
+ VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::text, $6::varchar, $7::timestamptz)`
+
+ await this.execute(
+ sql,
+ [db.workspace_id, db.card_id, db.message_id, db.type, db.content, db.creator, db.created],
+ 'insert patch'
+ )
+ }
+
+ async removePatches (card: CardID, fromId: MessageID, toId: MessageID): Promise {
+ const sql = `DELETE
+ FROM ${TableName.Patch}
+ WHERE workspace_id = $1::uuid
+ AND card_id = $2::varchar
+ AND message_id >= $3::bigint
+ AND message_id <= $4::bigint;`
+
+ await this.execute(sql, [this.workspace, card, BigInt(fromId), BigInt(toId)], 'remove patches')
+ }
+
+ // Attachment
+ async createAttachment (message: MessageID, card: CardID, creator: SocialID, created: Date): Promise {
+ const db: AttachmentDb = {
+ message_id: message,
+ card_id: card,
+ creator,
+ created
+ }
+ const sql = `INSERT INTO ${TableName.Attachment} (message_id, card_id, creator, created)
+ VALUES ($1::bigint, $2::varchar, $3::varchar, $4::timestamptz)`
+
+ await this.execute(sql, [db.message_id, db.card_id, db.creator, db.created], 'insert attachment')
+ }
+
+ async removeAttachment (message: MessageID, card: CardID): Promise {
+ const sql = `DELETE
+ FROM ${TableName.Attachment}
+ WHERE message_id = $1::bigint
+ AND card_id = $2::varchar`
+ await this.execute(sql, [message, card], 'remove attachment')
+ }
+
+ // Reaction
+ async createReaction (
+ card: CardID,
+ message: MessageID,
+ reaction: string,
+ creator: SocialID,
+ created: Date
+ ): Promise {
+ const select = `SELECT m.id
+ FROM ${TableName.Message} m
+ WHERE m.id = $1::bigint`
+
+ const messageDb = await this.execute(select, [message], 'select message')
+
+ if (messageDb.length > 0) {
+ const db: ReactionDb = {
+ workspace_id: this.workspace,
+ card_id: card,
+ message_id: message,
+ reaction,
+ creator,
+ created
+ }
+ const sql = `INSERT INTO ${TableName.Reaction} (workspace_id, card_id, message_id, reaction, creator, created)
+ VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::varchar, $6::timestamptz)`
+
+ await this.execute(
+ sql,
+ [db.workspace_id, db.card_id, db.message_id, db.reaction, db.creator, db.created],
+ 'insert reaction'
+ )
+ } else {
+ await this.createPatch(card, message, PatchType.addReaction, reaction, creator, created)
+ }
+ }
+
+ async removeReaction (
+ card: CardID,
+ message: MessageID,
+ reaction: string,
+ creator: SocialID,
+ created: Date
+ ): Promise {
+ const select = `SELECT m.id
+ FROM ${TableName.Message} m
+ WHERE m.id = $1::bigint`
+
+ const messageDb = await this.execute(select, [message], 'select message')
+
+ if (messageDb.length > 0) {
+ const sql = `DELETE
+ FROM ${TableName.Reaction}
+ WHERE workspace_id = $1::uuid
+ AND card_id = $2::varchar
+ AND message_id = $3::bigint
+ AND reaction = $4::varchar
+ AND creator = $5::varchar`
+ await this.execute(sql, [this.workspace, card, message, reaction, creator], 'remove reaction')
+ } else {
+ await this.createPatch(card, message, PatchType.removeReaction, reaction, creator, created)
+ }
+ }
+
+ // Thread
+ async createThread (card: CardID, message: MessageID, thread: CardID, created: Date): Promise {
+ const db: ThreadDb = {
+ workspace_id: this.workspace,
+ card_id: card,
+ message_id: message,
+ thread_id: thread,
+ replies_count: 0,
+ last_reply: created
+ }
+ const sql = `INSERT INTO ${TableName.Thread} (workspace_id, card_id, message_id, thread_id, replies_count,
+ last_reply)
+ VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::int, $6::timestamptz)`
+ await this.execute(
+ sql,
+ [db.workspace_id, db.card_id, db.message_id, db.thread_id, db.replies_count, db.last_reply],
+ 'insert thread'
+ )
+ }
+
+ async updateThread (thread: CardID, lastReply: Date, op: 'increment' | 'decrement'): Promise {
+ if (op === 'increment') {
+ const sql = `UPDATE ${TableName.Thread}
+ SET replies_count = replies_count + 1,
+ last_reply = $3::timestamptz
+ WHERE workspace_id = $1::uuid
+ AND thread_id = $2::varchar`
+ await this.execute(sql, [this.workspace, thread, lastReply], 'update thread')
+ } else if (op === 'decrement') {
+ const sql = `UPDATE ${TableName.Thread}
+ SET replies_count = GREATEST(replies_count - 1, 0)
+ WHERE workspace_id = $1::uuid
+ AND thread_id = $2::varchar`
+ await this.execute(sql, [this.workspace, thread], 'update thread')
+ }
+ }
+
+ // MessagesGroup
+ async createMessagesGroup (
+ card: CardID,
+ blobId: BlobID,
+ fromDate: Date,
+ toDate: Date,
+ fromId: MessageID,
+ toId: MessageID,
+ count: number
+ ): Promise {
+ const db: MessagesGroupDb = {
+ workspace_id: this.workspace,
+ card_id: card,
+ blob_id: blobId,
+ from_date: fromDate,
+ to_date: toDate,
+ from_id: fromId,
+ to_id: toId,
+ count
}
- async createPatch(card: CardID, message: MessageID, content: RichText, creator: SocialID, created: Date): Promise {
- const dbData: PatchDb = {
- workspace_id: this.workspace,
- card_id: card,
- message_id: message,
- content: content,
- creator: creator,
- created: created
- }
+ const sql = `INSERT INTO ${TableName.MessagesGroup} (workspace_id, card_id, blob_id, from_date, to_date, from_id,
+ to_id, count)
+ VALUES ($1::uuid, $2::varchar, $3::uuid, $4::timestamptz, $5::timestamptz, $6::bigint, $7::bigint,
+ $8::int)`
+ await this.execute(
+ sql,
+ [db.workspace_id, db.card_id, db.blob_id, db.from_date, db.to_date, db.from_id, db.to_id, db.count],
+ 'insert messages group'
+ )
+ }
- await this.insert(TableName.Patch, dbData)
+ async removeMessagesGroup (card: CardID, blobId: BlobID): Promise {
+ const sql = `DELETE
+ FROM ${TableName.MessagesGroup}
+ WHERE workspace_id = $1::uuid
+ AND card_id = $2::varchar
+ AND blob_id = $3::uuid`
+ await this.execute(sql, [this.workspace, card, blobId], 'remove messages group')
+ }
+
+ // Find messages
+ async find (params: FindMessagesParams): Promise {
+ // TODO: experiment with select to improve performance
+ const select = `SELECT m.id,
+ m.card_id,
+ m.content,
+ m.creator,
+ m.created,
+ t.thread_id as thread_id,
+ t.replies_count as replies_count,
+ t.last_reply as last_reply,
+ ${this.subSelectPatches()},
+ ${this.subSelectReactions()}
+ FROM ${TableName.Message} m
+ LEFT JOIN ${TableName.Thread} t
+ ON t.workspace_id = m.workspace_id AND t.card_id = m.card_id AND
+ t.message_id = m.id`
+
+ const { where, values } = this.buildMessageWhere(params)
+ const orderBy =
+ params.order != null ? `ORDER BY m.created ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` : ''
+ const limit = params.limit != null ? ` LIMIT ${params.limit}` : ''
+ const sql = [select, where, orderBy, limit].join(' ')
+
+ const result = await this.execute(sql, values, 'find messages')
+
+ return result.map((it: any) => toMessage(it))
+ }
+
+ buildMessageWhere (params: FindMessagesParams): { where: string, values: any[] } {
+ const where: string[] = ['m.workspace_id = $1::uuid']
+ const values: any[] = [this.workspace]
+
+ let index = 2
+
+ if (params.id != null) {
+ where.push(`m.id = $${index++}::bigint`)
+ values.push(params.id)
}
- //MessagesGroup
- async createMessagesGroup(card: CardID, blobId: BlobID, from_id: MessageID, to_id: MessageID, from_date: Date, to_date: Date, count: number): Promise {
- const dbData: MessagesGroupDb = {
- workspace_id: this.workspace,
- card_id: card,
- blob_id: blobId,
- from_id,
- to_id,
- from_date,
- to_date,
- count
- }
- await this.insert(TableName.MessagesGroup, dbData)
+ if (params.card != null) {
+ where.push(`m.card_id = $${index++}::varchar`)
+ values.push(params.card)
}
- //Attachment
- async createAttachment(message: MessageID, card: CardID, creator: SocialID, created: Date): Promise {
- const dbData: AttachmentDb = {
- message_id: message,
- card_id: card,
- creator: creator,
- created: created
- }
- await this.insert(TableName.Attachment, dbData)
+ const createdCondition = getCondition('m', 'created', index, params.created, 'timestamptz')
+
+ if (createdCondition != null) {
+ where.push(createdCondition.where)
+ values.push(createdCondition.value)
+ index++
}
- async removeAttachment(message: MessageID, card: CardID): Promise {
- await this.remove(TableName.Attachment, {
- message_id: message,
- card_id: card
- })
+ return { where: `WHERE ${where.join(' AND ')}`, values }
+ }
+
+ subSelectPatches (): string {
+ return `COALESCE(
+ (SELECT jsonb_agg(jsonb_build_object(
+ 'content', p.content,
+ 'creator', p.creator,
+ 'created', p.created
+ ) ORDER BY p.created DESC)
+ FROM ${TableName.Patch} p
+ WHERE p.message_id = m.id
+ AND p.workspace_id = m.workspace_id
+ AND p.card_id = m.card_id
+ AND p.type = 'update'
+ ), '[]'::jsonb) AS patches`
+ }
+
+ subSelectAttachments (): string {
+ return `COALESCE(
+ (SELECT jsonb_agg(jsonb_build_object(
+ 'card_id', a.card_id,
+ 'message_id', a.message_id,
+ 'creator', a.creator,
+ 'created', a.created
+ ))
+ FROM ${TableName.Attachment} a
+ WHERE a.message_id = m.id
+ ), '[]'::jsonb) AS attachments`
+ }
+
+ subSelectReactions (): string {
+ return `COALESCE(
+ (SELECT jsonb_agg(jsonb_build_object(
+ 'message_id', r.message_id,
+ 'reaction', r.reaction,
+ 'creator', r.creator,
+ 'created', r.created
+ ))
+ FROM ${TableName.Reaction} r
+ WHERE r.workspace_id = m.workspace_id
+ AND r.card_id = m.card_id
+ AND r.message_id = m.id
+ ), '[]'::jsonb) AS reactions`
+ }
+
+ // Find thread
+ async findThread (thread: CardID): Promise {
+ const sql = `SELECT t.card_id,
+ t.message_id,
+ t.thread_id,
+ t.replies_count,
+ t.last_reply
+ FROM ${TableName.Thread} t
+ WHERE t.workspace_id = $1::uuid
+ AND t.thread_id = $2::varchar
+ LIMIT 1;`
+
+ const result = await this.execute(sql, [this.workspace, thread], 'find thread')
+ return result.map((it: any) => toThread(it))[0]
+ }
+
+ // Find messages groups
+ async findMessagesGroups (params: FindMessagesGroupsParams): Promise {
+ const select = `SELECT mg.card_id,
+ mg.blob_id,
+ mg.from_date,
+ mg.to_date,
+ mg.from_id,
+ mg.to_id,
+ mg.count,
+ jsonb_agg(jsonb_build_object(
+ 'message_id', p.message_id,
+ 'type', p.type,
+ 'content', p.content,
+ 'creator', p.creator,
+ 'created', p.created
+ ) ORDER BY p.created) AS patches
+ FROM ${TableName.MessagesGroup} mg
+ LEFT JOIN ${TableName.Patch} p
+ ON p.workspace_id = mg.workspace_id
+ AND p.card_id = mg.card_id
+ AND p.message_id BETWEEN mg.from_id AND mg.to_id`
+
+ const { where, values } = this.buildMessagesGroupWhere(params)
+ const orderBy =
+ params.orderBy === 'toDate'
+ ? `ORDER BY mg.to_date ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}`
+ : `ORDER BY mg.from_date ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}`
+ const groupBy = 'GROUP BY mg.card_id, mg.blob_id, mg.from_date, mg.to_date, mg.from_id, mg.to_id, mg.count'
+
+ const limit = params.limit != null ? ` LIMIT ${params.limit}` : ''
+ const sql = [select, where, groupBy, orderBy, limit].join(' ')
+
+ const result = await this.execute(sql, values, 'find messages groups')
+
+ return result.map((it: any) => toMessagesGroup(it))
+ }
+
+ buildMessagesGroupWhere (params: FindMessagesGroupsParams): {
+ where: string
+ values: any[]
+ } {
+ const where: string[] = ['mg.workspace_id = $1::uuid']
+ const values: any[] = [this.workspace]
+
+ let index = 2
+
+ if (params.card != null) {
+ where.push(`mg.card_id = $${index++}::varchar`)
+ values.push(params.card)
}
- //Reaction
- async createReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID, created: Date): Promise {
- const dbData: ReactionDb = {
- workspace_id: this.workspace,
- card_id: card,
- message_id: message,
- reaction: reaction,
- creator: creator,
- created: created
- }
- await this.insert(TableName.Reaction, dbData)
+ if (params.blobId != null) {
+ where.push(`mg.blob_id = $${index++}`)
+ values.push(params.blobId)
}
- async removeReaction( card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise {
- await this.remove(TableName.Reaction, {
- workspace_id: this.workspace,
- card_id: card,
- message_id: message,
- reaction: reaction,
- creator: creator
- })
+ const fromDateCondition = getCondition('mg', 'from_date', index, params.fromDate, 'timestamptz')
+ if (fromDateCondition != null) {
+ where.push(fromDateCondition.where)
+ values.push(fromDateCondition.value)
+ index++
}
- //Find messages
- async find(params: FindMessagesParams): Promise {
- //TODO: experiment with select to improve performance
- const select = `SELECT m.id,
- m.card_id,
- m.content,
- m.creator,
- m.created,
- ${this.subSelectPatches()},
- ${this.subSelectAttachments()},
- ${this.subSelectReactions()}
- FROM ${TableName.Message} m`
-
- const {where, values} = this.buildMessageWhere( params)
- const orderBy = params.sort ? `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` : ''
- const limit = params.limit ? ` LIMIT ${params.limit}` : ''
- const sql = [select, where, orderBy, limit].join(' ')
-
- const result = await this.client.unsafe(sql, values)
-
- return result.map((it: any) => toMessage(it))
+ const toDateCondition = getCondition('mg', 'to_date', index, params.toDate, 'timestamptz')
+ if (toDateCondition != null) {
+ where.push(toDateCondition.where)
+ values.push(toDateCondition.value)
+ index++
}
- buildMessageWhere(params: FindMessagesParams): { where: string, values: any[] } {
- const where: string[] = ['m.workspace_id = $1']
- const values: any[] = [this.workspace]
-
- let index = 2
-
- if (params.id != null) {
- where.push(`m.id = $${index++}`)
- values.push(params.id)
- }
-
- if (params.card != null) {
- where.push(`m.card_id = $${index++}`)
- values.push(params.card)
- }
-
- if (params.from != null) {
- const exclude = params.excluded ?? false
- const direction = params.direction ?? Direction.Forward
- const getOperator = () => {
- if (exclude) {
- return direction === Direction.Forward ? '>' : '<'
- } else {
- return direction === Direction.Forward ? '>=' : '<='
- }
- }
-
- where.push(`m.created ${getOperator()} $${index++}`)
- values.push(params.from)
- }
-
-
- return {where: `WHERE ${where.join(' AND ')}`, values}
- }
-
- subSelectPatches(): string {
- return `array(
- SELECT jsonb_build_object(
- 'content', p.content,
- 'creator', p.creator,
- 'created', p.created
- )
- FROM ${TableName.Patch} p
- WHERE p.message_id = m.id AND p.workspace_id = m.workspace_id AND p.card_id = m.card_id
- ) AS patches`
- }
-
- subSelectAttachments(): string {
- return `array(
- SELECT jsonb_build_object(
- 'card_id', a.card_id,
- 'message_id', a.message_id,
- 'creator', a.creator,
- 'created', a.created
- )
- FROM ${TableName.Attachment} a
- WHERE a.message_id = m.id
- ) AS attachments`
- }
-
- subSelectReactions(): string {
- return `array(
- SELECT jsonb_build_object(
- 'message_id', r.message_id,
- 'reaction', r.reaction,
- 'creator', r.creator,
- 'created', r.created
- )
- FROM ${TableName.Reaction} r
- WHERE r.message_id = m.id AND r.workspace_id = m.workspace_id AND r.card_id = m.card_id
- ) AS reactions`
- }
-
-
- //Find messages groups
- async findGroups(params: FindMessagesGroupsParams): Promise {
- const select = `SELECT mg.card_id,
- mg.blob_id,
- mg.from_id,
- mg.to_id,
- mg.count
- FROM ${TableName.MessagesGroup} mg`
-
- const {where, values, index} = this.buildMessagesGroupWhere(this.workspace, params)
- const orderBy = params.sortBy ? `ORDER BY ${index} ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` : ''
- if(params.sortBy) {
- values.push(params.sortBy)
- }
- const limit = params.limit ? ` LIMIT ${params.limit}` : ''
- const sql = [select, where, orderBy, limit].join(' ')
-
- const result = await this.client.unsafe(sql, values)
-
- return result.map((it: any) => toMessagesGroup(it))
- }
-
- buildMessagesGroupWhere(workspace: string, params: FindMessagesGroupsParams): {
- where: string,
- values: any[],
- index: number
- } {
- const where: string[] = ['mg.workspace_id = $1']
- const values: any[] = [workspace]
-
- let index = 2
-
- if (params.card != null) {
- where.push(`mg.card_id = $${index++}`)
- values.push(params.card)
- }
-
- if (params.blobId != null) {
- where.push(`mg.blob_id = $${index++}`)
- values.push(params.blobId)
- }
-
- const fromIdCondition = getCondition("mg", "from_id", index, params.fromId);
- if (fromIdCondition != null) {
- where.push(fromIdCondition.where);
- values.push(fromIdCondition.value);
- index++;
- }
-
- const toIdCondition = getCondition("mg", "to_id", index, params.toId);
-
- if (toIdCondition != null) {
- where.push(toIdCondition.where);
- values.push(toIdCondition.value);
- index++;
- }
-
- const fromDateCondition = getCondition("mg", "from_date", index, params.fromDate);
- if (fromDateCondition != null) {
- where.push(fromDateCondition.where);
- values.push(fromDateCondition.value);
- index++;
- }
-
- const toDateCondition = getCondition("mg", "to_date", index, params.toDate);
- if (toDateCondition != null) {
- where.push(toDateCondition.where);
- values.push(toDateCondition.value);
- index++;
- }
-
-
- return {where: `WHERE ${where.join(' AND ')}`, values, index}
- }
+ return { where: `WHERE ${where.join(' AND ')}`, values }
+ }
}
-
diff --git a/packages/cockroach/src/db/notification.ts b/packages/cockroach/src/db/notification.ts
index ba43fa769e..705edc24f7 100644
--- a/packages/cockroach/src/db/notification.ts
+++ b/packages/cockroach/src/db/notification.ts
@@ -1,95 +1,120 @@
import {
- type MessageID,
- type ContextID,
- type CardID,
- type NotificationContext,
- type FindNotificationContextParams, SortOrder,
- type FindNotificationsParams, type Notification,
- type NotificationContextUpdate,
- type WorkspaceID
+ type MessageID,
+ type ContextID,
+ type CardID,
+ type NotificationContext,
+ type FindNotificationContextParams,
+ SortingOrder,
+ type FindNotificationsParams,
+ type Notification,
+ type NotificationContextUpdate,
+ type WorkspaceID
} from '@hcengineering/communication-types'
-import {BaseDb} from './base.ts'
-import {TableName, type ContextDb, type NotificationDb} from './schema.ts'
+import { BaseDb } from './base'
+import { TableName, type ContextDb, type NotificationDb } from './schema'
export class NotificationsDb extends BaseDb {
- async createNotification(message: MessageID, context: ContextID): Promise {
- const dbData: NotificationDb = {
- message_id: message,
- context
- }
- await this.insert(TableName.Notification, dbData)
+ async createNotification (message: MessageID, context: ContextID): Promise {
+ const db: NotificationDb = {
+ message_id: message,
+ context
+ }
+ const sql = `INSERT INTO ${TableName.Notification} (message_id, context_id)
+ VALUES ($1::bigint, $2::uuid)`
+ await this.execute(sql, [db.message_id, db.context], 'insert notification')
+ }
+
+ async removeNotification (message: MessageID, context: ContextID): Promise {
+ // TODO: do we need to remove notifications?
+ const sql = `DELETE
+ FROM ${TableName.NotificationContext}
+ WHERE id = $1::uuid`
+ await this.execute(sql, [context], 'remove notification')
+ }
+
+ async createContext (
+ personalWorkspace: WorkspaceID,
+ card: CardID,
+ lastView?: Date,
+ lastUpdate?: Date
+ ): Promise {
+ const db: ContextDb = {
+ workspace_id: this.workspace,
+ card_id: card,
+ personal_workspace: personalWorkspace,
+ last_view: lastView,
+ last_update: lastUpdate
+ }
+ const sql = `INSERT INTO ${TableName.NotificationContext} (workspace_id, card_id, personal_workspace, last_view, last_update)
+ VALUES ($1::uuid, $2::varchar, $3::uuid, $4::timestamptz, $5::timestamptz)
+ RETURNING id`
+ const result = await this.execute(
+ sql,
+ [db.workspace_id, db.card_id, db.personal_workspace, db.last_view, db.last_update],
+ 'insert notification context'
+ )
+ return result[0].id as ContextID
+ }
+
+ async removeContext (context: ContextID): Promise {
+ const sql = `DELETE
+ FROM ${TableName.Notification}
+ WHERE context = $1::uuid`
+ await this.execute(sql, [context], 'remove notification context')
+ }
+
+ async updateContext (context: ContextID, update: NotificationContextUpdate): Promise {
+ const dbData: Partial = {}
+
+ if (update.archivedFrom != null) {
+ dbData.archived_from = update.archivedFrom
+ }
+ if (update.lastView != null) {
+ dbData.last_view = update.lastView
+ }
+ if (update.lastUpdate != null) {
+ dbData.last_update = update.lastUpdate
}
- async removeNotification(message: MessageID, context: ContextID): Promise {
- await this.remove(TableName.Notification, {
- message_id: message,
- context
- })
+ if (Object.keys(dbData).length === 0) {
+ return
}
- async createContext(personalWorkspace: WorkspaceID,card: CardID, lastView?: Date, lastUpdate?: Date): Promise {
- const dbData: ContextDb = {
- workspace_id: this.workspace,
- card_id: card,
- personal_workspace: personalWorkspace,
- last_view: lastView,
- last_update: lastUpdate
- }
- return await this.insertWithReturn(TableName.NotificationContext, dbData, 'id') as ContextID
- }
+ const keys = Object.keys(dbData)
+ const values = Object.values(dbData)
- async removeContext(context: ContextID): Promise {
- await this.remove(TableName.NotificationContext, {
- id: context
- })
- }
+ const sql = `UPDATE ${TableName.NotificationContext}
+ SET ${keys.map((k, idx) => `"${k}" = $${idx + 1}::timestamptz`).join(', ')}
+ WHERE id = $${keys.length + 1}::uuid;`
+ await this.execute(sql, [values, context], 'update notification context')
+ }
- async updateContext(context: ContextID, update: NotificationContextUpdate): Promise {
- const dbData: Partial = {}
-
- if (update.archivedFrom != null) {
- dbData.archived_from = update.archivedFrom
- }
- if (update.lastView != null) {
- dbData.last_view = update.lastView
- }
- if (update.lastUpdate != null) {
- dbData.last_update = update.lastUpdate
- }
-
- if (Object.keys(dbData).length === 0) {
- return
- }
-
- const keys = Object.keys(dbData)
- const values = Object.values(dbData)
-
- const sql = `UPDATE ${TableName.NotificationContext}
- SET ${keys.map((k, idx) => `"${k}" = $${idx + 1}`).join(', ')}
- WHERE id = $${keys.length + 1}`
-
- await this.client.unsafe(sql, [values, context])
- }
-
- async findContexts(params: FindNotificationContextParams, personalWorkspaces: WorkspaceID[], workspace?: WorkspaceID,): Promise {
- const select = `
+ async findContexts (
+ params: FindNotificationContextParams,
+ personalWorkspaces: WorkspaceID[],
+ workspace?: WorkspaceID
+ ): Promise {
+ const select = `
SELECT nc.id, nc.card_id, nc.archived_from, nc.last_view, nc.last_update
- FROM ${TableName.NotificationContext} nc`;
- const {where, values} = this.buildContextWhere(params, personalWorkspaces, workspace)
- // const orderSql = `ORDER BY nc.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}`
- const limit = params.limit ? ` LIMIT ${params.limit}` : ''
- const sql = [select, where, limit].join(' ')
+ FROM ${TableName.NotificationContext} nc`
+ const { where, values } = this.buildContextWhere(params, personalWorkspaces, workspace)
+ // const orderSql = `ORDER BY nc.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}`
+ const limit = params.limit != null ? ` LIMIT ${params.limit}` : ''
+ const sql = [select, where, limit].join(' ')
- const result = await this.client.unsafe(sql, values);
+ const result = await this.execute(sql, values, 'find notification contexts')
- return result.map(this.toNotificationContext);
- }
+ return result.map((it) => this.toNotificationContext(it))
+ }
-
- async findNotifications(params: FindNotificationsParams, personalWorkspace: WorkspaceID, workspace?: WorkspaceID): Promise {
- //TODO: experiment with select to improve performance, should join with attachments and reactions?
- const select = `
+ async findNotifications (
+ params: FindNotificationsParams,
+ personalWorkspace: WorkspaceID,
+ workspace?: WorkspaceID
+ ): Promise {
+ // TODO: experiment with select to improve performance, should join with attachments and reactions?
+ const select = `
SELECT n.message_id,
n.context,
m.card_id AS message_card,
@@ -113,115 +138,121 @@ export class NotificationsDb extends BaseDb {
FROM ${TableName.Notification} n
JOIN ${TableName.NotificationContext} nc ON n.context = nc.id
JOIN ${TableName.Message} m ON n.message_id = m.id
- `;
- const {where, values} = this.buildNotificationWhere(params, personalWorkspace, workspace)
- const orderBy = params.sort ? `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` : ''
- const limit = params.limit ? ` LIMIT ${params.limit}` : ''
- const sql = [select, where, orderBy, limit].join(' ')
+ `
+ const { where, values } = this.buildNotificationWhere(params, personalWorkspace, workspace)
+ const orderBy =
+ params.order != null ? `ORDER BY m.created ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` : ''
+ const limit = params.limit != null ? ` LIMIT ${params.limit}` : ''
+ const sql = [select, where, orderBy, limit].join(' ')
- const result = await this.client.unsafe(sql, values);
+ const result = await this.execute(sql, values, 'find notifications')
- return result.map(this.toNotification);
- }
+ return result.map((it) => this.toNotification(it))
+ }
- buildContextWhere(params: FindNotificationContextParams, personalWorkspaces: WorkspaceID[], workspace?: WorkspaceID): {
- where: string,
- values: any[]
+ buildContextWhere (
+ params: FindNotificationContextParams,
+ personalWorkspaces: WorkspaceID[],
+ workspace?: WorkspaceID
+ ): {
+ where: string
+ values: any[]
} {
- const where: string[] = []
- const values: any[] = []
- let index = 1
+ const where: string[] = []
+ const values: any[] = []
+ let index = 1
- if (workspace != null) {
- where.push(`nc.workspace_id = $${index++}`)
- values.push(workspace)
- }
-
- if (personalWorkspaces.length > 0) {
- where.push(`nc.personal_workspace IN (${personalWorkspaces.map((it) => `$${index++}`).join(', ')})`)
- values.push(...personalWorkspaces)
- }
-
- if (params.card != null) {
- where.push(`nc.card_id = $${index++}`)
- values.push(params.card)
- }
-
- return {where: `WHERE ${where.join(' AND ')}`, values}
+ if (workspace != null) {
+ where.push(`nc.workspace_id = $${index++}::uuid`)
+ values.push(workspace)
}
- buildNotificationWhere(params: FindNotificationsParams, personalWorkspace: WorkspaceID, workspace?: WorkspaceID): {
- where: string,
- values: any[]
+ if (personalWorkspaces.length > 0) {
+ where.push(`nc.personal_workspace IN (${personalWorkspaces.map((it) => `$${index++}::uuid`).join(', ')})`)
+ values.push(...personalWorkspaces)
+ }
+
+ if (params.card != null) {
+ where.push(`nc.card_id = $${index++}::varchar`)
+ values.push(params.card)
+ }
+
+ return { where: `WHERE ${where.join(' AND ')}`, values }
+ }
+
+ buildNotificationWhere (
+ params: FindNotificationsParams,
+ personalWorkspace: WorkspaceID,
+ workspace?: WorkspaceID
+ ): {
+ where: string
+ values: any[]
} {
- const where: string[] = ['nc.personal_workspace = $1']
- const values: any[] = [personalWorkspace]
- let index = 2
+ const where: string[] = ['nc.personal_workspace = $1::uuid']
+ const values: any[] = [personalWorkspace]
+ let index = 2
- if (workspace != null) {
- where.push(`nc.workspace_id = $${index++}`)
- values.push(workspace)
- }
-
- if (params.context != null) {
- where.push(`n.context = $${index++}`)
- values.push(params.context)
- }
-
-
- if (params.read === true) {
- where.push(`nc.last_view IS NOT NULL AND nc.last_view >= m.created`)
- }
-
- if (params.read === false) {
- where.push(`(nc.last_view IS NULL OR nc.last_view > m.created)`)
- }
-
- if (params.archived === true) {
- where.push(`nc.archived_from IS NOT NULL AND nc.archived_from >= m.created`)
- }
-
- if (params.archived === false) {
- where.push(`(nc.archived_from IS NULL OR nc.archived_from > m.created)`)
- }
-
-
- return {where: `WHERE ${where.join(' AND ')}`, values}
+ if (workspace != null) {
+ where.push(`nc.workspace_id = $${index++}::uuid`)
+ values.push(workspace)
}
- toNotificationContext(row: any): NotificationContext {
- return {
- id: row.id,
- card: row.card_id,
- workspace: row.workspace_id,
- personalWorkspace: row.personal_workspace,
- archivedFrom: row.archived_from ? new Date(row.archived_from) : undefined,
- lastView: row.last_view ? new Date(row.last_view) : undefined,
- lastUpdate: row.last_update ? new Date(row.last_update) : undefined
- }
+ if (params.context != null) {
+ where.push(`n.context = $${index++}::uuid`)
+ values.push(params.context)
}
- toNotification(row: any): Notification {
- const lastPatch = row.patches?.[0]
- const lastView = row.last_view ? new Date(row.last_view) : undefined
- const archivedFrom = row.archived_from ? new Date(row.archived_from) : undefined
- const created = new Date(row.message_created)
-
- return {
- message: {
- id: row.id,
- card: row.message_card,
- content: lastPatch?.content ?? row.message_content,
- creator: row.message_creator,
- created,
- edited: new Date(lastPatch?.created ?? row.message_created),
- reactions: row.reactions ?? [],
- attachments: row.attachments ?? []
- },
- context: row.context,
- read: lastView != null && lastView >= created,
- archived: archivedFrom != null && archivedFrom >= created
- }
+ if (params.read === true) {
+ where.push('nc.last_view IS NOT NULL AND nc.last_view >= m.created')
}
+
+ if (params.read === false) {
+ where.push('(nc.last_view IS NULL OR nc.last_view > m.created)')
+ }
+
+ if (params.archived === true) {
+ where.push('nc.archived_from IS NOT NULL AND nc.archived_from >= m.created')
+ }
+
+ if (params.archived === false) {
+ where.push('(nc.archived_from IS NULL OR nc.archived_from > m.created)')
+ }
+
+ return { where: `WHERE ${where.join(' AND ')}`, values }
+ }
+
+ toNotificationContext (row: any): NotificationContext {
+ return {
+ id: row.id,
+ card: row.card_id,
+ workspace: row.workspace_id,
+ personalWorkspace: row.personal_workspace,
+ archivedFrom: row.archived_from != null ? new Date(row.archived_from) : undefined,
+ lastView: row.last_view != null ? new Date(row.last_view) : undefined,
+ lastUpdate: row.last_update != null ? new Date(row.last_update) : undefined
+ }
+ }
+
+ toNotification (row: any): Notification {
+ const lastPatch = row.patches?.[0]
+ const lastView = row.last_view != null ? new Date(row.last_view) : undefined
+ const archivedFrom = row.archived_from != null ? new Date(row.archived_from) : undefined
+ const created = new Date(row.message_created)
+
+ return {
+ message: {
+ id: row.id,
+ card: row.message_card,
+ content: lastPatch?.content ?? row.message_content,
+ creator: row.message_creator,
+ created,
+ edited: new Date(lastPatch?.created ?? row.message_created),
+ reactions: row.reactions ?? [],
+ attachments: row.attachments ?? []
+ },
+ context: row.context,
+ read: lastView != null && lastView >= created,
+ archived: archivedFrom != null && archivedFrom >= created
+ }
+ }
}
-
diff --git a/packages/cockroach/src/db/schema.ts b/packages/cockroach/src/db/schema.ts
index 755cadc9e8..1dfc6578c2 100644
--- a/packages/cockroach/src/db/schema.ts
+++ b/packages/cockroach/src/db/schema.ts
@@ -1,122 +1,183 @@
-import type {ContextID, MessageID, RichText, SocialID, CardID, BlobID, Message, Reaction, Attachment, MessagesGroup, WorkspaceID } from "@hcengineering/communication-types"
+import {
+ type ContextID,
+ type MessageID,
+ type RichText,
+ type SocialID,
+ type CardID,
+ type BlobID,
+ type Message,
+ type Reaction,
+ type Attachment,
+ type MessagesGroup,
+ type WorkspaceID,
+ type PatchType,
+ type Patch,
+ type Thread
+} from '@hcengineering/communication-types'
export enum TableName {
- Message = 'communication.messages',
- MessagesGroup = 'communication.messages_groups',
- Patch = 'communication.patch',
- Attachment = 'communication.attachments',
- Reaction = 'communication.reactions',
- Notification = 'communication.notifications',
- NotificationContext = 'communication.notification_context'
+ Attachment = 'communication.attachments',
+ Message = 'communication.messages',
+ MessagesGroup = 'communication.messages_groups',
+ Notification = 'communication.notifications',
+ NotificationContext = 'communication.notification_context',
+ Patch = 'communication.patch',
+ Reaction = 'communication.reactions',
+ Thread = 'communication.thread'
}
export interface MessageDb {
- id: MessageID,
- workspace_id: WorkspaceID,
- card_id: CardID,
- content: RichText,
- creator: SocialID,
- created: Date,
+ id: MessageID
+ workspace_id: WorkspaceID
+ card_id: CardID
+ content: RichText
+ creator: SocialID
+ created: Date
+ thread_id?: CardID
+ replies_count?: number
+ last_reply?: Date
}
export interface MessagesGroupDb {
- workspace_id: WorkspaceID,
- card_id: CardID,
- blob_id: BlobID,
- from_id: MessageID,
- to_id: MessageID,
- from_date: Date,
- to_date: Date,
- count: number
+ workspace_id: WorkspaceID
+ card_id: CardID
+ blob_id: BlobID
+ from_date: Date
+ to_date: Date
+ from_id: MessageID
+ to_id: MessageID
+ count: number
+ patches?: PatchDb[]
}
export interface PatchDb {
- workspace_id: WorkspaceID,
- card_id: CardID,
- message_id: MessageID,
- content: RichText,
- creator: SocialID,
- created: Date,
+ workspace_id: WorkspaceID
+ card_id: CardID
+ message_id: MessageID
+ type: PatchType
+ content: RichText
+ creator: SocialID
+ created: Date
}
export interface ReactionDb {
- workspace_id: WorkspaceID,
- card_id: CardID,
- message_id: MessageID,
- reaction: string,
- creator: SocialID
- created: Date
+ workspace_id: WorkspaceID
+ card_id: CardID
+ message_id: MessageID
+ reaction: string
+ creator: SocialID
+ created: Date
}
export interface AttachmentDb {
- message_id: MessageID,
- card_id: CardID,
- creator: SocialID
- created: Date
+ message_id: MessageID
+ card_id: CardID
+ creator: SocialID
+ created: Date
+}
+
+export interface ThreadDb {
+ workspace_id: WorkspaceID
+ card_id: CardID
+ message_id: MessageID
+ thread_id: CardID
+ replies_count: number
+ last_reply: Date
}
export interface NotificationDb {
- message_id: MessageID,
- context: ContextID
+ message_id: MessageID
+ context: ContextID
}
export interface ContextDb {
- workspace_id: WorkspaceID
- card_id: CardID
- personal_workspace: WorkspaceID
+ workspace_id: WorkspaceID
+ card_id: CardID
+ personal_workspace: WorkspaceID
- archived_from?: Date
- last_view?: Date
- last_update?: Date
+ archived_from?: Date
+ last_view?: Date
+ last_update?: Date
}
-
interface RawMessage extends MessageDb {
- patches?: PatchDb[]
- attachments?: AttachmentDb[]
- reactions?: ReactionDb[]
+ patches?: PatchDb[]
+ attachments?: AttachmentDb[]
+ reactions?: ReactionDb[]
}
-export function toMessage(raw: RawMessage): Message {
- const lastPatch = raw.patches?.[0]
- return {
- id: raw.id,
- card: raw.card_id,
- content: lastPatch?.content ?? raw.content,
- creator: raw.creator,
- created: new Date(raw.created),
- edited: lastPatch?.created ? new Date(lastPatch.created) : undefined,
- reactions: (raw.reactions ?? []).map(toReaction),
- attachments: (raw.attachments ?? []).map(toAttachment)
- }
+export function toMessage (raw: RawMessage): Message {
+ const lastPatch = raw.patches?.[0]
+
+ return {
+ id: String(raw.id) as MessageID,
+ card: raw.card_id,
+ content: lastPatch?.content ?? raw.content,
+ creator: raw.creator,
+ created: raw.created,
+ edited: lastPatch?.created ?? undefined,
+ thread:
+ raw.thread_id != null
+ ? {
+ card: raw.card_id,
+ message: String(raw.id) as MessageID,
+ thread: raw.thread_id,
+ repliesCount: raw.replies_count ?? 0,
+ lastReply: raw.last_reply ?? new Date()
+ }
+ : undefined,
+ reactions: (raw.reactions ?? []).map(toReaction),
+ attachments: (raw.attachments ?? []).map(toAttachment)
+ }
}
-export function toReaction(raw: ReactionDb): Reaction {
- return {
- message: raw.message_id,
- reaction: raw.reaction,
- creator: raw.creator,
- created: new Date(raw.created)
- }
+export function toReaction (raw: ReactionDb): Reaction {
+ return {
+ message: String(raw.message_id) as MessageID,
+ reaction: raw.reaction,
+ creator: raw.creator,
+ created: raw.created
+ }
}
-export function toAttachment(raw: AttachmentDb): Attachment {
- return {
- message: raw.message_id,
- card: raw.card_id,
- creator: raw.creator,
- created: new Date(raw.created)
- }
+export function toAttachment (raw: AttachmentDb): Attachment {
+ return {
+ message: String(raw.message_id) as MessageID,
+ card: raw.card_id,
+ creator: raw.creator,
+ created: raw.created
+ }
}
-export function toMessagesGroup(raw: MessagesGroupDb): MessagesGroup {
- return {
- card: raw.card_id,
- blobId: raw.blob_id,
- fromId: raw.from_id,
- toId: raw.to_id,
- fromDate: new Date(raw.from_date),
- toDate: new Date(raw.to_date),
- count: raw.count
- }
-}
\ No newline at end of file
+export function toMessagesGroup (raw: MessagesGroupDb): MessagesGroup {
+ return {
+ card: raw.card_id,
+ blobId: raw.blob_id,
+ fromDate: raw.from_date,
+ toDate: raw.to_date,
+ fromId: String(raw.from_id) as MessageID,
+ toId: String(raw.to_id) as MessageID,
+ count: raw.count,
+ patches: raw.patches == null ? [] : raw.patches.filter((it: any) => it.message_id != null).map(toPatch)
+ }
+}
+
+export function toPatch (raw: PatchDb): Patch {
+ return {
+ type: raw.type,
+ message: String(raw.message_id) as MessageID,
+ content: raw.content,
+ creator: raw.creator,
+ created: new Date(raw.created)
+ }
+}
+
+export function toThread (raw: ThreadDb): Thread {
+ return {
+ card: raw.card_id,
+ message: String(raw.message_id) as MessageID,
+ thread: raw.thread_id,
+ repliesCount: raw.replies_count,
+ lastReply: raw.last_reply
+ }
+}
diff --git a/packages/cockroach/src/db/utils.ts b/packages/cockroach/src/db/utils.ts
index 4b56ad3f86..9f4ac0a1b1 100644
--- a/packages/cockroach/src/db/utils.ts
+++ b/packages/cockroach/src/db/utils.ts
@@ -1,22 +1,28 @@
-export function getCondition(table: string, dbField: string, index: number, param: any): { where: string, value: any } | undefined {
- if (typeof param === 'object') {
- if (param.less != null) {
- return {where: `${table}.${dbField} < $${index}`, value: param.less};
- }
- if (param.lessOrEqual != null) {
- return {where: `${table}.${dbField} <= $${index}`, value: param.lessOrEqual};
- }
- if (param.greater != null) {
- return {where: `${table}.${dbField} > $${index}`, value: param.greater};
- }
- if (param.greaterOrEqual != null) {
- return {where: `${table}.${dbField} >= $${index}`, value: param.greaterOrEqual};
- }
+export function getCondition (
+ table: string,
+ dbField: string,
+ index: number,
+ param: any,
+ type: string
+): { where: string, value: any } | undefined {
+ if (typeof param === 'object') {
+ if (param.less != null) {
+ return { where: `${table}.${dbField} < $${index}::${type}`, value: param.less }
}
-
- if(param != null) {
- return {where: `${table}.${dbField} = $${index}`, value: param};
+ if (param.lessOrEqual != null) {
+ return { where: `${table}.${dbField} <= $${index}::${type}`, value: param.lessOrEqual }
}
+ if (param.greater != null) {
+ return { where: `${table}.${dbField} > $${index}::${type}`, value: param.greater }
+ }
+ if (param.greaterOrEqual != null) {
+ return { where: `${table}.${dbField} >= $${index}::${type}`, value: param.greaterOrEqual }
+ }
+ }
- return undefined
-}
\ No newline at end of file
+ if (param != null) {
+ return { where: `${table}.${dbField} = $${index}::${type}`, value: param }
+ }
+
+ return undefined
+}
diff --git a/packages/cockroach/src/index.ts b/packages/cockroach/src/index.ts
index 03eeab5ffa..32544c6dfa 100644
--- a/packages/cockroach/src/index.ts
+++ b/packages/cockroach/src/index.ts
@@ -1 +1 @@
-export * from './adapter.ts'
+export * from './adapter'
diff --git a/packages/cockroach/src/types.ts b/packages/cockroach/src/types.ts
new file mode 100644
index 0000000000..d15e572af3
--- /dev/null
+++ b/packages/cockroach/src/types.ts
@@ -0,0 +1,16 @@
+import { type ParameterOrJSON, type Row } from 'postgres'
+
+export interface Logger {
+ info: (message: string, data?: Record) => void
+ warn: (message: string, data?: Record) => void
+ error: (message: string, data?: Record) => void
+}
+
+export interface Options {
+ withLogs?: boolean
+}
+
+export interface SqlClient {
+ execute: )[]>(query: string, params?: ParameterOrJSON[]) => Promise
+ close: () => void
+}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
deleted file mode 100644
index 0560e4bd55..0000000000
--- a/packages/core/src/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './message.ts'
diff --git a/packages/core/src/message.ts b/packages/core/src/message.ts
deleted file mode 100644
index 48a43227a5..0000000000
--- a/packages/core/src/message.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { MessageID } from '@hcengineering/communication-types'
-
-let lastTimestamp = Math.floor(Date.now() / 1000)
-let counter = 0
-
-export function generateMessageId(): MessageID {
- const timestamp = Math.floor(Date.now() / 1000)
-
- if (timestamp !== lastTimestamp) {
- lastTimestamp = timestamp
- counter = 0
- }
-
- counter++
-
- return ((timestamp << 24) | counter) as MessageID
-}
-
-export function parseMessageId(id: MessageID): { timestamp: number; counter: number } {
- const timestamp = id >> 24
- const counter = id & 0xffffff
-
- return { timestamp, counter }
-}
diff --git a/packages/examples/package.json b/packages/examples/package.json
index 9dfa63f469..e79ec57aa2 100644
--- a/packages/examples/package.json
+++ b/packages/examples/package.json
@@ -1,6 +1,6 @@
{
"name": "@hcengineering/communication-examples",
- "version": "0.1.9",
+ "version": "0.1.47",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
@@ -13,9 +13,7 @@
"@types/bun": "^1.1.14"
},
"dependencies": {
- "@hcengineering/communication-client-query": "workspace:*",
"@hcengineering/communication-client-ws": "workspace:*",
- "@hcengineering/communication-client-sqlite": "workspace:*",
"@hcengineering/communication-types": "workspace:*"
},
"peerDependencies": {
diff --git a/packages/examples/src/index.ts b/packages/examples/src/index.ts
index 414362ed14..d72db680f7 100644
--- a/packages/examples/src/index.ts
+++ b/packages/examples/src/index.ts
@@ -1,39 +1,33 @@
import {
type Message,
type SocialID,
- SortOrder,
+ SortingOrder,
type CardID,
type Window,
type WorkspaceID
} from '@hcengineering/communication-types'
-import { getWebsocketClient } from '@hcengineering/communication-client-ws'
-import { getSqliteClient } from '@hcengineering/communication-client-sqlite'
-import { createMessagesQuery, initLiveQueries } from '@hcengineering/communication-client-query'
+import { getWebsocketClient, createMessagesQuery } from '@hcengineering/communication-client-ws'
const card = 'cd0aba36-1c4f-4170-95f2-27a12a5415f6' as CardID
const workspace = 'cd0aba36-1c4f-4170-95f2-27a12a5415f6' as WorkspaceID
-const personalWorkspace = 'cd0aba36-1c4f-4170-95f2-27a12a5415f5' as WorkspaceID
+// const personalWorkspace = 'cd0aba36-1c4f-4170-95f2-27a12a5415f5' as WorkspaceID
const creator1 = 'email:vasya@huly.com' as SocialID
-async function getClient(type: 'ws' | 'sqlite') {
- if (type === 'ws') {
- const platformUrl = 'ws://localhost:8090'
- const token = 'token'
- return await getWebsocketClient(platformUrl, token)
- }
-
- return await getSqliteClient(workspace, personalWorkspace)
-}
-
export async function example() {
- const client = await getClient('sqlite')
- initLiveQueries(client)
+ const platformUrl = 'ws://localhost:8090'
+ const token = 'token'
+ const client = await getWebsocketClient(
+ platformUrl,
+ token,
+ workspace,
+ 'http://localhost:4022/blob/:workspace/:blobId/:filename'
+ )
const query1 = createMessagesQuery()
let window: Window | undefined = undefined
- query1.query({ card, sort: SortOrder.Desc }, (res) => {
+ query1.query({ card, order: SortingOrder.Descending }, (res) => {
window = res
const r = window.getResult()
r.reverse()
@@ -51,7 +45,7 @@ export async function example() {
})
async function editMessage(message: Message) {
- await client.createPatch(card, message.id, message.content + '_1_', creator1)
+ await client.updateMessage(card, message.id, message.content + '_1_', creator1)
}
async function deleteMessage(message: Message) {
diff --git a/packages/query/package.json b/packages/query/package.json
index 1a3669fcb8..87dbd672de 100644
--- a/packages/query/package.json
+++ b/packages/query/package.json
@@ -1,6 +1,6 @@
{
"name": "@hcengineering/communication-query",
- "version": "0.1.9",
+ "version": "0.1.47",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
@@ -11,11 +11,12 @@
],
"devDependencies": {
"@types/bun": "^1.1.14",
- "@types/crypto-js": "^4.2.2"
+ "@types/crypto-js": "^4.2.2"
},
"dependencies": {
- "@hcengineering/communication-types": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
+ "@hcengineering/communication-shared": "workspace:*",
+ "@hcengineering/communication-types": "workspace:*",
"fast-equals": "^5.0.1"
},
"peerDependencies": {
diff --git a/packages/query/src/lq.ts b/packages/query/src/lq.ts
index 9dbae0845b..44563787ec 100644
--- a/packages/query/src/lq.ts
+++ b/packages/query/src/lq.ts
@@ -1,4 +1,8 @@
-import { type FindMessagesParams, type FindNotificationsParams } from '@hcengineering/communication-types'
+import {
+ type FindMessagesParams,
+ type FindNotificationsParams,
+ type WorkspaceID
+} from '@hcengineering/communication-types'
import { deepEqual } from 'fast-equals'
import type {
MessagesQueryCallback,
@@ -7,7 +11,7 @@ import type {
QueryClient
} from '@hcengineering/communication-sdk-types'
-import type { Query, QueryId } from './types'
+import type { FindParams, PagedQuery, QueryId } from './types'
import { MessagesQuery } from './messages/query'
import { NotificationQuery } from './notifications/query'
@@ -18,14 +22,15 @@ interface CreateQueryResult {
const maxQueriesCache = 10
export class LiveQueries {
- private readonly client: QueryClient
- private readonly queries = new Map()
+ private readonly queries = new Map()
private readonly unsubscribed = new Set()
private counter: number = 0
- constructor(client: QueryClient) {
- this.client = client
- }
+ constructor(
+ private readonly client: QueryClient,
+ private readonly workspace: WorkspaceID,
+ private readonly filesUrl: string
+ ) {}
async onEvent(event: ResponseEvent): Promise {
for (const q of this.queries.values()) {
@@ -66,11 +71,11 @@ export class LiveQueries {
return exists
} else {
const result = exists.copyResult()
- return new MessagesQuery(this.client, id, params, callback, result)
+ return new MessagesQuery(this.client, this.workspace, this.filesUrl, id, params, callback, result)
}
}
- return new MessagesQuery(this.client, id, params, callback)
+ return new MessagesQuery(this.client, this.workspace, this.filesUrl, id, params, callback)
}
private createNotificationQuery(
@@ -103,7 +108,7 @@ export class LiveQueries {
}
}
- private findNotificationQuery(params: FindMessagesParams): NotificationQuery | undefined {
+ private findNotificationQuery(params: FindNotificationsParams): NotificationQuery | undefined {
for (const query of this.queries.values()) {
if (query instanceof NotificationQuery) {
if (!this.queryCompare(params, query.params)) continue
@@ -112,7 +117,7 @@ export class LiveQueries {
}
}
- private queryCompare(q1: FindMessagesParams, q2: FindMessagesParams): boolean {
+ private queryCompare(q1: FindParams, q2: FindParams): boolean {
if (Object.keys(q1).length !== Object.keys(q2).length) {
return false
}
@@ -136,7 +141,7 @@ export class LiveQueries {
this.unsubscribed.delete(id)
}
- private unsubscribeQuery(query: Query): void {
+ private unsubscribeQuery(query: PagedQuery): void {
this.unsubscribed.add(query.id)
query.removeCallback()
if (this.unsubscribed.size > maxQueriesCache) {
diff --git a/packages/query/src/messages/query.ts b/packages/query/src/messages/query.ts
index 08279eeaaf..073527450b 100644
--- a/packages/query/src/messages/query.ts
+++ b/packages/query/src/messages/query.ts
@@ -1,201 +1,693 @@
import {
- type FindMessagesParams,
- type MessageID,
- type Message,
- type Patch,
- SortOrder
+ type FindMessagesParams,
+ type Message,
+ type MessagesGroup,
+ type WorkspaceID,
+ type MessageID,
+ SortingOrder,
+ type Patch,
+ type ParsedFile,
+ PatchType,
+ type CardID
} from '@hcengineering/communication-types'
import {
- type AttachmentCreatedEvent,
- type MessageCreatedEvent,
- type PatchCreatedEvent,
- type ReactionCreatedEvent,
- type ResponseEvent,
- ResponseEventType,
- type AttachmentRemovedEvent,
- type MessageRemovedEvent,
- type ReactionRemovedEvent
+ ResponseEventType,
+ type AttachmentCreatedEvent,
+ type AttachmentRemovedEvent,
+ type MessageCreatedEvent,
+ type MessageRemovedEvent,
+ type PatchCreatedEvent,
+ type QueryCallback,
+ type QueryClient,
+ type ReactionCreatedEvent,
+ type ReactionRemovedEvent,
+ type ResponseEvent,
+ type ThreadCreatedEvent
} from '@hcengineering/communication-sdk-types'
+import { loadGroupFile, parseMessageId } from '@hcengineering/communication-shared'
-import { BaseQuery } from '../query'
+import { QueryResult } from '../result'
+import { defaultQueryParams, Direction, type QueryId, type PagedQuery } from '../types'
+import { WindowImpl } from '../window'
+import { addReaction, addReply, removeReaction, removeReply } from './utils'
-export class MessagesQuery extends BaseQuery {
- override async find(params: FindMessagesParams): Promise {
- return this.client.findMessages(params, this.id)
+const GROUPS_LIMIT = 20
+
+export class MessagesQuery implements PagedQuery {
+ protected result: Promise> | QueryResult
+
+ private messagesFromFiles: Message[] = []
+
+ private readonly groupsBuffer: MessagesGroup[] = []
+
+ private firstGroup?: MessagesGroup
+ private lastGroup?: MessagesGroup
+
+ private readonly limit: number
+
+ private readonly next = {
+ hasMessages: true,
+ hasGroups: true
+ }
+
+ private readonly prev = {
+ hasMessages: true,
+ hasGroups: true
+ }
+
+ constructor (
+ protected readonly client: QueryClient,
+ private readonly workspace: WorkspaceID,
+ private readonly filesUrl: string,
+ public readonly id: QueryId,
+ public readonly params: FindMessagesParams,
+ private callback?: QueryCallback,
+ initialResult?: QueryResult
+ ) {
+ const baseLimit = params.id != null ? 1 : this.params.limit ?? defaultQueryParams.limit
+ this.limit = baseLimit + 1
+ this.params = {
+ ...params,
+ order: params.order ?? defaultQueryParams.order
}
+ if (initialResult !== undefined) {
+ const messages = initialResult.getResult()
+ const count = messages.length
- override getObjectId(object: Message): MessageID {
- return object.id
- }
-
- override getObjectDate(object: Message): Date {
- return object.created
- }
-
- override async onEvent(event: ResponseEvent): Promise {
- switch (event.type) {
- case ResponseEventType.MessageCreated:
- return await this.onCreateMessageEvent(event)
- case ResponseEventType.MessageRemoved:
- return await this.onRemoveMessageEvent(event)
- case ResponseEventType.PatchCreated:
- return await this.onCreatePatchEvent(event)
- case ResponseEventType.ReactionCreated:
- return await this.onCreateReactionEvent(event)
- case ResponseEventType.ReactionRemoved:
- return await this.onRemoveReactionEvent(event)
- case ResponseEventType.AttachmentCreated:
- return await this.onCreateAttachmentEvent(event)
- case ResponseEventType.AttachmentRemoved:
- return await this.onRemoveAttachmentEvent(event)
+ if (count < this.limit) {
+ this.result = initialResult
+ } else {
+ if (this.params.order === SortingOrder.Ascending) {
+ this.result = new QueryResult(messages.slice(0, baseLimit), (x) => x.id)
+ this.result.setHead(true)
+ this.result.setTail(false)
+ } else {
+ this.result = new QueryResult(messages.slice(0, baseLimit), (x) => x.id)
+ this.result.setHead(false)
+ this.result.setTail(true)
}
+ }
+ void this.notify()
+ } else {
+ this.result = new QueryResult([] as Message[], (x) => x.id)
+
+ if (this.isInitLoadingForward()) {
+ this.result.setHead(true)
+ void this.requestLoadNextPage()
+ } else {
+ this.result.setTail(true)
+ void this.requestLoadPrevPage()
+ }
+ }
+ }
+
+ setCallback (callback: QueryCallback): void {
+ this.callback = callback
+ void this.notify()
+ }
+
+ removeCallback (): void {
+ this.callback = () => {}
+ }
+
+ async requestLoadNextPage (): Promise {
+ if (this.result instanceof Promise) this.result = await this.result
+
+ if (!this.result.isTail()) {
+ this.result = this.loadPage(Direction.Forward, this.result)
+ void this.result
+ .then(() => this.notify())
+ .catch((error) => {
+ console.error('Failed to load messages', error)
+ })
+ }
+ }
+
+ async requestLoadPrevPage (): Promise {
+ if (this.result instanceof Promise) this.result = await this.result
+ if (!this.result.isHead()) {
+ this.result = this.loadPage(Direction.Backward, this.result)
+ void this.result
+ .then(() => this.notify())
+ .catch((error) => {
+ console.error('Failed to load messages', error)
+ })
+ }
+ }
+
+ private isInitLoadingForward (): boolean {
+ const { order, created, id } = this.params
+
+ if (id != null) {
+ return false
}
- async onCreateMessageEvent(event: MessageCreatedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
+ if (created == null) return order === SortingOrder.Ascending
+ if (created instanceof Date) return order === SortingOrder.Ascending
+ // TODO: fix me
+ if (created.less != null) return order !== SortingOrder.Ascending
+ if (created.lessOrEqual != null) return order !== SortingOrder.Ascending
+ if (created.greater != null) return order === SortingOrder.Ascending
+ if (created.greaterOrEqual != null) return order === SortingOrder.Ascending
- const message = {
- ...event.message,
- edited: event.message.edited,
- created: event.message.created
- }
- const exists = this.result.get(message.id)
+ return false
+ }
- if (exists !== undefined) return
- if (!this.match(message)) return
+ private async loadPage (direction: Direction, result: QueryResult): Promise> {
+ const { messages, fromDb } =
+ direction === Direction.Forward ? await this.loadNextMessages(result) : await this.loadPrevMessages(result)
- if (this.result.isTail()) {
- if (this.params.sort === SortOrder.Asc) {
- this.result.push(message)
- } else {
- this.result.unshift(message)
+ if (!result.isHead() && direction === Direction.Backward) {
+ result.setHead(messages.length < this.limit)
+ }
+ if (!result.isTail() && direction === Direction.Forward) {
+ result.setTail(messages.length < this.limit)
+ }
+
+ if (messages.length === this.limit && this.limit > 1) {
+ const lastMessage = messages.pop()
+ if (lastMessage != null && !fromDb) {
+ direction === Direction.Forward
+ ? this.messagesFromFiles.unshift(lastMessage)
+ : this.messagesFromFiles.push(lastMessage)
+ }
+ }
+
+ result.append(messages)
+
+ return result
+ }
+
+ // Load next
+ private async loadNextMessages (result: QueryResult): Promise<{ messages: Message[], fromDb: boolean }> {
+ const messages: Message[] = this.messagesFromFiles.splice(0, this.limit)
+
+ if (messages.length >= this.limit) return { messages, fromDb: false }
+
+ while (this.next.hasGroups || this.groupsBuffer.length > 0) {
+ await this.loadGroups(Direction.Forward, result)
+
+ messages.push(...this.messagesFromFiles.splice(0, this.limit - messages.length))
+
+ if (messages.length >= this.limit) return { messages, fromDb: false }
+ }
+
+ const dbMessages = await this.findNextMessages(this.limit - messages.length, result)
+ this.next.hasMessages = dbMessages.length > 0
+ messages.push(...dbMessages)
+ return { messages, fromDb: dbMessages.length > 0 }
+ }
+
+ private async findNextMessages (limit: number, result: QueryResult): Promise {
+ if (this.next.hasGroups) {
+ return []
+ }
+
+ if (result.isTail()) return []
+
+ const last = result.getLast()
+
+ return await this.find({
+ ...this.params,
+ created:
+ last != null
+ ? {
+ greater: last?.created
}
- await this.notify()
- }
+ : undefined,
+ limit,
+ order: SortingOrder.Ascending
+ })
+ }
+
+ // Load prev
+ private async loadPrevMessages (result: QueryResult): Promise<{ messages: Message[], fromDb: boolean }> {
+ const messages: Message[] = []
+
+ if (this.prev.hasMessages) {
+ const prevMessages = await this.findPrevMessages(this.limit, result)
+ this.prev.hasMessages = prevMessages.length > 0
+ messages.push(...prevMessages)
}
- private match(message: Message): boolean {
- if (this.params.id != null && this.params.id !== message.id) {
- return false
- }
- if (this.params.card != null && this.params.card !== message.card) {
- return false
- }
- return true
+ if (messages.length >= this.limit) return { messages, fromDb: true }
+
+ const restLimit = this.limit - messages.length
+ const fromBuffer = this.messagesFromFiles.splice(-restLimit, restLimit).reverse()
+ messages.push(...fromBuffer)
+
+ if (messages.length >= this.limit) return { messages, fromDb: false }
+
+ while (this.prev.hasGroups || this.groupsBuffer.length > 0) {
+ await this.loadGroups(Direction.Backward, result)
+
+ const rest = this.limit - messages.length
+ const fromBuffer2 = this.messagesFromFiles.splice(-rest, rest).reverse()
+
+ messages.push(...fromBuffer2)
+ if (messages.length >= this.limit) return { messages, fromDb: false }
}
- private async onCreatePatchEvent(event: PatchCreatedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
+ return { messages, fromDb: false }
+ }
+
+ private async findPrevMessages (limit: number, result: QueryResult): Promise {
+ if (!this.prev.hasMessages || result.isHead()) return []
+
+ const first = result.getLast()
+
+ return await this.find({
+ ...this.params,
+ created:
+ first != null
+ ? {
+ less: first?.created
+ }
+ : undefined,
+ limit,
+ order: SortingOrder.Descending
+ })
+ }
+
+ private async loadGroups (direction: Direction, result: QueryResult): Promise {
+ let messagesCount = 0
+ const lastResult = result.getLast()
+ const toLoad: MessagesGroup[] = []
+ const toBuffer: MessagesGroup[] = []
+
+ while (messagesCount < this.limit) {
+ const currentGroups = this.groupsBuffer.splice(direction === Direction.Forward ? 0 : -GROUPS_LIMIT, GROUPS_LIMIT)
+ const hasGroups = direction === Direction.Forward ? this.next.hasGroups : this.prev.hasGroups
+ if (currentGroups.length === 0 && !hasGroups) break
+
+ const groups =
+ currentGroups.length > 0
+ ? currentGroups
+ : await this.findGroups(
+ direction,
+ direction === Direction.Forward ? this.lastGroup?.fromDate : this.firstGroup?.fromDate
+ )
+
+ if (currentGroups.length === 0) {
+ this.firstGroup = direction === Direction.Forward ? this.firstGroup ?? groups[0] : groups[groups.length - 1]
+ this.lastGroup =
+ direction === Direction.Forward ? groups[groups.length - 1] ?? this.lastGroup : this.lastGroup ?? groups[0]
+
+ if (direction === Direction.Forward) {
+ this.next.hasGroups = groups.length >= GROUPS_LIMIT
+ } else {
+ this.prev.hasGroups = groups.length >= GROUPS_LIMIT
}
-
- const patch = {
- ...event.patch,
- created: event.patch.created
+ if (this.params.id != null) {
+ this.next.hasGroups = false
+ this.prev.hasGroups = false
}
+ }
- const message = this.result.get(patch.message)
+ const orderedGroups = direction === Direction.Forward ? groups : groups.reverse()
+ while (messagesCount < this.limit && orderedGroups.length > 0) {
+ const group = direction === Direction.Forward ? orderedGroups.shift() : orderedGroups.pop()
+ if (group == null) break
+ toLoad.push(group)
+ messagesCount += group.count
+ }
- if (message === undefined) return
-
- if (message.created < patch.created) {
- this.result.update(this.applyPatch(message, patch))
- await this.notify()
- }
+ while (orderedGroups.length > 0) {
+ const group = direction === Direction.Forward ? orderedGroups.shift() : orderedGroups.pop()
+ if (group == null) break
+ toBuffer.push(group)
+ messagesCount += group.count
+ }
}
- private async onRemoveMessageEvent(event: MessageRemovedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
-
- const deleted = this.result.delete(event.message)
-
- if (deleted !== undefined) {
- await this.notify()
- }
+ if (direction === Direction.Forward) {
+ this.groupsBuffer.push(...toBuffer)
+ } else {
+ this.groupsBuffer.unshift(...toBuffer)
}
- private async onCreateReactionEvent(event: ReactionCreatedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
+ const parsedFiles = await Promise.all(toLoad.map((group) => this.loadMessagesFromFiles(group)))
- const reaction = {
- ...event.reaction,
- created: event.reaction.created
- }
- const message = this.result.get(reaction.message)
- if (message === undefined) return
+ for (const file of parsedFiles) {
+ if (file.messages.length === 0) continue
+ if (direction === Direction.Forward) {
+ const firstInFile = file.messages[0]
+ const queryDate =
+ lastResult != null && firstInFile.created < lastResult?.created ? lastResult?.created : undefined
+ this.messagesFromFiles.push(...this.matchFileMessages(file, queryDate))
+ } else {
+ const lastInFile = file.messages[file.messages.length - 1]
+ const queryDate =
+ lastResult != null && lastInFile.created > lastResult?.created ? lastResult?.created : undefined
+ this.messagesFromFiles.unshift(...this.matchFileMessages(file, queryDate))
+ }
+ }
+ }
- message.reactions.push(reaction)
- this.result.update(message)
- await this.notify()
+ private matchFileMessages (file: ParsedFile, created?: Date): Message[] {
+ let result: Message[] = file.messages
+ if (this.params.id != null) {
+ const msg = file.messages.find((it) => it.id === this.params.id)
+ result = msg != null ? [msg] : []
}
- private async onRemoveReactionEvent(event: ReactionRemovedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
-
- const message = this.result.get(event.message)
- if (message === undefined) return
-
- const reactions = message.reactions.filter((it) => it.reaction !== event.reaction && it.creator !== event.creator)
- if (reactions.length === message.reactions.length) return
-
- const updated = {
- ...message,
- reactions
- }
- this.result.update(updated)
- await this.notify()
+ if (created != null) {
+ result =
+ this.params.order === SortingOrder.Ascending
+ ? result.filter((it) => it.created > created)
+ : result.filter((it) => it.created < created)
}
- private async onCreateAttachmentEvent(event: AttachmentCreatedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
+ return result
+ }
- const attachment = {
- ...event.attachment,
- created: event.attachment.created
- }
- const message = this.result.get(attachment.message)
- if (message === undefined) return
+ private async loadMessagesFromFiles (group: MessagesGroup): Promise {
+ const parsedFile = await loadGroupFile(this.workspace, this.filesUrl, group, { retries: 5 })
- message.attachments.push(attachment)
- this.result.update(message)
- await this.notify()
+ const patches = group.patches ?? []
+
+ const patchesMap = new Map()
+ for (const patch of patches) {
+ patchesMap.set(patch.message, [...(patchesMap.get(patch.message) ?? []), patch])
}
- private async onRemoveAttachmentEvent(event: AttachmentRemovedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
+ return {
+ metadata: parsedFile.metadata,
+ messages:
+ patches.length > 0
+ ? parsedFile.messages.map((message) => this.applyPatches(message, patchesMap.get(message.id) ?? []))
+ : parsedFile.messages
+ }
+ }
- const message = this.result.get(event.message)
- if (message === undefined) return
+ private async findGroupByMessage (id: MessageID): Promise {
+ const date = parseMessageId(id)
+ const group1 = (await this.client.findMessagesGroups({
+ card: this.params.card,
+ limit: 1,
+ toDate: {
+ greaterOrEqual: date
+ },
+ order: SortingOrder.Descending,
+ orderBy: 'fromDate'
+ }))[0]
- const attachments = message.attachments.filter((it) => it.card !== event.card)
- if (attachments.length === message.attachments.length) return
-
- const updated = {
- ...message,
- attachments
- }
- this.result.update(updated)
- await this.notify()
+ if (group1 !== undefined) {
+ return group1
}
- private applyPatch(message: Message, patch: Patch): Message {
+ return (await this.client.findMessagesGroups({
+ card: this.params.card,
+ limit: 1,
+ fromDate: {
+ lessOrEqual: date
+ },
+ order: SortingOrder.Ascending,
+ orderBy: 'fromDate'
+ }))[0]
+ }
+
+ private async findGroups (direction: Direction, fromDate?: Date): Promise {
+ if (this.params.id != null) {
+ const group = await this.findGroupByMessage(this.params.id)
+ return group !== undefined ? [group] : []
+ }
+
+ if (fromDate == null) {
+ return await this.client.findMessagesGroups({
+ card: this.params.card,
+ limit: GROUPS_LIMIT,
+ order: direction === Direction.Forward ? SortingOrder.Ascending : SortingOrder.Descending,
+ orderBy: 'fromDate'
+ })
+ }
+
+ return await this.client.findMessagesGroups({
+ card: this.params.card,
+ limit: GROUPS_LIMIT,
+ order: direction === Direction.Forward ? SortingOrder.Ascending : SortingOrder.Descending,
+ orderBy: 'fromDate',
+ fromDate:
+ direction === Direction.Forward
+ ? {
+ greater: fromDate
+ }
+ : {
+ less: fromDate
+ }
+ })
+ }
+
+ private async find (params: FindMessagesParams): Promise {
+ return await this.client.findMessages(params, this.id)
+ }
+
+ private async notify (): Promise {
+ if (this.callback == null) return
+ if (this.result instanceof Promise) this.result = await this.result
+ const result = this.result.getResult()
+ this.callback(new WindowImpl(result, this.result.isTail(), this.result.isHead(), this))
+ }
+
+ async unsubscribe (): Promise {
+ await this.client.unsubscribeQuery(this.id)
+ }
+
+ async onEvent (event: ResponseEvent): Promise {
+ switch (event.type) {
+ case ResponseEventType.MessageCreated: {
+ await this.onCreateMessageEvent(event)
+ return
+ }
+ case ResponseEventType.MessageRemoved: {
+ await this.onRemoveMessageEvent(event)
+ return
+ }
+ case ResponseEventType.PatchCreated: {
+ await this.onCreatePatchEvent(event)
+ return
+ }
+ case ResponseEventType.ReactionCreated: {
+ await this.onCreateReactionEvent(event)
+ return
+ }
+ case ResponseEventType.ReactionRemoved: {
+ await this.onRemoveReactionEvent(event)
+ return
+ }
+ case ResponseEventType.AttachmentCreated: {
+ await this.onCreateAttachmentEvent(event)
+ return
+ }
+ case ResponseEventType.AttachmentRemoved: {
+ await this.onRemoveAttachmentEvent(event)
+ return
+ }
+ case ResponseEventType.ThreadCreated: {
+ await this.onCreateThreadEvent(event)
+ }
+ }
+ }
+
+ async onCreateThreadEvent (event: ThreadCreatedEvent): Promise {
+ if (this.params.card !== event.thread.card) return
+ if (this.result instanceof Promise) this.result = await this.result
+
+ const message = this.result.get(event.thread.message)
+ if (message !== undefined) {
+ const updated: Message = {
+ ...message,
+ thread: event.thread
+ }
+
+ this.result.update(updated)
+ void this.notify()
+ }
+
+ this.messagesFromFiles = this.messagesFromFiles.map((it) => {
+ if (it.id === event.thread.message) {
return {
- ...message,
- content: patch.content,
- creator: patch.creator,
- created: patch.created
+ ...it,
+ thread: event.thread
}
+ }
+ return it
+ })
+ }
+
+ async onCreateMessageEvent (event: MessageCreatedEvent): Promise {
+ if (this.result instanceof Promise) this.result = await this.result
+ if (this.params.card !== event.message.card) return
+ const { message } = event
+ const exists = this.result.get(message.id)
+
+ if (exists !== undefined) return
+ if (!this.match(message)) return
+
+ if (this.result.isTail()) {
+ if (this.params.order === SortingOrder.Ascending) {
+ this.result.push(message)
+ } else {
+ this.result.unshift(message)
+ }
+ await this.notify()
}
+ }
+
+ private match (message: Message): boolean {
+ if (this.params.id != null && this.params.id !== message.id) {
+ return false
+ }
+ if (this.params.card !== message.card) {
+ return false
+ }
+ return true
+ }
+
+ private async onCreatePatchEvent (event: PatchCreatedEvent): Promise {
+ if (this.params.card !== event.card) return
+ if (this.result instanceof Promise) this.result = await this.result
+
+ const { patch } = event
+ const messageId = BigInt(patch.message)
+ const group = this.groupsBuffer.find((it) => BigInt(it.fromId) <= messageId && BigInt(it.toId) >= messageId)
+
+ if (group != null) {
+ group.patches.push(patch)
+ }
+
+ const message = this.result.get(patch.message)
+ if (message === undefined) return
+
+ if (message.created < patch.created) {
+ this.result.update(this.applyPatch(message, patch))
+ await this.notify()
+ }
+ }
+
+ private async onRemoveMessageEvent (event: MessageRemovedEvent): Promise {
+ if (this.params.card !== event.card) return
+ if (this.result instanceof Promise) this.result = await this.result
+
+ const deleted = this.result.delete(event.message)
+
+ if (deleted !== undefined) {
+ void this.notify()
+ }
+
+ this.messagesFromFiles = this.messagesFromFiles.filter((it) => it.id !== event.message)
+ }
+
+ private async onCreateReactionEvent (event: ReactionCreatedEvent): Promise {
+ if (this.result instanceof Promise) this.result = await this.result
+ if (this.params.card !== event.card) return
+
+ const reaction = {
+ ...event.reaction,
+ created: event.reaction.created
+ }
+
+ const message = this.result.get(reaction.message)
+ if (message !== undefined) {
+ this.result.update(addReaction(message, reaction))
+ void this.notify()
+ }
+
+ const fromBuffer = this.messagesFromFiles.find((it) => it.id === reaction.message)
+ if (fromBuffer !== undefined) {
+ addReaction(fromBuffer, reaction)
+ }
+ }
+
+ private async onRemoveReactionEvent (event: ReactionRemovedEvent): Promise {
+ if (this.result instanceof Promise) this.result = await this.result
+ if (this.params.card !== event.card) return
+
+ const message = this.result.get(event.message)
+ if (message !== undefined) {
+ const updated = removeReaction(message, event.reaction, event.creator)
+ if (updated.reactions.length !== message.reactions.length) {
+ this.result.update(updated)
+ void this.notify()
+ }
+ }
+ this.messagesFromFiles = this.messagesFromFiles.map((it) =>
+ it.id === event.message ? removeReaction(it, event.reaction, event.creator) : it
+ )
+ }
+
+ private async onCreateAttachmentEvent (event: AttachmentCreatedEvent): Promise {
+ if (this.result instanceof Promise) this.result = await this.result
+
+ const attachment = {
+ ...event.attachment,
+ created: event.attachment.created
+ }
+ const message = this.result.get(attachment.message)
+ if (message === undefined) return
+
+ message.attachments.push(attachment)
+ this.result.update(message)
+ await this.notify()
+ }
+
+ private async onRemoveAttachmentEvent (event: AttachmentRemovedEvent): Promise {
+ if (this.result instanceof Promise) this.result = await this.result
+
+ const message = this.result.get(event.message)
+ if (message === undefined) return
+
+ const attachments = message.attachments.filter((it) => it.card !== event.card)
+ if (attachments.length === message.attachments.length) return
+
+ const updated = {
+ ...message,
+ attachments
+ }
+ this.result.update(updated)
+ await this.notify()
+ }
+
+ private applyPatch (message: Message, patch: Patch): Message {
+ switch (patch.type) {
+ case PatchType.update:
+ return {
+ ...message,
+ edited: patch.created,
+ content: patch.content
+ }
+ case PatchType.addReaction:
+ return addReaction(message, {
+ message: message.id,
+ reaction: patch.content,
+ creator: patch.creator,
+ created: patch.created
+ })
+ case PatchType.removeReaction:
+ return removeReaction(message, patch.content, patch.creator)
+ case PatchType.addReply:
+ return addReply(message, patch.content as CardID, patch.created)
+ case PatchType.removeReply:
+ return removeReply(message, patch.content as CardID)
+ }
+
+ return message
+ }
+
+ private applyPatches (message: Message, patches: Patch[]): Message {
+ if (patches.length === 0) return message
+
+ for (const p of patches) {
+ message = this.applyPatch(message, p)
+ }
+ return message
+ }
+
+ copyResult (): QueryResult | undefined {
+ if (this.result instanceof Promise) {
+ return undefined
+ }
+
+ return this.result.copy()
+ }
}
diff --git a/packages/query/src/messages/utils.ts b/packages/query/src/messages/utils.ts
new file mode 100644
index 0000000000..1db6a1260d
--- /dev/null
+++ b/packages/query/src/messages/utils.ts
@@ -0,0 +1,54 @@
+import type { CardID, Message, Reaction, SocialID } from '@hcengineering/communication-types'
+
+export function addReaction (message: Message, reaction: Reaction): Message {
+ message.reactions.push(reaction)
+ return message
+}
+
+export function removeReaction (message: Message, emoji: string, creator: SocialID): Message {
+ const reactions = message.reactions.filter((it) => it.reaction !== emoji || it.creator !== creator)
+ if (reactions.length === message.reactions.length) return message
+
+ return {
+ ...message,
+ reactions
+ }
+}
+
+export function addReply (message: Message, thread: CardID, created: Date): Message {
+ if (message.thread === undefined) {
+ return {
+ ...message,
+ thread: {
+ card: message.card,
+ message: message.id,
+ thread,
+ repliesCount: 1,
+ lastReply: created
+ }
+ }
+ }
+
+ if (message.thread.thread !== thread) return message
+
+ return {
+ ...message,
+ thread: {
+ ...message.thread,
+ repliesCount: message.thread.repliesCount + 1,
+ lastReply: created
+ }
+ }
+}
+
+export function removeReply (message: Message, thread: CardID): Message {
+ if (message.thread === undefined || message.thread.thread !== thread) return message
+
+ return {
+ ...message,
+ thread: {
+ ...message.thread,
+ repliesCount: message.thread.repliesCount - 1
+ }
+ }
+}
diff --git a/packages/query/src/notifications/query.ts b/packages/query/src/notifications/query.ts
index e318a01660..7b0d5b6a50 100644
--- a/packages/query/src/notifications/query.ts
+++ b/packages/query/src/notifications/query.ts
@@ -1,129 +1,138 @@
import {
- type FindNotificationsParams,
- SortOrder,
- type Notification,
- type MessageID,
+ type FindNotificationsParams,
+ SortingOrder,
+ type Notification,
+ type MessageID
} from '@hcengineering/communication-types'
import {
- type NotificationCreatedEvent,
- ResponseEventType,
- type ResponseEvent,
- type NotificationContextRemovedEvent,
- type NotificationRemovedEvent,
- type NotificationContextUpdatedEvent,
+ type NotificationCreatedEvent,
+ ResponseEventType,
+ type ResponseEvent,
+ type NotificationContextRemovedEvent,
+ type NotificationRemovedEvent,
+ type NotificationContextUpdatedEvent
} from '@hcengineering/communication-sdk-types'
-import {BaseQuery} from '../query.ts';
+import { BaseQuery } from '../query'
export class NotificationQuery extends BaseQuery {
- override async find(params: FindNotificationsParams): Promise {
- return this.client.findNotifications(params, this.id)
+ override async find (params: FindNotificationsParams): Promise {
+ return await this.client.findNotifications(params, this.id)
+ }
+
+ override getObjectId (object: Notification): MessageID {
+ return object.message.id
+ }
+
+ override getObjectDate (object: Notification): Date {
+ return object.message.created
+ }
+
+ override async onEvent (event: ResponseEvent): Promise {
+ switch (event.type) {
+ case ResponseEventType.NotificationCreated: {
+ await this.onCreateNotificationEvent(event)
+ return
+ }
+ case ResponseEventType.NotificationRemoved: {
+ await this.onRemoveNotificationEvent(event)
+ return
+ }
+ case ResponseEventType.NotificationContextUpdated: {
+ await this.onUpdateNotificationContextEvent(event)
+ return
+ }
+ case ResponseEventType.NotificationContextRemoved: {
+ await this.onRemoveNotificationContextEvent(event)
+ }
+ }
+ }
+
+ async onCreateNotificationEvent (event: NotificationCreatedEvent): Promise {
+ if (this.result instanceof Promise) {
+ this.result = await this.result
}
- override getObjectId(object: Notification): MessageID {
- return object.message.id
+ const exists = this.result.get(event.notification.message.id)
+ if (exists !== undefined) return
+
+ if (this.params.message != null && this.params.message !== event.notification.message.id) return
+ if (this.params.context != null && this.params.context !== event.notification.context) return
+
+ if (this.result.isTail()) {
+ if (this.params.order === SortingOrder.Ascending) {
+ this.result.push(event.notification)
+ } else {
+ this.result.unshift(event.notification)
+ }
+ await this.notify()
+ }
+ }
+
+ private async onUpdateNotificationContextEvent (event: NotificationContextUpdatedEvent): Promise {
+ if (this.result instanceof Promise) {
+ this.result = await this.result
}
- override getObjectDate(object: Notification): Date {
- return object.message.created
- }
+ if (this.params.context != null && this.params.context !== event.context) return
+ if (event.update.lastView === undefined && event.update.archivedFrom === undefined) return
- override async onEvent(event: ResponseEvent): Promise {
- switch (event.type) {
- case ResponseEventType.NotificationCreated:
- return await this.onCreateNotificationEvent(event)
- case ResponseEventType.NotificationRemoved:
- return await this.onRemoveNotificationEvent(event)
- case ResponseEventType.NotificationContextUpdated:
- return await this.onUpdateNotificationContextEvent(event)
- case ResponseEventType.NotificationContextRemoved:
- return await this.onRemoveNotificationContextEvent(event)
- }
- }
+ const toUpdate =
+ this.params.context === event.context
+ ? this.result.getResult()
+ : this.result.getResult().filter((it) => it.context === event.context)
+ if (toUpdate.length === 0) return
- async onCreateNotificationEvent(event: NotificationCreatedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
-
- const exists = this.result.get(event.notification.message.id)
- if (exists !== undefined) return
-
- if (this.params.message != null && this.params.message !== event.notification.message.id) return
- if (this.params.context != null && this.params.context !== event.notification.context) return
-
- if (this.result.isTail()) {
- if (this.params.sort === SortOrder.Asc) {
- this.result.push(event.notification)
- } else {
- this.result.unshift(event.notification)
+ for (const notification of toUpdate) {
+ this.result.update({
+ ...notification,
+ ...(event.update.lastView !== undefined
+ ? {
+ read: event.update.lastView < notification.message.created
}
- await this.notify()
- }
- }
-
-
- private async onUpdateNotificationContextEvent(event: NotificationContextUpdatedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
-
- if (this.params.context != null && this.params.context !== event.context) return
- if (event.update.lastView === undefined && event.update.archivedFrom === undefined) return
-
- const toUpdate = this.params.context === event.context ?
- this.result.getResult()
- : this.result.getResult().filter(it => it.context === event.context)
- if (toUpdate.length === 0) return
-
- for (const notification of toUpdate) {
- this.result.update({
- ...notification,
- ...event.update.lastView !== undefined ? {
- read: event.update.lastView < notification.message.created
- } : {},
- ...event.update.archivedFrom !== undefined ? {
- archived: event.update.archivedFrom < notification.message.created
- } : {}
- })
- }
- }
-
- private async onRemoveNotificationEvent(event: NotificationRemovedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
-
- const deleted = this.result.delete(event.message)
-
- if (deleted !== undefined) {
- await this.notify()
- }
- }
-
- private async onRemoveNotificationContextEvent(event: NotificationContextRemovedEvent): Promise {
- if (this.result instanceof Promise) {
- this.result = await this.result
- }
-
- if (this.params.context != null && this.params.context !== event.context) return
-
- if (event.context === this.params.context) {
- if (this.result.length === 0) return
- this.result.deleteAll()
- this.result.setHead(true)
- this.result.setTail(true)
- await this.notify()
- } else {
- const toRemove = this.result.getResult().filter(it => it.context === event.context)
- if (toRemove.length === 0) return
-
- for (const notification of toRemove) {
- this.result.delete(notification.message.id)
+ : {}),
+ ...(event.update.archivedFrom !== undefined
+ ? {
+ archived: event.update.archivedFrom < notification.message.created
}
- await this.notify()
- }
+ : {})
+ })
+ }
+ }
+ private async onRemoveNotificationEvent (event: NotificationRemovedEvent): Promise {
+ if (this.result instanceof Promise) {
+ this.result = await this.result
}
+ const deleted = this.result.delete(event.message)
+
+ if (deleted !== undefined) {
+ await this.notify()
+ }
+ }
+
+ private async onRemoveNotificationContextEvent (event: NotificationContextRemovedEvent): Promise {
+ if (this.result instanceof Promise) {
+ this.result = await this.result
+ }
+
+ if (this.params.context != null && this.params.context !== event.context) return
+
+ if (event.context === this.params.context) {
+ if (this.result.length === 0) return
+ this.result.deleteAll()
+ this.result.setHead(true)
+ this.result.setTail(true)
+ await this.notify()
+ } else {
+ const toRemove = this.result.getResult().filter((it) => it.context === event.context)
+ if (toRemove.length === 0) return
+
+ for (const notification of toRemove) {
+ this.result.delete(notification.message.id)
+ }
+ await this.notify()
+ }
+ }
}
diff --git a/packages/query/src/query.ts b/packages/query/src/query.ts
index fd95c80cf3..a770f1b0ef 100644
--- a/packages/query/src/query.ts
+++ b/packages/query/src/query.ts
@@ -1,11 +1,11 @@
-import { Direction, type ID, SortOrder } from '@hcengineering/communication-types'
+import { SortingOrder, type ID } from '@hcengineering/communication-types'
import { type ResponseEvent, type QueryCallback, type QueryClient } from '@hcengineering/communication-sdk-types'
import { QueryResult } from './result'
-import { defaultQueryParams, type FindParams, type Query, type QueryId } from './types'
+import { defaultQueryParams, type FindParams, type PagedQuery, type QueryId } from './types'
import { WindowImpl } from './window'
-export class BaseQuery implements Query {
+export class BaseQuery implements PagedQuery {
protected result: QueryResult | Promise>
private forward: Promise | T[] = []
private backward: Promise | T[] = []
@@ -24,20 +24,18 @@ export class BaseQuery implements Query {
const limit = this.params.limit ?? defaultQueryParams.limit
const findParams = {
...this.params,
- excluded: this.params.excluded ?? defaultQueryParams.excluded,
- direction: this.params.direction ?? defaultQueryParams.direction,
- sort: this.params.sort ?? defaultQueryParams.sort,
+ sort: this.params.order ?? defaultQueryParams.order,
limit: limit + 1
}
const findPromise = this.find(findParams)
this.result = findPromise.then((res) => {
- const isTail = params.from ? res.length <= limit : params.sort === SortOrder.Desc
- const isHead = params.from === undefined && params.sort === SortOrder.Asc
+ const isTail = false
+ const isHead = false
if (!isTail) {
res.pop()
}
- const qResult = new QueryResult(res, this.getObjectId)
+ const qResult = new QueryResult(res, (x) => this.getObjectId(x))
qResult.setTail(isTail)
qResult.setHead(isHead)
@@ -55,25 +53,25 @@ export class BaseQuery implements Query {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async find(params: FindParams): Promise {
- /*Implement in subclass*/
+ /* Implement in subclass */
return [] as T[]
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected getObjectId(object: T): ID {
- /*Implement in subclass*/
+ /* Implement in subclass */
return '' as ID
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected getObjectDate(object: T): Date {
- /*Implement in subclass*/
- return new Date(0) as Date
+ /* Implement in subclass */
+ return new Date(0)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async onEvent(event: ResponseEvent): Promise {
- /*Implement in subclass*/
+ /* Implement in subclass */
}
setCallback(callback: QueryCallback): void {
@@ -99,7 +97,7 @@ export class BaseQuery implements Query {
this.callback(window)
}
- async loadForward() {
+ async requestLoadNextPage(): Promise {
if (this.result instanceof Promise) {
this.result = await this.result
}
@@ -117,9 +115,8 @@ export class BaseQuery implements Query {
...this.params,
from: this.getObjectDate(last),
excluded: true,
- direction: Direction.Forward,
limit: limit + 1,
- sort: SortOrder.Asc
+ order: SortingOrder.Ascending
}
const forward = this.find(findParams)
@@ -139,7 +136,7 @@ export class BaseQuery implements Query {
})
}
- async loadBackward() {
+ async requestLoadPrevPage(): Promise {
if (this.result instanceof Promise) {
this.result = await this.result
}
@@ -149,17 +146,15 @@ export class BaseQuery implements Query {
if (this.result.isHead()) return
- const first = this.params.sort === SortOrder.Asc ? this.result.getFirst() : this.result.getLast()
+ const first = this.params.order === SortingOrder.Ascending ? this.result.getFirst() : this.result.getLast()
if (first === undefined) return
const limit = this.params.limit ?? defaultQueryParams.limit
const findParams: FindParams = {
...this.params,
from: this.getObjectDate(first),
- excluded: true,
- direction: Direction.Backward,
limit: limit + 1,
- sort: SortOrder.Desc
+ order: SortingOrder.Descending
}
const backward = this.find(findParams)
@@ -172,7 +167,7 @@ export class BaseQuery implements Query {
res.pop()
}
- if (this.params.sort === SortOrder.Asc) {
+ if (this.params.order === SortingOrder.Ascending) {
const reversed = res.reverse()
this.result.prepend(reversed)
} else {
diff --git a/packages/query/src/result.ts b/packages/query/src/result.ts
index 58e288c967..5186ccebd1 100644
--- a/packages/query/src/result.ts
+++ b/packages/query/src/result.ts
@@ -25,11 +25,11 @@ export class QueryResult {
return this.head
}
- setHead(head: boolean) {
+ setHead(head: boolean): void {
this.head = head
}
- setTail(tail: boolean) {
+ setTail(tail: boolean): void {
this.tail = tail
}
@@ -47,7 +47,7 @@ export class QueryResult {
return object
}
- deleteAll() {
+ deleteAll(): void {
this.objectById.clear()
}
@@ -71,12 +71,12 @@ export class QueryResult {
return Array.from(this.objectById.values())[this.objectById.size - 1]
}
- prepend(objects: T[]) {
+ prepend(objects: T[]): void {
const current = Array.from(this.objectById.entries())
this.objectById = new Map([...objects.map<[ID, T]>((object) => [this.getId(object), object]), ...current])
}
- append(objects: T[]) {
+ append(objects: T[]): void {
for (const object of objects) {
this.objectById.set(this.getId(object), object)
}
diff --git a/packages/query/src/types.ts b/packages/query/src/types.ts
index 31ddd46f95..cf4a6b8e4f 100644
--- a/packages/query/src/types.ts
+++ b/packages/query/src/types.ts
@@ -1,33 +1,34 @@
import { type ResponseEvent } from '@hcengineering/communication-sdk-types'
-import { Direction, SortOrder, type Window } from '@hcengineering/communication-types'
+import { SortingOrder, type Window } from '@hcengineering/communication-types'
-import { QueryResult } from './result.ts'
+import { QueryResult } from './result'
export type QueryId = number
export const defaultQueryParams = {
limit: 50,
- excluded: false,
- direction: Direction.Forward,
- sort: SortOrder.Desc
+ order: SortingOrder.Ascending
}
-export type FindParams = Partial & {
- from?: Date
+export enum Direction {
+ Forward = 1,
+ Backward = -1
}
-export interface Query {
+export type FindParams = Partial
+
+export interface PagedQuery {
readonly id: QueryId
readonly params: P
- onEvent(event: ResponseEvent): Promise
+ onEvent: (event: ResponseEvent) => Promise
- loadForward(): Promise
- loadBackward(): Promise
+ requestLoadNextPage: () => Promise
+ requestLoadPrevPage: () => Promise
- unsubscribe(): Promise
+ unsubscribe: () => Promise
- setCallback(callback: (window: Window) => void): void
- removeCallback(): void
- copyResult(): QueryResult | undefined
+ setCallback: (callback: (window: Window) => void) => void
+ removeCallback: () => void
+ copyResult: () => QueryResult | undefined
}
diff --git a/packages/query/src/window.ts b/packages/query/src/window.ts
index 824df8615a..a31d7026cb 100644
--- a/packages/query/src/window.ts
+++ b/packages/query/src/window.ts
@@ -1,13 +1,13 @@
import type { Window } from '@hcengineering/communication-types'
-import type { Query } from './types'
+import type { PagedQuery } from './types'
export class WindowImpl implements Window {
constructor(
private readonly result: T[],
private readonly isTail: boolean,
private readonly isHead: boolean,
- private readonly query: Query
+ private readonly query: PagedQuery
) {}
getResult(): T[] {
@@ -16,12 +16,12 @@ export class WindowImpl implements Window {
async loadNextPage(): Promise {
if (!this.hasNextPage()) return
- await this.query.loadForward()
+ await this.query.requestLoadNextPage()
}
async loadPrevPage(): Promise {
if (!this.hasPrevPage()) return
- await this.query.loadBackward()
+ await this.query.requestLoadPrevPage()
}
hasNextPage(): boolean {
diff --git a/packages/sdk-types/package.json b/packages/sdk-types/package.json
index 4295ff7149..084cb2176c 100644
--- a/packages/sdk-types/package.json
+++ b/packages/sdk-types/package.json
@@ -1,6 +1,6 @@
{
"name": "@hcengineering/communication-sdk-types",
- "version": "0.1.9",
+ "version": "0.1.47",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./types/index.d.ts",
@@ -20,7 +20,7 @@
},
"repository": {
"type": "git",
- "url": "git+https://github.com/hcengineering/communication.git",
+ "url": "git+https://github.com/hcengineering/communication.git"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com"
diff --git a/packages/sdk-types/src/client.ts b/packages/sdk-types/src/client.ts
index 8d03ea2e70..36415edfb6 100644
--- a/packages/sdk-types/src/client.ts
+++ b/packages/sdk-types/src/client.ts
@@ -9,7 +9,9 @@ import type {
NotificationContextUpdate,
RichText,
SocialID,
- Notification
+ Notification,
+ FindMessagesGroupsParams,
+ MessagesGroup
} from '@hcengineering/communication-types'
import type { FindMessagesParams } from '@hcengineering/communication-types'
@@ -18,7 +20,7 @@ import type { ResponseEvent } from './responseEvent.ts'
export interface Client {
createMessage(card: CardID, content: RichText, creator: SocialID): Promise
removeMessage(card: CardID, id: MessageID): Promise
- createPatch(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise
+ updateMessage(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise
createReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise
removeReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise
@@ -26,6 +28,8 @@ export interface Client {
createAttachment(card: CardID, message: MessageID, attachment: CardID, creator: SocialID): Promise
removeAttachment(card: CardID, message: MessageID, attachment: CardID): Promise
+ createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise
+
createNotification(message: MessageID, context: ContextID): Promise
removeNotification(message: MessageID, context: ContextID): Promise
@@ -36,6 +40,7 @@ export interface Client {
onEvent(event: ResponseEvent): void
findMessages(params: FindMessagesParams, queryId?: number): Promise
+ findMessagesGroups(params: FindMessagesGroupsParams): Promise
findNotificationContexts(params: FindNotificationContextParams, queryId?: number): Promise
findNotifications(params: FindNotificationsParams, queryId?: number): Promise
diff --git a/packages/sdk-types/src/db.ts b/packages/sdk-types/src/db.ts
index 24d9a1cb85..fecd247840 100644
--- a/packages/sdk-types/src/db.ts
+++ b/packages/sdk-types/src/db.ts
@@ -14,47 +14,55 @@ import type {
BlobID,
FindMessagesGroupsParams,
MessagesGroup,
- WorkspaceID
+ WorkspaceID,
+ PatchType,
+ Thread
} from '@hcengineering/communication-types'
export interface DbAdapter {
createMessage(card: CardID, content: RichText, creator: SocialID, created: Date): Promise
+ removeMessage(card: CardID, id: MessageID, socialIds?: SocialID[]): Promise
+ removeMessages(card: CardID, fromId: MessageID, toId: MessageID): Promise