mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-13 05:07:43 +02:00
@@ -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=="],
|
||||
|
||||
+8
-7
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -44,7 +44,7 @@ class BaseQuery<P extends Record<string, any>, C extends QueryCallback<any>> {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<void> {
|
||||
async updateMessage(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
@@ -98,6 +106,10 @@ class DbClient implements Client {
|
||||
return rawMessages.map((it) => this.toMessage(it))
|
||||
}
|
||||
|
||||
async findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]> {
|
||||
return await this.db.findMessagesGroups(params)
|
||||
}
|
||||
|
||||
async findMessage(params: FindMessagesParams): Promise<Message | undefined> {
|
||||
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<Notification[]> {
|
||||
//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<void> {
|
||||
//TODO: implement
|
||||
}
|
||||
|
||||
async unsubscribeQuery() {
|
||||
//ignore
|
||||
}
|
||||
@@ -178,5 +196,5 @@ export async function getSqliteClient(
|
||||
dbUrl = 'file:communication.sqlite3?vfs=opfs'
|
||||
): Promise<Client> {
|
||||
const db = await createSqliteDbAdapter(dbUrl)
|
||||
return new DbClient(db, workspace, personalWorkspace)
|
||||
return new DbClient(db, workspace, personalWorkspace) as unknown as Client
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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<void> {
|
||||
async updateMessage(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise<void> {
|
||||
const event: CreatePatchEvent = {
|
||||
type: RequestEventType.CreatePatch,
|
||||
card,
|
||||
@@ -130,39 +132,11 @@ class WsClient implements Client {
|
||||
}
|
||||
|
||||
async findMessages(params: FindMessagesParams, queryId?: number): Promise<Message[]> {
|
||||
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<MessagesGroup[]> {
|
||||
return await this.ws.send('findMessagesGroups', [params])
|
||||
}
|
||||
|
||||
async createNotification(message: MessageID, context: ContextID): Promise<void> {
|
||||
@@ -229,12 +203,23 @@ class WsClient implements Client {
|
||||
private async sendEvent(event: RequestEvent): Promise<EventResult> {
|
||||
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<void> {
|
||||
//TODO: implement
|
||||
}
|
||||
|
||||
close() {
|
||||
void this.ws.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWebsocketClient(url: string, token: string): Promise<Client> {
|
||||
return new WsClient(url, token)
|
||||
export async function getWebsocketClient(
|
||||
url: string,
|
||||
token: string,
|
||||
workspace: WorkspaceID,
|
||||
filesUrl: string
|
||||
): Promise<Client> {
|
||||
const client = new WsClient(url, token)
|
||||
initLiveQueries(client, workspace, filesUrl)
|
||||
return client
|
||||
}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
import { createMessagesQuery, createNotificationsQuery } from '@hcengineering/communication-client-query'
|
||||
|
||||
export * from './client'
|
||||
export { createMessagesQuery, createNotificationsQuery }
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
DROP SCHEMA IF EXISTS communication CASCADE;
|
||||
CREATE SCHEMA IF NOT EXISTS communication;
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
CREATE INDEX IF NOT EXISTS idx_patch_workspace_card_message ON communication.patch (workspace_id, card_id, message_id);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
);
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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<MessageID> {
|
||||
return await this.message.createMessage(card, content, creator, created)
|
||||
}
|
||||
|
||||
async removeMessage(card: CardID, message: MessageID, socialIds?: SocialID[]): Promise<void> {
|
||||
await this.message.removeMessage(card, message, socialIds)
|
||||
}
|
||||
|
||||
async removeMessages(card: CardID, fromId: MessageID, toId: MessageID): Promise<void> {
|
||||
await this.message.removeMessages(card, fromId, toId)
|
||||
}
|
||||
|
||||
async createPatch(
|
||||
card: CardID,
|
||||
message: MessageID,
|
||||
type: PatchType,
|
||||
content: RichText,
|
||||
creator: SocialID,
|
||||
created: Date
|
||||
): Promise<void> {
|
||||
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<MessageID | undefined> {
|
||||
return await this.message.removeMessage(card, message)
|
||||
}
|
||||
|
||||
async removeMessages(card: CardID, ids: MessageID[]): Promise<MessageID[]> {
|
||||
return await this.message.removeMessages(card, ids)
|
||||
async removePatches(card: CardID, fromId: MessageID, toId: MessageID): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await this.message.removeMessagesGroup(card, blobId)
|
||||
}
|
||||
|
||||
async createReaction(
|
||||
@@ -77,35 +92,35 @@ export class CockroachAdapter implements DbAdapter {
|
||||
creator: SocialID,
|
||||
created: Date
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
return await this.message.createAttachment(message, attachment, creator, created)
|
||||
await this.message.createAttachment(message, attachment, creator, created)
|
||||
}
|
||||
|
||||
async removeAttachment(message: MessageID, attachment: CardID): Promise<void> {
|
||||
return await this.message.removeAttachment(message, attachment)
|
||||
await this.message.removeAttachment(message, attachment)
|
||||
}
|
||||
|
||||
async findMessages(params: FindMessagesParams): Promise<Message[]> {
|
||||
return await this.message.find(params)
|
||||
async createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise<void> {
|
||||
await this.message.createThread(card, message, thread, created)
|
||||
}
|
||||
|
||||
async findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]> {
|
||||
return await this.message.findGroups(params)
|
||||
async updateThread(thread: CardID, lastReply: Date, op: 'increment' | 'decrement'): Promise<void> {
|
||||
await this.message.updateThread(thread, lastReply, op)
|
||||
}
|
||||
|
||||
async createNotification(message: MessageID, context: ContextID): Promise<void> {
|
||||
return await this.notification.createNotification(message, context)
|
||||
await this.notification.createNotification(message, context)
|
||||
}
|
||||
|
||||
async removeNotification(message: MessageID, context: ContextID): Promise<void> {
|
||||
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<void> {
|
||||
return await this.notification.updateContext(context, update)
|
||||
await this.notification.updateContext(context, update)
|
||||
}
|
||||
|
||||
async removeContext(context: ContextID): Promise<void> {
|
||||
return await this.notification.removeContext(context)
|
||||
await this.notification.removeContext(context)
|
||||
}
|
||||
|
||||
// Finds
|
||||
async findMessages(params: FindMessagesParams): Promise<Message[]> {
|
||||
return await this.message.find(params)
|
||||
}
|
||||
|
||||
async findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]> {
|
||||
return await this.message.findMessagesGroups(params)
|
||||
}
|
||||
|
||||
async findThread(thread: CardID): Promise<Thread | undefined> {
|
||||
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<DbAdapter> {
|
||||
const db = connect(connectionString)
|
||||
const sqlClient = await db.getClient()
|
||||
export async function createDbAdapter(
|
||||
connectionString: string,
|
||||
workspace: WorkspaceID,
|
||||
logger?: Logger,
|
||||
options?: Options
|
||||
): Promise<DbAdapter> {
|
||||
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<T extends any[] = (Row & Iterable<Row>)[]>(query: string, params?: ParameterOrJSON<any>[]): Promise<T> {
|
||||
return await retry(() => this.fetch<T>(query, params), { retries: 5 })
|
||||
}
|
||||
|
||||
private async fetch<T extends any[] = (Row & Iterable<Row>)[]>(
|
||||
query: string,
|
||||
params?: ParameterOrJSON<any>[]
|
||||
): Promise<T> {
|
||||
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<T extends any[] = (Row & Iterable<Row>)[]>(query: string, params?: ParameterOrJSON<any>[]): Promise<T> {
|
||||
return await this.sql.unsafe<T>(query, params)
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.db.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -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<T extends any[] = (Row & Iterable<Row>)[]>(
|
||||
sql: string,
|
||||
params?: ParameterOrJSON<any>[],
|
||||
name?: string
|
||||
): Promise<T> {
|
||||
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<string, any>): Promise<void> {
|
||||
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<string, any>, returnField: string): Promise<any> {
|
||||
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<string, any>): Promise<void> {
|
||||
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<string, any>, returnField: string): Promise<any[]> {
|
||||
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<T extends any[] = (Row & Iterable<Row>)[]>(
|
||||
name: string,
|
||||
logger: Logger,
|
||||
sql: string,
|
||||
params?: ParameterOrJSON<any>[]
|
||||
): Promise<T> {
|
||||
const start = performance.now()
|
||||
|
||||
try {
|
||||
return await this.client.execute(sql, params)
|
||||
} finally {
|
||||
const time = performance.now() - start
|
||||
logger.info(name, { time })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MessageID> {
|
||||
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<MessageID> {
|
||||
const id = generateMessageId()
|
||||
const db: MessageDb = {
|
||||
id,
|
||||
workspace_id: this.workspace,
|
||||
card_id: card,
|
||||
content,
|
||||
creator,
|
||||
created
|
||||
}
|
||||
|
||||
async removeMessage(card: CardID, message: MessageID): Promise<MessageID | undefined> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
const db: PatchDb = {
|
||||
workspace_id: this.workspace,
|
||||
card_id: card,
|
||||
message_id: message,
|
||||
type,
|
||||
content,
|
||||
creator,
|
||||
created
|
||||
}
|
||||
|
||||
async removeMessages(card: CardID, ids: MessageID[]): Promise<MessageID[]> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<Message[]> {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<Thread | undefined> {
|
||||
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<MessagesGroup[]> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<Message[]> {
|
||||
//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<MessagesGroup[]> {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
const dbData: NotificationDb = {
|
||||
message_id: message,
|
||||
context
|
||||
}
|
||||
await this.insert(TableName.Notification, dbData)
|
||||
async createNotification (message: MessageID, context: ContextID): Promise<void> {
|
||||
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<void> {
|
||||
// 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<ContextID> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
const dbData: Partial<ContextDb> = {}
|
||||
|
||||
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<void> {
|
||||
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<ContextID> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
const dbData: Partial<ContextDb> = {}
|
||||
|
||||
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<NotificationContext[]> {
|
||||
const select = `
|
||||
async findContexts (
|
||||
params: FindNotificationContextParams,
|
||||
personalWorkspaces: WorkspaceID[],
|
||||
workspace?: WorkspaceID
|
||||
): Promise<NotificationContext[]> {
|
||||
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<Notification[]> {
|
||||
//TODO: experiment with select to improve performance, should join with attachments and reactions?
|
||||
const select = `
|
||||
async findNotifications (
|
||||
params: FindNotificationsParams,
|
||||
personalWorkspace: WorkspaceID,
|
||||
workspace?: WorkspaceID
|
||||
): Promise<Notification[]> {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
if (param != null) {
|
||||
return { where: `${table}.${dbField} = $${index}::${type}`, value: param }
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './adapter.ts'
|
||||
export * from './adapter'
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { type ParameterOrJSON, type Row } from 'postgres'
|
||||
|
||||
export interface Logger {
|
||||
info: (message: string, data?: Record<string, any>) => void
|
||||
warn: (message: string, data?: Record<string, any>) => void
|
||||
error: (message: string, data?: Record<string, any>) => void
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
withLogs?: boolean
|
||||
}
|
||||
|
||||
export interface SqlClient {
|
||||
execute: <T extends any[] = (Row & Iterable<Row>)[]>(query: string, params?: ParameterOrJSON<any>[]) => Promise<T>
|
||||
close: () => void
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './message.ts'
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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<Message> | 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) {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
+17
-12
@@ -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<QueryId, Query>()
|
||||
private readonly queries = new Map<QueryId, PagedQuery>()
|
||||
private readonly unsubscribed = new Set<QueryId>()
|
||||
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<void> {
|
||||
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) {
|
||||
|
||||
@@ -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<Message, FindMessagesParams> {
|
||||
override async find(params: FindMessagesParams): Promise<Message[]> {
|
||||
return this.client.findMessages(params, this.id)
|
||||
const GROUPS_LIMIT = 20
|
||||
|
||||
export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
|
||||
protected result: Promise<QueryResult<Message>> | QueryResult<Message>
|
||||
|
||||
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<Message>,
|
||||
initialResult?: QueryResult<Message>
|
||||
) {
|
||||
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<void> {
|
||||
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<Message>): void {
|
||||
this.callback = callback
|
||||
void this.notify()
|
||||
}
|
||||
|
||||
removeCallback (): void {
|
||||
this.callback = () => {}
|
||||
}
|
||||
|
||||
async requestLoadNextPage (): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<Message>): Promise<QueryResult<Message>> {
|
||||
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<Message>): 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<Message>): Promise<Message[]> {
|
||||
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<Message>): 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<void> {
|
||||
if (this.result instanceof Promise) {
|
||||
this.result = await this.result
|
||||
return { messages, fromDb: false }
|
||||
}
|
||||
|
||||
private async findPrevMessages (limit: number, result: QueryResult<Message>): Promise<Message[]> {
|
||||
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<Message>): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<ParsedFile> {
|
||||
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<MessageID, Patch[]>()
|
||||
for (const patch of patches) {
|
||||
patchesMap.set(patch.message, [...(patchesMap.get(patch.message) ?? []), patch])
|
||||
}
|
||||
|
||||
private async onRemoveAttachmentEvent(event: AttachmentRemovedEvent): Promise<void> {
|
||||
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<MessagesGroup | undefined> {
|
||||
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<MessagesGroup[]> {
|
||||
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<Message[]> {
|
||||
return await this.client.findMessages(params, this.id)
|
||||
}
|
||||
|
||||
private async notify (): Promise<void> {
|
||||
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<void> {
|
||||
await this.client.unsubscribeQuery(this.id)
|
||||
}
|
||||
|
||||
async onEvent (event: ResponseEvent): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<Message> | undefined {
|
||||
if (this.result instanceof Promise) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return this.result.copy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Notification, FindNotificationsParams> {
|
||||
override async find(params: FindNotificationsParams): Promise<Notification[]> {
|
||||
return this.client.findNotifications(params, this.id)
|
||||
override async find (params: FindNotificationsParams): Promise<Notification[]> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-23
@@ -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<T, P extends FindParams> implements Query<T, P> {
|
||||
export class BaseQuery<T, P extends FindParams> implements PagedQuery<T, P> {
|
||||
protected result: QueryResult<T> | Promise<QueryResult<T>>
|
||||
private forward: Promise<T[]> | T[] = []
|
||||
private backward: Promise<T[]> | T[] = []
|
||||
@@ -24,20 +24,18 @@ export class BaseQuery<T, P extends FindParams> implements Query<T, P> {
|
||||
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<T, P extends FindParams> implements Query<T, P> {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected async find(params: FindParams): Promise<T[]> {
|
||||
/*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<void> {
|
||||
/*Implement in subclass*/
|
||||
/* Implement in subclass */
|
||||
}
|
||||
|
||||
setCallback(callback: QueryCallback<T>): void {
|
||||
@@ -99,7 +97,7 @@ export class BaseQuery<T, P extends FindParams> implements Query<T, P> {
|
||||
this.callback(window)
|
||||
}
|
||||
|
||||
async loadForward() {
|
||||
async requestLoadNextPage(): Promise<void> {
|
||||
if (this.result instanceof Promise) {
|
||||
this.result = await this.result
|
||||
}
|
||||
@@ -117,9 +115,8 @@ export class BaseQuery<T, P extends FindParams> implements Query<T, P> {
|
||||
...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<T, P extends FindParams> implements Query<T, P> {
|
||||
})
|
||||
}
|
||||
|
||||
async loadBackward() {
|
||||
async requestLoadPrevPage(): Promise<void> {
|
||||
if (this.result instanceof Promise) {
|
||||
this.result = await this.result
|
||||
}
|
||||
@@ -149,17 +146,15 @@ export class BaseQuery<T, P extends FindParams> implements Query<T, P> {
|
||||
|
||||
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<T, P extends FindParams> implements Query<T, P> {
|
||||
res.pop()
|
||||
}
|
||||
|
||||
if (this.params.sort === SortOrder.Asc) {
|
||||
if (this.params.order === SortingOrder.Ascending) {
|
||||
const reversed = res.reverse()
|
||||
this.result.prepend(reversed)
|
||||
} else {
|
||||
|
||||
@@ -25,11 +25,11 @@ export class QueryResult<T> {
|
||||
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<T> {
|
||||
return object
|
||||
}
|
||||
|
||||
deleteAll() {
|
||||
deleteAll(): void {
|
||||
this.objectById.clear()
|
||||
}
|
||||
|
||||
@@ -71,12 +71,12 @@ export class QueryResult<T> {
|
||||
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)
|
||||
}
|
||||
|
||||
+16
-15
@@ -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<typeof defaultQueryParams> & {
|
||||
from?: Date
|
||||
export enum Direction {
|
||||
Forward = 1,
|
||||
Backward = -1
|
||||
}
|
||||
|
||||
export interface Query<R = any, P = FindParams> {
|
||||
export type FindParams = Partial<typeof defaultQueryParams>
|
||||
|
||||
export interface PagedQuery<R = any, P = FindParams> {
|
||||
readonly id: QueryId
|
||||
readonly params: P
|
||||
|
||||
onEvent(event: ResponseEvent): Promise<void>
|
||||
onEvent: (event: ResponseEvent) => Promise<void>
|
||||
|
||||
loadForward(): Promise<void>
|
||||
loadBackward(): Promise<void>
|
||||
requestLoadNextPage: () => Promise<void>
|
||||
requestLoadPrevPage: () => Promise<void>
|
||||
|
||||
unsubscribe(): Promise<void>
|
||||
unsubscribe: () => Promise<void>
|
||||
|
||||
setCallback(callback: (window: Window<R>) => void): void
|
||||
removeCallback(): void
|
||||
copyResult(): QueryResult<R> | undefined
|
||||
setCallback: (callback: (window: Window<R>) => void) => void
|
||||
removeCallback: () => void
|
||||
copyResult: () => QueryResult<R> | undefined
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { Window } from '@hcengineering/communication-types'
|
||||
|
||||
import type { Query } from './types'
|
||||
import type { PagedQuery } from './types'
|
||||
|
||||
export class WindowImpl<T> implements Window<T> {
|
||||
constructor(
|
||||
private readonly result: T[],
|
||||
private readonly isTail: boolean,
|
||||
private readonly isHead: boolean,
|
||||
private readonly query: Query<T>
|
||||
private readonly query: PagedQuery<T>
|
||||
) {}
|
||||
|
||||
getResult(): T[] {
|
||||
@@ -16,12 +16,12 @@ export class WindowImpl<T> implements Window<T> {
|
||||
|
||||
async loadNextPage(): Promise<void> {
|
||||
if (!this.hasNextPage()) return
|
||||
await this.query.loadForward()
|
||||
await this.query.requestLoadNextPage()
|
||||
}
|
||||
|
||||
async loadPrevPage(): Promise<void> {
|
||||
if (!this.hasPrevPage()) return
|
||||
await this.query.loadBackward()
|
||||
await this.query.requestLoadPrevPage()
|
||||
}
|
||||
|
||||
hasNextPage(): boolean {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<MessageID>
|
||||
removeMessage(card: CardID, id: MessageID): Promise<void>
|
||||
createPatch(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise<void>
|
||||
updateMessage(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise<void>
|
||||
|
||||
createReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise<void>
|
||||
removeReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise<void>
|
||||
@@ -26,6 +28,8 @@ export interface Client {
|
||||
createAttachment(card: CardID, message: MessageID, attachment: CardID, creator: SocialID): Promise<void>
|
||||
removeAttachment(card: CardID, message: MessageID, attachment: CardID): Promise<void>
|
||||
|
||||
createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise<void>
|
||||
|
||||
createNotification(message: MessageID, context: ContextID): Promise<void>
|
||||
removeNotification(message: MessageID, context: ContextID): Promise<void>
|
||||
|
||||
@@ -36,6 +40,7 @@ export interface Client {
|
||||
onEvent(event: ResponseEvent): void
|
||||
|
||||
findMessages(params: FindMessagesParams, queryId?: number): Promise<Message[]>
|
||||
findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]>
|
||||
findNotificationContexts(params: FindNotificationContextParams, queryId?: number): Promise<NotificationContext[]>
|
||||
findNotifications(params: FindNotificationsParams, queryId?: number): Promise<Notification[]>
|
||||
|
||||
|
||||
@@ -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<MessageID>
|
||||
removeMessage(card: CardID, id: MessageID, socialIds?: SocialID[]): Promise<void>
|
||||
removeMessages(card: CardID, fromId: MessageID, toId: MessageID): Promise<void>
|
||||
|
||||
removeMessage(card: CardID, id: MessageID): Promise<MessageID | undefined>
|
||||
removeMessages(card: CardID, ids: MessageID[]): Promise<MessageID[]>
|
||||
|
||||
createPatch(card: CardID, message: MessageID, content: RichText, creator: SocialID, created: Date): Promise<void>
|
||||
createPatch(
|
||||
card: CardID,
|
||||
message: MessageID,
|
||||
type: PatchType,
|
||||
content: RichText,
|
||||
creator: SocialID,
|
||||
created: Date
|
||||
): Promise<void>
|
||||
removePatches(card: CardID, fromId: MessageID, toId: MessageID): Promise<void>
|
||||
|
||||
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<void>
|
||||
removeMessagesGroup(card: CardID, blobId: BlobID): Promise<void>
|
||||
|
||||
createReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID, created: Date): Promise<void>
|
||||
|
||||
removeReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise<void>
|
||||
|
||||
createAttachment(message: MessageID, attachment: CardID, creator: SocialID, created: Date): Promise<void>
|
||||
|
||||
removeAttachment(message: MessageID, attachment: CardID): Promise<void>
|
||||
|
||||
findMessages(query: FindMessagesParams): Promise<Message[]>
|
||||
createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise<void>
|
||||
updateThread(thread: CardID, lastReply: Date, op: 'increment' | 'decrement'): Promise<void>
|
||||
|
||||
findMessagesGroups(query: FindMessagesGroupsParams): Promise<MessagesGroup[]>
|
||||
findMessages(params: FindMessagesParams): Promise<Message[]>
|
||||
findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]>
|
||||
findThread(thread: CardID): Promise<Thread | undefined>
|
||||
|
||||
createNotification(message: MessageID, context: ContextID): Promise<void>
|
||||
|
||||
removeNotification(message: MessageID, context: ContextID): Promise<void>
|
||||
|
||||
createContext(personalWorkspace: WorkspaceID, card: CardID, lastView?: Date, lastUpdate?: Date): Promise<ContextID>
|
||||
|
||||
updateContext(context: ContextID, update: NotificationContextUpdate): Promise<void>
|
||||
|
||||
removeContext(context: ContextID): Promise<void>
|
||||
|
||||
findContexts(
|
||||
@@ -62,7 +70,6 @@ export interface DbAdapter {
|
||||
personalWorkspaces: WorkspaceID[],
|
||||
workspace?: WorkspaceID
|
||||
): Promise<NotificationContext[]>
|
||||
|
||||
findNotifications(
|
||||
params: FindNotificationsParams,
|
||||
personalWorkspace: WorkspaceID,
|
||||
|
||||
@@ -4,7 +4,13 @@ import type { Client } from './client'
|
||||
|
||||
export type QueryClient = Pick<
|
||||
Client,
|
||||
'onEvent' | 'findMessages' | 'findNotificationContexts' | 'findNotifications' | 'unsubscribeQuery' | 'close'
|
||||
| 'onEvent'
|
||||
| 'findMessages'
|
||||
| 'findMessagesGroups'
|
||||
| 'findNotificationContexts'
|
||||
| 'findNotifications'
|
||||
| 'unsubscribeQuery'
|
||||
| 'close'
|
||||
>
|
||||
|
||||
export type QueryCallback<T> = (window: Window<T>) => void
|
||||
|
||||
@@ -5,24 +5,35 @@ import type {
|
||||
NotificationContextUpdate,
|
||||
RichText,
|
||||
SocialID,
|
||||
MessagesGroup
|
||||
MessagesGroup,
|
||||
BlobID
|
||||
} from '@hcengineering/communication-types'
|
||||
|
||||
export enum RequestEventType {
|
||||
CreateMessage = 'createMessage',
|
||||
RemoveMessage = 'removeMessage',
|
||||
RemoveMessages = 'removeMessages',
|
||||
|
||||
CreatePatch = 'createPatch',
|
||||
RemovePatches = 'removePatches',
|
||||
|
||||
CreateReaction = 'createReaction',
|
||||
RemoveReaction = 'removeReaction',
|
||||
|
||||
CreateAttachment = 'createAttachment',
|
||||
RemoveAttachment = 'removeAttachment',
|
||||
|
||||
CreateThread = 'createThread',
|
||||
|
||||
CreateMessagesGroup = 'createMessagesGroup',
|
||||
RemoveMessagesGroup = 'removeMessagesGroup',
|
||||
|
||||
CreateNotification = 'createNotification',
|
||||
RemoveNotification = 'removeNotification',
|
||||
|
||||
CreateNotificationContext = 'createNotificationContext',
|
||||
RemoveNotificationContext = 'removeNotificationContext',
|
||||
UpdateNotificationContext = 'updateNotificationContext',
|
||||
CreateMessagesGroup = 'createMessagesGroup'
|
||||
UpdateNotificationContext = 'updateNotificationContext'
|
||||
}
|
||||
|
||||
export type RequestEvent =
|
||||
@@ -30,6 +41,7 @@ export type RequestEvent =
|
||||
| RemoveMessageEvent
|
||||
| RemoveMessagesEvent
|
||||
| CreatePatchEvent
|
||||
| RemovePatchesEvent
|
||||
| CreateReactionEvent
|
||||
| RemoveReactionEvent
|
||||
| CreateAttachmentEvent
|
||||
@@ -40,6 +52,8 @@ export type RequestEvent =
|
||||
| RemoveNotificationContextEvent
|
||||
| UpdateNotificationContextEvent
|
||||
| CreateMessagesGroupEvent
|
||||
| RemoveMessagesGroupEvent
|
||||
| CreateThreadEvent
|
||||
|
||||
export interface CreateMessageEvent {
|
||||
type: RequestEventType.CreateMessage
|
||||
@@ -57,8 +71,8 @@ export interface RemoveMessageEvent {
|
||||
export interface RemoveMessagesEvent {
|
||||
type: RequestEventType.RemoveMessages
|
||||
card: CardID
|
||||
messages: MessageID[]
|
||||
silent?: boolean
|
||||
fromId: MessageID
|
||||
toId: MessageID
|
||||
}
|
||||
|
||||
export interface CreatePatchEvent {
|
||||
@@ -69,6 +83,13 @@ export interface CreatePatchEvent {
|
||||
creator: SocialID
|
||||
}
|
||||
|
||||
export interface RemovePatchesEvent {
|
||||
type: RequestEventType.RemovePatches
|
||||
card: CardID
|
||||
fromId: MessageID
|
||||
toId: MessageID
|
||||
}
|
||||
|
||||
export interface CreateReactionEvent {
|
||||
type: RequestEventType.CreateReaction
|
||||
card: CardID
|
||||
@@ -100,6 +121,13 @@ export interface RemoveAttachmentEvent {
|
||||
attachment: CardID
|
||||
}
|
||||
|
||||
export interface CreateThreadEvent {
|
||||
type: RequestEventType.CreateThread
|
||||
card: CardID
|
||||
message: MessageID
|
||||
thread: CardID
|
||||
}
|
||||
|
||||
export interface CreateNotificationEvent {
|
||||
type: RequestEventType.CreateNotification
|
||||
message: MessageID
|
||||
@@ -135,6 +163,12 @@ export interface CreateMessagesGroupEvent {
|
||||
group: MessagesGroup
|
||||
}
|
||||
|
||||
export interface RemoveMessagesGroupEvent {
|
||||
type: RequestEventType.RemoveMessagesGroup
|
||||
card: CardID
|
||||
blobId: BlobID
|
||||
}
|
||||
|
||||
export type EventResult = CreateMessageResult | CreateNotificationContextResult | {}
|
||||
|
||||
export interface CreateMessageResult {
|
||||
|
||||
@@ -9,18 +9,19 @@ import type {
|
||||
Patch,
|
||||
Reaction,
|
||||
SocialID,
|
||||
Notification
|
||||
Notification,
|
||||
Thread
|
||||
} from '@hcengineering/communication-types'
|
||||
|
||||
export enum ResponseEventType {
|
||||
MessageCreated = 'messageCreated',
|
||||
MessageRemoved = 'messageRemoved',
|
||||
MessagesRemoved = 'messagesRemoved',
|
||||
PatchCreated = 'patchCreated',
|
||||
ReactionCreated = 'reactionCreated',
|
||||
ReactionRemoved = 'reactionRemoved',
|
||||
AttachmentCreated = 'attachmentCreated',
|
||||
AttachmentRemoved = 'attachmentRemoved',
|
||||
ThreadCreated = 'threadCreated',
|
||||
NotificationCreated = 'notificationCreated',
|
||||
NotificationRemoved = 'notificationRemoved',
|
||||
NotificationContextCreated = 'notificationContextCreated',
|
||||
@@ -31,7 +32,6 @@ export enum ResponseEventType {
|
||||
export type ResponseEvent =
|
||||
| MessageCreatedEvent
|
||||
| MessageRemovedEvent
|
||||
| MessagesRemovedEvent
|
||||
| PatchCreatedEvent
|
||||
| ReactionCreatedEvent
|
||||
| ReactionRemovedEvent
|
||||
@@ -42,6 +42,7 @@ export type ResponseEvent =
|
||||
| NotificationContextCreatedEvent
|
||||
| NotificationContextRemovedEvent
|
||||
| NotificationContextUpdatedEvent
|
||||
| ThreadCreatedEvent
|
||||
|
||||
export interface MessageCreatedEvent {
|
||||
type: ResponseEventType.MessageCreated
|
||||
@@ -54,12 +55,6 @@ export interface MessageRemovedEvent {
|
||||
message: MessageID
|
||||
}
|
||||
|
||||
export interface MessagesRemovedEvent {
|
||||
type: ResponseEventType.MessagesRemoved
|
||||
card: CardID
|
||||
messages: MessageID[]
|
||||
}
|
||||
|
||||
export interface PatchCreatedEvent {
|
||||
type: ResponseEventType.PatchCreated
|
||||
card: CardID
|
||||
@@ -93,6 +88,11 @@ export interface AttachmentRemovedEvent {
|
||||
attachment: CardID
|
||||
}
|
||||
|
||||
export interface ThreadCreatedEvent {
|
||||
type: ResponseEventType.ThreadCreated
|
||||
thread: Thread
|
||||
}
|
||||
|
||||
export interface NotificationCreatedEvent {
|
||||
type: ResponseEventType.NotificationCreated
|
||||
personalWorkspace: string
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface ConnectionInfo {
|
||||
sessionId: string
|
||||
personalWorkspace: WorkspaceID
|
||||
socialIds: SocialID[]
|
||||
isSystem: boolean
|
||||
}
|
||||
|
||||
export interface ServerApi {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/communication-server",
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.47",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
type Reaction,
|
||||
type Attachment,
|
||||
type SocialID,
|
||||
type WorkspaceID
|
||||
type WorkspaceID,
|
||||
PatchType
|
||||
} from '@hcengineering/communication-types'
|
||||
import {
|
||||
type CreateAttachmentEvent,
|
||||
@@ -38,62 +39,65 @@ import {
|
||||
ResponseEventType,
|
||||
type CreateMessagesGroupEvent,
|
||||
type RemoveMessagesEvent,
|
||||
type MessagesRemovedEvent
|
||||
type ThreadCreatedEvent,
|
||||
type CreateThreadEvent,
|
||||
type ConnectionInfo,
|
||||
type RemovePatchesEvent,
|
||||
type RemoveMessagesGroupEvent
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
|
||||
export type Result = {
|
||||
export interface Result {
|
||||
responseEvent?: ResponseEvent
|
||||
result: EventResult
|
||||
}
|
||||
|
||||
export type UserInfo = {
|
||||
personalWorkspace: WorkspaceID
|
||||
socialIds: SocialID[]
|
||||
}
|
||||
|
||||
export class EventProcessor {
|
||||
constructor(
|
||||
private readonly db: DbAdapter,
|
||||
private readonly workspace: WorkspaceID
|
||||
) {}
|
||||
|
||||
async process(user: UserInfo, event: RequestEvent): Promise<Result> {
|
||||
async process(info: ConnectionInfo, event: RequestEvent): Promise<Result> {
|
||||
switch (event.type) {
|
||||
case RequestEventType.CreateMessage:
|
||||
return await this.createMessage(event, user)
|
||||
return await this.createMessage(event, info)
|
||||
case RequestEventType.RemoveMessage:
|
||||
return await this.removeMessage(event, user)
|
||||
return await this.removeMessage(event, info)
|
||||
case RequestEventType.RemoveMessages:
|
||||
return await this.removeMessages(event, user)
|
||||
return await this.removeMessages(event, info)
|
||||
case RequestEventType.CreatePatch:
|
||||
return await this.createPatch(event, user)
|
||||
return await this.createPatch(event, info)
|
||||
case RequestEventType.CreateReaction:
|
||||
return await this.createReaction(event, user)
|
||||
return await this.createReaction(event, info)
|
||||
case RequestEventType.RemoveReaction:
|
||||
return await this.removeReaction(event, user)
|
||||
return await this.removeReaction(event, info)
|
||||
case RequestEventType.CreateAttachment:
|
||||
return await this.createAttachment(event, user)
|
||||
return await this.createAttachment(event, info)
|
||||
case RequestEventType.RemoveAttachment:
|
||||
return await this.removeAttachment(event, user)
|
||||
return await this.removeAttachment(event, info)
|
||||
case RequestEventType.CreateNotification:
|
||||
return await this.createNotification(event, user)
|
||||
return await this.createNotification(event, info)
|
||||
case RequestEventType.RemoveNotification:
|
||||
return await this.removeNotification(event, user)
|
||||
return await this.removeNotification(event, info)
|
||||
case RequestEventType.CreateNotificationContext:
|
||||
return await this.createNotificationContext(event, user)
|
||||
return await this.createNotificationContext(event, info)
|
||||
case RequestEventType.RemoveNotificationContext:
|
||||
return await this.removeNotificationContext(event, user)
|
||||
return await this.removeNotificationContext(event, info)
|
||||
case RequestEventType.UpdateNotificationContext:
|
||||
return await this.updateNotificationContext(event, user)
|
||||
return await this.updateNotificationContext(event, info)
|
||||
case RequestEventType.CreateMessagesGroup:
|
||||
return await this.createMessagesGroup(event, user)
|
||||
return await this.createMessagesGroup(event, info)
|
||||
case RequestEventType.CreateThread:
|
||||
return await this.createThread(event, info)
|
||||
case RequestEventType.RemovePatches:
|
||||
return await this.removePatches(event, info)
|
||||
case RequestEventType.RemoveMessagesGroup:
|
||||
return await this.removeMessagesGroup(event, info)
|
||||
}
|
||||
}
|
||||
|
||||
private async createMessage(event: CreateMessageEvent, user: UserInfo): Promise<Result> {
|
||||
if (!user.socialIds.includes(event.creator)) {
|
||||
throw new Error('Forbidden')
|
||||
}
|
||||
private async createMessage(event: CreateMessageEvent, info: ConnectionInfo): Promise<Result> {
|
||||
this.checkCreator(info, event.creator)
|
||||
|
||||
const created = new Date()
|
||||
const id = await this.db.createMessage(event.card, event.content, event.creator, created)
|
||||
@@ -102,8 +106,7 @@ export class EventProcessor {
|
||||
card: event.card,
|
||||
content: event.content,
|
||||
creator: event.creator,
|
||||
created: created,
|
||||
edited: created,
|
||||
created,
|
||||
reactions: [],
|
||||
attachments: []
|
||||
}
|
||||
@@ -117,18 +120,17 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private async createPatch(event: CreatePatchEvent, user: UserInfo): Promise<Result> {
|
||||
if (!user.socialIds.includes(event.creator)) {
|
||||
throw new Error('Forbidden')
|
||||
}
|
||||
private async createPatch(event: CreatePatchEvent, info: ConnectionInfo): Promise<Result> {
|
||||
this.checkCreator(info, event.creator)
|
||||
const created = new Date()
|
||||
await this.db.createPatch(event.card, event.message, event.content, event.creator, created)
|
||||
await this.db.createPatch(event.card, event.message, PatchType.update, event.content, event.creator, created)
|
||||
|
||||
const patch: Patch = {
|
||||
type: PatchType.update,
|
||||
message: event.message,
|
||||
content: event.content,
|
||||
creator: event.creator,
|
||||
created: created
|
||||
created
|
||||
}
|
||||
const responseEvent: PatchCreatedEvent = {
|
||||
type: ResponseEventType.PatchCreated,
|
||||
@@ -141,16 +143,9 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async removeMessage(event: RemoveMessageEvent, _: UserInfo): Promise<Result> {
|
||||
const res = await this.db.removeMessage(event.card, event.message)
|
||||
|
||||
if (res === undefined) {
|
||||
return {
|
||||
responseEvent: undefined,
|
||||
result: { id: res }
|
||||
}
|
||||
}
|
||||
private async removeMessage(event: RemoveMessageEvent, info: ConnectionInfo): Promise<Result> {
|
||||
const socialIds = info.isSystem ? undefined : info.socialIds
|
||||
await this.db.removeMessage(event.card, event.message, socialIds)
|
||||
|
||||
const responseEvent: MessageRemovedEvent = {
|
||||
type: ResponseEventType.MessageRemoved,
|
||||
@@ -160,37 +155,34 @@ export class EventProcessor {
|
||||
|
||||
return {
|
||||
responseEvent,
|
||||
result: { id: res }
|
||||
result: {}
|
||||
}
|
||||
}
|
||||
|
||||
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async removeMessages(event: RemoveMessagesEvent, _: UserInfo): Promise<Result> {
|
||||
const ids = await this.db.removeMessages(event.card, event.messages)
|
||||
|
||||
if (event.silent === true) {
|
||||
return {
|
||||
responseEvent: undefined,
|
||||
result: { ids }
|
||||
}
|
||||
}
|
||||
|
||||
const responseEvent: MessagesRemovedEvent = {
|
||||
type: ResponseEventType.MessagesRemoved,
|
||||
card: event.card,
|
||||
messages: ids
|
||||
}
|
||||
|
||||
return {
|
||||
responseEvent,
|
||||
result: { ids }
|
||||
}
|
||||
}
|
||||
|
||||
private async createReaction(event: CreateReactionEvent, user: UserInfo): Promise<Result> {
|
||||
if (!user.socialIds.includes(event.creator)) {
|
||||
private async removeMessages(event: RemoveMessagesEvent, info: ConnectionInfo): Promise<Result> {
|
||||
if (!info.isSystem) {
|
||||
throw new Error('Forbidden')
|
||||
}
|
||||
await this.db.removeMessages(event.card, event.fromId, event.toId)
|
||||
|
||||
return {
|
||||
result: {}
|
||||
}
|
||||
}
|
||||
|
||||
private async removePatches(event: RemovePatchesEvent, info: ConnectionInfo): Promise<Result> {
|
||||
if (!info.isSystem) {
|
||||
throw new Error('Forbidden')
|
||||
}
|
||||
await this.db.removePatches(event.card, event.fromId, event.toId)
|
||||
|
||||
return {
|
||||
result: {}
|
||||
}
|
||||
}
|
||||
|
||||
private async createReaction(event: CreateReactionEvent, info: ConnectionInfo): Promise<Result> {
|
||||
this.checkCreator(info, event.creator)
|
||||
const created = new Date()
|
||||
await this.db.createReaction(event.card, event.message, event.reaction, event.creator, created)
|
||||
|
||||
@@ -198,7 +190,7 @@ export class EventProcessor {
|
||||
message: event.message,
|
||||
reaction: event.reaction,
|
||||
creator: event.creator,
|
||||
created: created
|
||||
created
|
||||
}
|
||||
const responseEvent: ReactionCreatedEvent = {
|
||||
type: ResponseEventType.ReactionCreated,
|
||||
@@ -211,10 +203,8 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private async removeReaction(event: RemoveReactionEvent, user: UserInfo): Promise<Result> {
|
||||
if (!user.socialIds.includes(event.creator)) {
|
||||
throw new Error('Forbidden')
|
||||
}
|
||||
private async removeReaction(event: RemoveReactionEvent, info: ConnectionInfo): Promise<Result> {
|
||||
this.checkCreator(info, event.creator)
|
||||
await this.db.removeReaction(event.card, event.message, event.reaction, event.creator)
|
||||
const responseEvent: ReactionRemovedEvent = {
|
||||
type: ResponseEventType.ReactionRemoved,
|
||||
@@ -229,8 +219,8 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async createAttachment(event: CreateAttachmentEvent, _: UserInfo): Promise<Result> {
|
||||
private async createAttachment(event: CreateAttachmentEvent, info: ConnectionInfo): Promise<Result> {
|
||||
this.checkCreator(info, event.creator)
|
||||
const created = new Date()
|
||||
await this.db.createAttachment(event.message, event.card, event.creator, created)
|
||||
|
||||
@@ -238,7 +228,7 @@ export class EventProcessor {
|
||||
message: event.message,
|
||||
card: event.card,
|
||||
creator: event.creator,
|
||||
created: created
|
||||
created
|
||||
}
|
||||
const responseEvent: AttachmentCreatedEvent = {
|
||||
type: ResponseEventType.AttachmentCreated,
|
||||
@@ -252,8 +242,8 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async removeAttachment(event: RemoveAttachmentEvent, _: UserInfo): Promise<Result> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async removeAttachment(event: RemoveAttachmentEvent, _: ConnectionInfo): Promise<Result> {
|
||||
await this.db.removeAttachment(event.message, event.card)
|
||||
const responseEvent: AttachmentRemovedEvent = {
|
||||
type: ResponseEventType.AttachmentRemoved,
|
||||
@@ -267,8 +257,8 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async createNotification(event: CreateNotificationEvent, _: UserInfo): Promise<Result> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async createNotification(event: CreateNotificationEvent, _: ConnectionInfo): Promise<Result> {
|
||||
await this.db.createNotification(event.message, event.context)
|
||||
|
||||
return {
|
||||
@@ -276,12 +266,12 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private async removeNotification(event: RemoveNotificationEvent, user: UserInfo): Promise<Result> {
|
||||
private async removeNotification(event: RemoveNotificationEvent, info: ConnectionInfo): Promise<Result> {
|
||||
await this.db.removeNotification(event.message, event.context)
|
||||
|
||||
const responseEvent: NotificationRemovedEvent = {
|
||||
type: ResponseEventType.NotificationRemoved,
|
||||
personalWorkspace: user.personalWorkspace,
|
||||
personalWorkspace: info.personalWorkspace,
|
||||
message: event.message,
|
||||
context: event.context
|
||||
}
|
||||
@@ -291,14 +281,17 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private async createNotificationContext(event: CreateNotificationContextEvent, user: UserInfo): Promise<Result> {
|
||||
const id = await this.db.createContext(user.personalWorkspace, event.card, event.lastView, event.lastUpdate)
|
||||
private async createNotificationContext(
|
||||
event: CreateNotificationContextEvent,
|
||||
info: ConnectionInfo
|
||||
): Promise<Result> {
|
||||
const id = await this.db.createContext(info.personalWorkspace, event.card, event.lastView, event.lastUpdate)
|
||||
const responseEvent: NotificationContextCreatedEvent = {
|
||||
type: ResponseEventType.NotificationContextCreated,
|
||||
context: {
|
||||
id,
|
||||
workspace: this.workspace,
|
||||
personalWorkspace: user.personalWorkspace,
|
||||
personalWorkspace: info.personalWorkspace,
|
||||
card: event.card,
|
||||
lastView: event.lastView,
|
||||
lastUpdate: event.lastUpdate
|
||||
@@ -310,11 +303,14 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private async removeNotificationContext(event: RemoveNotificationContextEvent, user: UserInfo): Promise<Result> {
|
||||
private async removeNotificationContext(
|
||||
event: RemoveNotificationContextEvent,
|
||||
info: ConnectionInfo
|
||||
): Promise<Result> {
|
||||
await this.db.removeContext(event.context)
|
||||
const responseEvent: NotificationContextRemovedEvent = {
|
||||
type: ResponseEventType.NotificationContextRemoved,
|
||||
personalWorkspace: user.personalWorkspace,
|
||||
personalWorkspace: info.personalWorkspace,
|
||||
context: event.context
|
||||
}
|
||||
return {
|
||||
@@ -323,12 +319,12 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
async updateNotificationContext(event: UpdateNotificationContextEvent, user: UserInfo): Promise<Result> {
|
||||
async updateNotificationContext(event: UpdateNotificationContextEvent, info: ConnectionInfo): Promise<Result> {
|
||||
await this.db.updateContext(event.context, event.update)
|
||||
|
||||
const responseEvent: NotificationContextUpdatedEvent = {
|
||||
type: ResponseEventType.NotificationContextUpdated,
|
||||
personalWorkspace: user.personalWorkspace,
|
||||
personalWorkspace: info.personalWorkspace,
|
||||
context: event.context,
|
||||
update: event.update
|
||||
}
|
||||
@@ -338,14 +334,54 @@ export class EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async createMessagesGroup(event: CreateMessagesGroupEvent, _: UserInfo): Promise<Result> {
|
||||
const { fromId, toId, fromDate, toDate, count } = event.group
|
||||
await this.db.createMessagesGroup(event.group.card, event.group.blobId, fromId, toId, fromDate, toDate, count)
|
||||
async createMessagesGroup(event: CreateMessagesGroupEvent, info: ConnectionInfo): Promise<Result> {
|
||||
if (!info.isSystem) {
|
||||
throw new Error('Forbidden')
|
||||
}
|
||||
const { fromDate, toDate, count, fromId, toId, card, blobId } = event.group
|
||||
await this.db.createMessagesGroup(card, blobId, fromDate, toDate, fromId, toId, count)
|
||||
|
||||
return {
|
||||
responseEvent: undefined,
|
||||
result: {}
|
||||
}
|
||||
}
|
||||
|
||||
async removeMessagesGroup(event: RemoveMessagesGroupEvent, info: ConnectionInfo): Promise<Result> {
|
||||
if (!info.isSystem) {
|
||||
throw new Error('Forbidden')
|
||||
}
|
||||
await this.db.removeMessagesGroup(event.card, event.blobId)
|
||||
|
||||
return {
|
||||
responseEvent: undefined,
|
||||
result: {}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async createThread(event: CreateThreadEvent, _: ConnectionInfo): Promise<Result> {
|
||||
const date = new Date()
|
||||
await this.db.createThread(event.card, event.message, event.thread, date)
|
||||
const responseEvent: ThreadCreatedEvent = {
|
||||
type: ResponseEventType.ThreadCreated,
|
||||
thread: {
|
||||
card: event.card,
|
||||
thread: event.thread,
|
||||
message: event.message,
|
||||
repliesCount: 0,
|
||||
lastReply: date
|
||||
}
|
||||
}
|
||||
return {
|
||||
responseEvent,
|
||||
result: {}
|
||||
}
|
||||
}
|
||||
|
||||
private checkCreator(info: ConnectionInfo, creator: SocialID): void {
|
||||
if (!info.socialIds.includes(creator) && !info.isSystem) {
|
||||
throw new Error('Forbidden')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
ServerApi
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
|
||||
import { Manager, type BroadcastSessionsFunc } from './manager.ts'
|
||||
import { Manager, type BroadcastSessionsFunc } from './manager'
|
||||
|
||||
export class Api implements ServerApi {
|
||||
private readonly manager: Manager
|
||||
@@ -26,7 +26,7 @@ export class Api implements ServerApi {
|
||||
private readonly db: DbAdapter,
|
||||
private readonly broadcast: BroadcastSessionsFunc
|
||||
) {
|
||||
this.manager = new Manager(this.ctx, db, this.workspace, broadcast)
|
||||
this.manager = new Manager(this.ctx, this.db, this.workspace, this.broadcast)
|
||||
}
|
||||
|
||||
static async create(
|
||||
@@ -35,7 +35,7 @@ export class Api implements ServerApi {
|
||||
dbUrl: string,
|
||||
broadcast: BroadcastSessionsFunc
|
||||
): Promise<Api> {
|
||||
const db = await createDbAdapter(dbUrl, workspace)
|
||||
const db = await createDbAdapter(dbUrl, workspace, ctx, { withLogs: true })
|
||||
return new Api(ctx, workspace, db, broadcast)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,17 +18,17 @@ import type {
|
||||
MessagesGroup,
|
||||
WorkspaceID
|
||||
} from '@hcengineering/communication-types'
|
||||
|
||||
import { Triggers } from './triggers.ts'
|
||||
import { EventProcessor } from './eventProcessor.ts'
|
||||
import type { MeasureContext } from '@hcengineering/core'
|
||||
|
||||
import { Triggers } from './triggers'
|
||||
import { EventProcessor } from './eventProcessor'
|
||||
|
||||
type QueryId = number | string
|
||||
type QueryType = 'message' | 'notification' | 'context'
|
||||
|
||||
export type BroadcastSessionsFunc = (ctx: MeasureContext, sessionIds: string[], result: any) => void
|
||||
|
||||
type SessionInfo = {
|
||||
interface SessionInfo {
|
||||
personalWorkspace: string
|
||||
messageQueries: Map<QueryId, FindMessagesParams>
|
||||
notificationQueries: Map<QueryId, FindNotificationsParams>
|
||||
@@ -36,8 +36,8 @@ type SessionInfo = {
|
||||
}
|
||||
|
||||
export class Manager {
|
||||
private dataBySessionId: Map<string, SessionInfo> = new Map()
|
||||
private triggers: Triggers
|
||||
private readonly dataBySessionId = new Map<string, SessionInfo>()
|
||||
private readonly triggers: Triggers
|
||||
private readonly eventProcessor: EventProcessor
|
||||
|
||||
constructor(
|
||||
@@ -58,17 +58,16 @@ export class Manager {
|
||||
return result
|
||||
}
|
||||
|
||||
async findMessagesGroups(info: ConnectionInfo, params: FindMessagesGroupsParams): Promise<MessagesGroup[]> {
|
||||
async findMessagesGroups(_: ConnectionInfo, params: FindMessagesGroupsParams): Promise<MessagesGroup[]> {
|
||||
return await this.db.findMessagesGroups(params)
|
||||
}
|
||||
|
||||
async event(info: ConnectionInfo, event: RequestEvent): Promise<EventResult> {
|
||||
const { result, responseEvent } = await this.eventProcessor.process(
|
||||
{ personalWorkspace: info.personalWorkspace, socialIds: info.socialIds },
|
||||
event
|
||||
)
|
||||
const eventResult = await this.eventProcessor.process(info, event)
|
||||
|
||||
const { result, responseEvent } = eventResult
|
||||
if (responseEvent !== undefined) {
|
||||
void this.next(responseEvent)
|
||||
void this.next(info, responseEvent)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -86,7 +85,7 @@ export class Manager {
|
||||
}
|
||||
|
||||
if (type === 'message') {
|
||||
data.messageQueries.set(queryId, params)
|
||||
data.messageQueries.set(queryId, params as FindMessagesParams)
|
||||
} else if (type === 'notification') {
|
||||
data.notificationQueries.set(queryId, params)
|
||||
} else if (type === 'context') {
|
||||
@@ -107,10 +106,10 @@ export class Manager {
|
||||
this.dataBySessionId.delete(sessionId)
|
||||
}
|
||||
|
||||
async next(event: ResponseEvent): Promise<void> {
|
||||
async next(info: ConnectionInfo, event: ResponseEvent): Promise<void> {
|
||||
await this.responseEvent(event)
|
||||
const derived = await this.triggers.process(event)
|
||||
await Promise.all(derived.map((it) => this.next(it)))
|
||||
const derived = await this.triggers.process(event, info)
|
||||
await Promise.all(derived.map((it) => this.next(info, it)))
|
||||
}
|
||||
|
||||
private async responseEvent(event: ResponseEvent): Promise<void> {
|
||||
@@ -143,8 +142,6 @@ export class Manager {
|
||||
{ card: event.card, id: event.message },
|
||||
Array.from(info.messageQueries.values())
|
||||
)
|
||||
case ResponseEventType.MessagesRemoved:
|
||||
return this.matchMessagesQuery({ card: event.card }, Array.from(info.messageQueries.values()))
|
||||
case ResponseEventType.ReactionCreated:
|
||||
return this.matchMessagesQuery(
|
||||
{ card: event.card, id: event.reaction.message },
|
||||
@@ -165,6 +162,11 @@ export class Manager {
|
||||
{ card: event.card, id: event.message },
|
||||
Array.from(info.messageQueries.values())
|
||||
)
|
||||
case ResponseEventType.ThreadCreated:
|
||||
return this.matchMessagesQuery(
|
||||
{ card: event.thread.card, id: event.thread.message },
|
||||
Array.from(info.messageQueries.values())
|
||||
)
|
||||
case ResponseEventType.NotificationCreated:
|
||||
return (
|
||||
info.personalWorkspace === event.personalWorkspace &&
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
ResponseEventType,
|
||||
type DbAdapter,
|
||||
type MessageCreatedEvent,
|
||||
type NotificationContextCreatedEvent,
|
||||
type NotificationCreatedEvent,
|
||||
type ResponseEvent
|
||||
type DbAdapter,
|
||||
type ResponseEvent,
|
||||
ResponseEventType,
|
||||
type MessageRemovedEvent,
|
||||
type ConnectionInfo
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
import type { NotificationContext, ContextID, CardID, WorkspaceID } from '@hcengineering/communication-types'
|
||||
import { type WorkspaceID, PatchType, type Patch } from '@hcengineering/communication-types'
|
||||
|
||||
export class Triggers {
|
||||
constructor(
|
||||
@@ -14,103 +14,65 @@ export class Triggers {
|
||||
private readonly workspace: WorkspaceID
|
||||
) {}
|
||||
|
||||
async process(event: ResponseEvent): Promise<ResponseEvent[]> {
|
||||
async process(event: ResponseEvent, info: ConnectionInfo): Promise<ResponseEvent[]> {
|
||||
switch (event.type) {
|
||||
case ResponseEventType.MessageCreated:
|
||||
return this.createNotifications(event)
|
||||
return await this.onMessageCreated(event)
|
||||
case ResponseEventType.MessageRemoved:
|
||||
return await this.onMessageRemoved(event, info)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
private async createNotifications(event: MessageCreatedEvent): Promise<ResponseEvent[]> {
|
||||
const card = event.message.card as any as CardID
|
||||
const subscribedPersonalWorkspaces = [
|
||||
'cd0aba36-1c4f-4170-95f2-27a12a5415f7',
|
||||
'cd0aba36-1c4f-4170-95f2-27a12a5415f8'
|
||||
] as WorkspaceID[]
|
||||
async onMessageRemoved(event: MessageRemovedEvent, info: ConnectionInfo): Promise<ResponseEvent[]> {
|
||||
const { card } = event
|
||||
const thread = await this.db.findThread(card)
|
||||
if (thread === undefined) return []
|
||||
|
||||
const res: ResponseEvent[] = []
|
||||
const contexts = await this.db.findContexts({ card }, [], this.workspace)
|
||||
const date = new Date()
|
||||
const socialId = info.socialIds[0]
|
||||
|
||||
res.push(...(await this.updateNotificationContexts(event.message.created, contexts)))
|
||||
|
||||
for (const personalWorkspace of subscribedPersonalWorkspaces) {
|
||||
const existsContext = contexts.find(
|
||||
(it) => it.card === card && it.personalWorkspace === personalWorkspace && this.workspace === it.workspace
|
||||
)
|
||||
const contextId = await this.getOrCreateContextId(
|
||||
personalWorkspace,
|
||||
card,
|
||||
res,
|
||||
event.message.created,
|
||||
existsContext
|
||||
)
|
||||
|
||||
await this.db.createNotification(event.message.id, contextId)
|
||||
|
||||
const resultEvent: NotificationCreatedEvent = {
|
||||
type: ResponseEventType.NotificationCreated,
|
||||
personalWorkspace,
|
||||
notification: {
|
||||
context: contextId,
|
||||
message: event.message,
|
||||
read: false,
|
||||
archived: false
|
||||
}
|
||||
}
|
||||
res.push(resultEvent)
|
||||
const patch: Patch = {
|
||||
message: thread.message,
|
||||
type: PatchType.removeReply,
|
||||
content: thread.thread,
|
||||
creator: socialId,
|
||||
created: date
|
||||
}
|
||||
await this.db.updateThread(thread.thread, date, 'decrement')
|
||||
await this.db.createPatch(thread.card, patch.message, patch.type, patch.content, patch.creator, patch.created)
|
||||
|
||||
return res
|
||||
return [
|
||||
{
|
||||
type: ResponseEventType.PatchCreated,
|
||||
card: thread.card,
|
||||
patch
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
private async getOrCreateContextId(
|
||||
personalWorkspace: WorkspaceID,
|
||||
card: CardID,
|
||||
res: ResponseEvent[],
|
||||
lastUpdate: Date,
|
||||
context?: NotificationContext
|
||||
): Promise<ContextID> {
|
||||
if (context !== undefined) {
|
||||
return context.id
|
||||
} else {
|
||||
const contextId = await this.db.createContext(personalWorkspace, card, undefined, lastUpdate)
|
||||
const newContext = {
|
||||
id: contextId,
|
||||
card,
|
||||
workspace: this.workspace,
|
||||
personalWorkspace
|
||||
}
|
||||
const resultEvent: NotificationContextCreatedEvent = {
|
||||
type: ResponseEventType.NotificationContextCreated,
|
||||
context: newContext
|
||||
}
|
||||
async onMessageCreated(event: MessageCreatedEvent): Promise<ResponseEvent[]> {
|
||||
const { message } = event
|
||||
const thread = await this.db.findThread(message.card)
|
||||
if (thread === undefined) return []
|
||||
|
||||
res.push(resultEvent)
|
||||
|
||||
return contextId
|
||||
const date = new Date()
|
||||
const patch: Patch = {
|
||||
message: thread.message,
|
||||
type: PatchType.addReply,
|
||||
content: thread.thread,
|
||||
creator: message.creator,
|
||||
created: date
|
||||
}
|
||||
}
|
||||
await this.db.updateThread(thread.thread, date, 'increment')
|
||||
await this.db.createPatch(thread.card, patch.message, patch.type, patch.content, patch.creator, patch.created)
|
||||
|
||||
private async updateNotificationContexts(
|
||||
lastUpdate: Date,
|
||||
contexts: NotificationContext[]
|
||||
): Promise<ResponseEvent[]> {
|
||||
const res: ResponseEvent[] = []
|
||||
for (const context of contexts) {
|
||||
if (context.lastUpdate === undefined || context.lastUpdate < lastUpdate) {
|
||||
await this.db.updateContext(context.id, { lastUpdate })
|
||||
res.push({
|
||||
type: ResponseEventType.NotificationContextUpdated,
|
||||
personalWorkspace: context.personalWorkspace,
|
||||
context: context.id,
|
||||
update: {
|
||||
lastUpdate
|
||||
}
|
||||
})
|
||||
return [
|
||||
{
|
||||
type: ResponseEventType.PatchCreated,
|
||||
card: thread.card,
|
||||
patch
|
||||
}
|
||||
}
|
||||
return res
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/communication-core",
|
||||
"version": "0.1.9",
|
||||
"name": "@hcengineering/communication-shared",
|
||||
"version": "0.1.47",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
@@ -10,13 +10,15 @@
|
||||
"types/**/*.d.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.1.14"
|
||||
"@types/bun": "^1.1.14",
|
||||
"@types/js-yaml": "^4.0.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/communication-types": "workspace:*"
|
||||
"@hcengineering/communication-types": "workspace:*",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -0,0 +1,58 @@
|
||||
import type {
|
||||
FileMessage,
|
||||
FileMetadata,
|
||||
MessagesGroup,
|
||||
ParsedFile,
|
||||
WorkspaceID
|
||||
} from '@hcengineering/communication-types'
|
||||
import yaml from 'js-yaml'
|
||||
|
||||
import { retry, type RetryOptions } from './retry'
|
||||
|
||||
export async function loadGroupFile(
|
||||
workspace: WorkspaceID,
|
||||
filesUrl: string,
|
||||
group: MessagesGroup,
|
||||
options: RetryOptions
|
||||
): Promise<ParsedFile> {
|
||||
const url = getFileUrl(workspace, filesUrl, group.blobId)
|
||||
|
||||
const file = await retry(() => fetchFile(url), options)
|
||||
const [metadata, messages] = yaml.loadAll(file) as [FileMetadata, FileMessage[]]
|
||||
|
||||
return {
|
||||
metadata,
|
||||
messages: messages.map((message) => ({
|
||||
id: message.id,
|
||||
card: metadata.card,
|
||||
content: message.content,
|
||||
edited: message.edited,
|
||||
creator: message.creator,
|
||||
created: message.created,
|
||||
attachments: [],
|
||||
reactions: message.reactions.map((reaction) => ({
|
||||
message: message.id,
|
||||
reaction: reaction.reaction,
|
||||
creator: reaction.creator,
|
||||
created: reaction.created
|
||||
}))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchFile(url: string): Promise<string> {
|
||||
const res = await fetch(url)
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch file: ${res.statusText}`)
|
||||
}
|
||||
|
||||
return await res.text()
|
||||
}
|
||||
|
||||
function getFileUrl(workspace: WorkspaceID, urlTemplate: string, file: string): string {
|
||||
return urlTemplate
|
||||
.replaceAll(':filename', encodeURIComponent(file))
|
||||
.replaceAll(':workspace', encodeURIComponent(workspace))
|
||||
.replaceAll(':blobId', encodeURIComponent(file))
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './message.ts'
|
||||
export * from './retry.ts'
|
||||
export * from './files.ts'
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { MessageID } from '@hcengineering/communication-types'
|
||||
|
||||
let lastTimestamp = 0
|
||||
let counter = 0n
|
||||
|
||||
export function generateMessageId(): MessageID {
|
||||
const timestamp = Math.floor(Date.now() / 1000)
|
||||
|
||||
if (timestamp !== lastTimestamp) {
|
||||
lastTimestamp = timestamp
|
||||
counter = 0n
|
||||
}
|
||||
|
||||
const id = (BigInt(timestamp) << 20n) | counter
|
||||
counter++
|
||||
|
||||
return id.toString() as MessageID
|
||||
}
|
||||
|
||||
export function parseMessageId(messageId: MessageID): Date {
|
||||
const timestamp = Number(BigInt(messageId) >> 20n)
|
||||
|
||||
return new Date(timestamp * 1000)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export interface RetryOptions {
|
||||
retries: number
|
||||
delay?: number
|
||||
}
|
||||
|
||||
export async function retry<T>(op: () => Promise<T>, { retries, delay }: RetryOptions): Promise<T> {
|
||||
let error: any
|
||||
while (retries > 0) {
|
||||
retries--
|
||||
try {
|
||||
return await op()
|
||||
} catch (err: any) {
|
||||
error = err
|
||||
if (retries !== 0 && delay !== undefined && delay > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, delay))
|
||||
}
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/communication-sqlite-wasm",
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.47",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
|
||||
@@ -73,8 +73,6 @@ export class SqliteAdapter {
|
||||
workspace: string,
|
||||
card: CardID,
|
||||
blobId: BlobID,
|
||||
from_id: MessageID,
|
||||
to_id: MessageID,
|
||||
from_date: Date,
|
||||
to_date: Date,
|
||||
count: number
|
||||
|
||||
@@ -3,10 +3,10 @@ import {
|
||||
type MessageID,
|
||||
type CardID,
|
||||
type FindMessagesParams,
|
||||
SortOrder,
|
||||
SortingOrder,
|
||||
type SocialID,
|
||||
type RichText,
|
||||
Direction, type Reaction, type Attachment
|
||||
type Reaction, type Attachment
|
||||
} from '@hcengineering/communication-types'
|
||||
|
||||
import {BaseDb} from './base.ts'
|
||||
@@ -124,7 +124,7 @@ export class MessagesDb extends BaseDb {
|
||||
|
||||
const where = this.buildMessageWhere(workspace, params)
|
||||
const groupBy = `GROUP BY m.id`
|
||||
const orderBy = params.sort ? `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` : ''
|
||||
const orderBy = params.order ? `ORDER BY m.created ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` : ''
|
||||
const limit = params.limit ? ` LIMIT ${params.limit}` : ''
|
||||
const sql = [select, where, groupBy, orderBy, limit].join(' ')
|
||||
|
||||
@@ -143,19 +143,20 @@ export class MessagesDb extends BaseDb {
|
||||
where.push(`m.id = '${params.id}'`)
|
||||
}
|
||||
|
||||
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()} ${params.from}`)
|
||||
}
|
||||
//TODO: FIX ME
|
||||
// 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()} ${params.from}`)
|
||||
// }
|
||||
|
||||
return `WHERE ${where.join(' AND ')}`
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
type ContextID,
|
||||
type CardID,
|
||||
type NotificationContext,
|
||||
type FindNotificationContextParams, SortOrder,
|
||||
type FindNotificationContextParams, SortingOrder,
|
||||
type FindNotificationsParams, type Notification,
|
||||
type NotificationContextUpdate
|
||||
} from '@hcengineering/communication-types'
|
||||
@@ -130,7 +130,7 @@ export class NotificationsDb extends BaseDb {
|
||||
`;
|
||||
const where = this.buildNotificationWhere(params, personalWorkspace, workspace)
|
||||
const groupBy = `GROUP BY n.message_id, n.context_id, m.id, nc.card_id, nc.archived_from, nc.last_view, nc.last_update`;
|
||||
const orderBy = `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}`
|
||||
const orderBy = `ORDER BY m.created ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}`
|
||||
const limit = params.limit ? ` LIMIT ${params.limit}` : ''
|
||||
const sql = [select, where, groupBy, orderBy, limit].join(' ')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/communication-types",
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.47",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { CardID, Message, MessageID, RichText, SocialID } from './message'
|
||||
|
||||
export interface FileMetadata {
|
||||
card: CardID
|
||||
title: string
|
||||
fromDate: Date
|
||||
toDate: Date
|
||||
}
|
||||
|
||||
export interface FileMessage {
|
||||
id: MessageID
|
||||
content: RichText
|
||||
edited?: Date
|
||||
creator: SocialID
|
||||
created: Date
|
||||
reactions: FileReaction[]
|
||||
thread?: FileThread
|
||||
}
|
||||
|
||||
export interface FileReaction {
|
||||
reaction: string
|
||||
creator: SocialID
|
||||
created: Date
|
||||
}
|
||||
|
||||
export interface FileThread {
|
||||
thread: CardID
|
||||
repliesCount: number
|
||||
lastReply: Date
|
||||
replied: SocialID[]
|
||||
}
|
||||
|
||||
export interface ParsedFile {
|
||||
metadata: FileMetadata
|
||||
messages: Message[]
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './message.ts'
|
||||
export * from './notification.ts'
|
||||
export * from './query.ts'
|
||||
export * from './file.ts'
|
||||
|
||||
@@ -7,19 +7,18 @@ export type SocialID = PersonId
|
||||
export type WorkspaceID = WorkspaceUuid
|
||||
export type RichText = string
|
||||
|
||||
export type ID = string | number
|
||||
export type MessageID = number & { message: true }
|
||||
export type ID = string
|
||||
export type MessageID = string & { message: true }
|
||||
|
||||
interface Object {
|
||||
creator: SocialID
|
||||
created: Date
|
||||
}
|
||||
|
||||
export interface Message extends Object {
|
||||
export interface Message {
|
||||
id: MessageID
|
||||
card: CardID
|
||||
content: RichText
|
||||
creator: SocialID
|
||||
created: Date
|
||||
|
||||
edited?: Date
|
||||
thread?: Thread
|
||||
reactions: Reaction[]
|
||||
attachments: Attachment[]
|
||||
}
|
||||
@@ -32,19 +31,43 @@ export interface MessagesGroup {
|
||||
fromDate: Date
|
||||
toDate: Date
|
||||
count: number
|
||||
patches: Patch[]
|
||||
}
|
||||
|
||||
export interface Patch extends Object {
|
||||
export interface Patch {
|
||||
message: MessageID
|
||||
content: RichText
|
||||
type: PatchType
|
||||
content: string
|
||||
creator: SocialID
|
||||
created: Date
|
||||
}
|
||||
|
||||
export interface Reaction extends Object {
|
||||
export enum PatchType {
|
||||
update = 'update',
|
||||
addReaction = 'addReaction',
|
||||
removeReaction = 'removeReaction',
|
||||
addReply = 'addReply',
|
||||
removeReply = 'removeReply'
|
||||
}
|
||||
|
||||
export interface Reaction {
|
||||
message: MessageID
|
||||
reaction: string
|
||||
creator: SocialID
|
||||
created: Date
|
||||
}
|
||||
|
||||
export interface Attachment extends Object {
|
||||
export interface Attachment {
|
||||
message: MessageID
|
||||
card: CardID
|
||||
creator: SocialID
|
||||
created: Date
|
||||
}
|
||||
|
||||
export interface Thread {
|
||||
card: CardID
|
||||
message: MessageID
|
||||
thread: CardID
|
||||
repliesCount: number
|
||||
lastReply: Date
|
||||
}
|
||||
|
||||
+16
-27
@@ -1,15 +1,15 @@
|
||||
import { SortingOrder } from '@hcengineering/core'
|
||||
|
||||
import type { BlobID, CardID, MessageID } from './message'
|
||||
import type { ContextID } from './notification'
|
||||
|
||||
export enum SortOrder {
|
||||
Asc = 1,
|
||||
Desc = -1
|
||||
}
|
||||
export { SortingOrder }
|
||||
|
||||
export enum Direction {
|
||||
Backward = 1,
|
||||
Forward = -1
|
||||
}
|
||||
export type ComparisonOperator = 'less' | 'lessOrEqual' | 'greater' | 'greaterOrEqual' | 'notEqual'
|
||||
|
||||
type Exclusive<T> = {
|
||||
[K in keyof T]: Record<K, T[K]> & Partial<Record<Exclude<keyof T, K>, never>>
|
||||
}[keyof T]
|
||||
|
||||
export interface Window<T> {
|
||||
getResult(): T[]
|
||||
@@ -22,16 +22,14 @@ export interface Window<T> {
|
||||
}
|
||||
|
||||
interface FindParams {
|
||||
from?: Date
|
||||
excluded?: boolean
|
||||
direction?: Direction
|
||||
sort?: SortOrder
|
||||
order?: SortingOrder
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface FindMessagesParams extends FindParams {
|
||||
id?: MessageID
|
||||
card?: CardID
|
||||
card: CardID
|
||||
created?: Exclusive<Record<ComparisonOperator, Date>> | Date
|
||||
}
|
||||
|
||||
export interface FindNotificationsParams extends FindParams {
|
||||
@@ -39,6 +37,7 @@ export interface FindNotificationsParams extends FindParams {
|
||||
message?: MessageID
|
||||
read?: boolean
|
||||
archived?: boolean
|
||||
created?: Exclusive<Record<ComparisonOperator, Date>> | Date
|
||||
}
|
||||
|
||||
export interface FindNotificationContextParams extends FindParams {
|
||||
@@ -46,20 +45,10 @@ export interface FindNotificationContextParams extends FindParams {
|
||||
card?: CardID
|
||||
}
|
||||
|
||||
export type ComparisonOperator = 'less' | 'lessOrEqual' | 'greater' | 'greaterOrEqual' | 'notEqual'
|
||||
|
||||
type Exclusive<T> = {
|
||||
[K in keyof T]: Record<K, T[K]> & Partial<Record<Exclude<keyof T, K>, never>>
|
||||
}[keyof T]
|
||||
|
||||
export interface FindMessagesGroupsParams {
|
||||
export interface FindMessagesGroupsParams extends FindParams {
|
||||
card?: CardID
|
||||
blobId?: BlobID
|
||||
fromId?: Exclusive<Record<ComparisonOperator, MessageID>> | MessageID
|
||||
toId?: Exclusive<Record<ComparisonOperator, MessageID>> | MessageID
|
||||
fromDate?: Exclusive<Record<ComparisonOperator, Date>> | Date
|
||||
toDate?: Exclusive<Record<ComparisonOperator, Date>> | Date
|
||||
limit?: number
|
||||
sortBy?: 'fromId' | 'toId' | 'fromDate' | 'toDate'
|
||||
sort?: SortOrder
|
||||
fromDate?: Partial<Record<ComparisonOperator, Date>> | Date
|
||||
toDate?: Partial<Record<ComparisonOperator, Date>> | Date
|
||||
orderBy?: 'fromDate' | 'toDate'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user