UBERF-6870: Speedup server broadcast of derived transactions (#5553)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-05-09 16:06:28 +07:00 committed by GitHub
parent 356dcc005a
commit 747bf3c8ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
75 changed files with 551 additions and 2113 deletions

View File

@ -128,18 +128,6 @@ dependencies:
'@rush-temp/core':
specifier: file:./projects/core.tgz
version: file:projects/core.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/dev-account':
specifier: file:./projects/dev-account.tgz
version: file:projects/dev-account.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/dev-client-resources':
specifier: file:./projects/dev-client-resources.tgz
version: file:projects/dev-client-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/dev-server':
specifier: file:./projects/dev-server.tgz
version: file:projects/dev-server.tgz(@types/node@20.11.19)(esbuild@0.20.1)
'@rush-temp/dev-storage':
specifier: file:./projects/dev-storage.tgz
version: file:projects/dev-storage.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/devmodel':
specifier: file:./projects/devmodel.tgz
version: file:projects/devmodel.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
@ -18326,133 +18314,6 @@ packages:
- ts-node
dev: false
file:projects/dev-account.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-LWWFUvgYWEbSKEbgl0DW088ikIIbxd94B+J5g5qGsBZUnVs2EfYKIOHZ6IkY3RcaqnLEYlwml6JHS2BuaPWh+A==, tarball: file:projects/dev-account.tgz}
id: file:projects/dev-account.tgz
name: '@rush-temp/dev-account'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
simplytyped: 3.3.0(typescript@5.3.3)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/dev-client-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-cGChKmk9nSzZ32ne0g+PFZ2wCnZQN2UYQ/6Sf475WKMOU5n6vFcnTWjgPoDjNhGDN9Eqb/N42dGE/9FrFm5ypg==, tarball: file:projects/dev-client-resources.tgz}
id: file:projects/dev-client-resources.tgz
name: '@rush-temp/dev-client-resources'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/dev-server.tgz(@types/node@20.11.19)(esbuild@0.20.1):
resolution: {integrity: sha512-ejFT8uh0XsPvXdz1nxvESgUii09Eo5kTjqSiR5tnm7Xijo9nUAS8NCELlQ5CXdgh0ho2DUZWsLBAKx158E+YUQ==, tarball: file:projects/dev-server.tgz}
id: file:projects/dev-server.tgz
name: '@rush-temp/dev-server'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
jwt-simple: 0.5.6
prettier: 3.2.5
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
ts-node: 10.9.2(@types/node@20.11.19)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@swc/core'
- '@swc/wasm'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
dev: false
file:projects/dev-storage.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-cgVla92iBIDPB+2c/z+YMbTFaONzy46pEas/QIYS8j6pNBGEIm8JjXwVy/ThSW+6wpi9vmwlUWOQe1ILsUdNXA==, tarball: file:projects/dev-storage.tgz}
id: file:projects/dev-storage.tgz
name: '@rush-temp/dev-storage'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/devmodel-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-NDdNczJE2bG7cuAqbD38xHMKXFndVeF3z0tvfBKVs6sVWauXThe448YkAMVY6NxEIczGqAqmyPHK0IHbuvEqgg==, tarball: file:projects/devmodel-resources.tgz}
id: file:projects/devmodel-resources.tgz
@ -21282,7 +21143,7 @@ packages:
dev: false
file:projects/prod.tgz(bufferutil@4.0.8)(sass@1.71.1)(ts-node@10.9.2)(utf-8-validate@6.0.3):
resolution: {integrity: sha512-/CKLzPjaTfkDNjT0evklXES1ISDy11ikLCJKFuyI4OQ6nfbt84wTqkO80Egr9IZScuNA0M0MEeJP+01pO2LNdA==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-ApNm3LLGsu8aU/ySQbpHVWR+td2eUUeCzgjnkwdw83E4QZ9mFj8I9hTN0z4gC32PAOFdxRt6P0J8vA78WBNfGw==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@ -24360,7 +24221,7 @@ packages:
dev: false
file:projects/tool.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.3):
resolution: {integrity: sha512-aCu871KUBezC10ng0g9MwVF4UdeqlKVlMvLH4r0eBFvZx/XkEPiP++bL5dDuYo9o4cU2bhj+7uTzLCePUQVDZQ==, tarball: file:projects/tool.tgz}
resolution: {integrity: sha512-mudcpqzHDkcN8NfVsWXUj+rtgp9QfGvIsmYSFEjvU9yzw+WIZJ3eqnU72/Vbhn1WgtMEKLweb3oqEtS9tmGAog==, tarball: file:projects/tool.tgz}
id: file:projects/tool.tgz
name: '@rush-temp/tool'
version: 0.0.0

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -1,94 +0,0 @@
{
"name": "@hcengineering/dev-account",
"entries": [
{
"version": "0.6.7",
"tag": "@hcengineering/dev-account_v0.6.7",
"date": "Mon, 09 Aug 2021 08:00:44 GMT",
"comments": {
"patch": [
{
"comment": "fix"
}
]
}
},
{
"version": "0.6.6",
"tag": "@hcengineering/dev-account_v0.6.6",
"date": "Sun, 08 Aug 2021 12:00:07 GMT",
"comments": {
"patch": [
{
"comment": "Change"
}
]
}
},
{
"version": "0.6.5",
"tag": "@hcengineering/dev-account_v0.6.5",
"date": "Sun, 08 Aug 2021 11:43:36 GMT",
"comments": {
"patch": [
{
"comment": "Update"
}
]
}
},
{
"version": "0.6.4",
"tag": "@hcengineering/dev-account_v0.6.4",
"date": "Sun, 08 Aug 2021 11:38:34 GMT",
"comments": {
"patch": [
{
"comment": "Update"
}
]
}
},
{
"version": "0.6.3",
"tag": "@hcengineering/dev-account_v0.6.3",
"date": "Sun, 08 Aug 2021 10:21:31 GMT",
"comments": {
"patch": [
{
"comment": "Lint"
}
]
}
},
{
"version": "0.6.2",
"tag": "@hcengineering/dev-account_v0.6.2",
"date": "Sun, 08 Aug 2021 10:14:57 GMT",
"comments": {
"patch": [
{
"comment": "Initial"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/platform\" from `~0.6.0` to `~0.6.4`"
}
]
}
},
{
"version": "0.6.1",
"tag": "@hcengineering/dev-account_v0.6.1",
"date": "Sun, 08 Aug 2021 09:33:36 GMT",
"comments": {
"patch": [
{
"comment": "Initial implementation"
}
]
}
}
]
}

View File

@ -1,53 +0,0 @@
# Change Log - @hcengineering/dev-account
This log was last generated on Mon, 09 Aug 2021 08:00:44 GMT and should not be manually modified.
## 0.6.7
Mon, 09 Aug 2021 08:00:44 GMT
### Patches
- fix
## 0.6.6
Sun, 08 Aug 2021 12:00:07 GMT
### Patches
- Change
## 0.6.5
Sun, 08 Aug 2021 11:43:36 GMT
### Patches
- Update
## 0.6.4
Sun, 08 Aug 2021 11:38:34 GMT
### Patches
- Update
## 0.6.3
Sun, 08 Aug 2021 10:21:31 GMT
### Patches
- Lint
## 0.6.2
Sun, 08 Aug 2021 10:14:57 GMT
### Patches
- Initial
## 0.6.1
Sun, 08 Aug 2021 09:33:36 GMT
### Patches
- Initial implementation

View File

@ -1,4 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -1,40 +0,0 @@
{
"name": "@hcengineering/dev-account",
"version": "0.6.7",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"simplytyped": "^3.3.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/platform": "^0.6.9",
"@hcengineering/core": "^0.6.28",
"@hcengineering/server-token": "^0.6.7"
}
}

View File

@ -1,45 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import platform, { Severity, Status } from '@hcengineering/platform'
import { getWorkspaceId } from '@hcengineering/core'
import { generateToken } from '@hcengineering/server-token'
function login (endpoint: string, email: string, password: string, workspace: string): any {
if (email !== 'rosamund@hc.engineering' && email !== 'elon@hc.engineering') {
return { error: new Status(Severity.ERROR, platform.status.Unauthorized, {}) }
}
if (password !== '1111') {
return { error: new Status(Severity.ERROR, platform.status.Unauthorized, {}) }
}
if (workspace !== 'ws1' && workspace !== 'ws2') {
return { error: new Status(Severity.ERROR, platform.status.Unauthorized, {}) }
}
const token = generateToken(email, getWorkspaceId(workspace))
return { result: { token, endpoint } }
}
export function handleRequest (req: any, serverEndpoint: string): any {
if (req.method === 'login') {
return login(serverEndpoint, req.params[0], req.params[1], req.params[2])
}
return { error: new Status(Severity.ERROR, platform.status.BadRequest, {}) }
}

View File

@ -1,32 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { handleRequest } from './account'
/**
* @public
*/
export function handle (req: string | null | undefined, serverEndpoint: string): { statusCode: number, body: string } {
if (req === null || req === undefined) return { statusCode: 401, body: 'unauthorized' }
const resp = handleRequest(JSON.parse(req), serverEndpoint)
if (resp.error !== undefined) {
return { statusCode: 401, body: '' }
}
return {
statusCode: 200,
body: JSON.stringify(resp)
}
}

View File

@ -1,10 +0,0 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -1,37 +0,0 @@
{
"name": "@hcengineering/dev-client-resources",
"entries": [
{
"version": "0.6.1",
"tag": "@hcengineering/dev-client-resources_v0.6.1",
"date": "Wed, 11 Aug 2021 09:37:04 GMT",
"comments": {
"patch": [
{
"comment": "Server support for workspaces"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.8` to `~0.6.10`"
},
{
"comment": "Updating dependency \"@hcengineering/dev-storage\" from `~0.6.3` to `~0.6.6`"
}
]
}
},
{
"version": "0.6.0",
"tag": "@hcengineering/dev-client-resources_v0.6.0",
"date": "Sun, 08 Aug 2021 10:14:57 GMT",
"comments": {
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/platform\" from `~0.6.3` to `~0.6.4`"
}
]
}
}
]
}

View File

@ -1,16 +0,0 @@
# Change Log - @hcengineering/dev-client-resources
This log was last generated on Wed, 11 Aug 2021 09:37:04 GMT and should not be manually modified.
## 0.6.1
Wed, 11 Aug 2021 09:37:04 GMT
### Patches
- Server support for workspaces
## 0.6.0
Sun, 08 Aug 2021 10:14:57 GMT
_Initial release_

View File

@ -1,4 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -1,44 +0,0 @@
{
"name": "@hcengineering/dev-client-resources",
"version": "0.6.1",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/platform": "^0.6.9",
"@hcengineering/core": "^0.6.28",
"@hcengineering/client": "^0.6.14",
"@hcengineering/dev-storage": "^0.6.6",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/model-all": "^0.6.0",
"@hcengineering/devmodel": "^0.6.0",
"@hcengineering/rpc": "^0.6.1"
}
}

View File

@ -1,169 +0,0 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import core, {
Account,
Class,
ClientConnection,
Doc,
DocChunk,
DocumentQuery,
Domain,
DOMAIN_TX,
FindOptions,
FindResult,
getWorkspaceId,
MeasureDoneOperation,
MeasureMetricsContext,
Ref,
SearchOptions,
SearchQuery,
SearchResult,
ServerStorage,
Timestamp,
Tx,
TxHandler,
TxResult
} from '@hcengineering/core'
import { createInMemoryTxAdapter } from '@hcengineering/dev-storage'
import devmodel from '@hcengineering/devmodel'
import { setMetadata } from '@hcengineering/platform'
import { protoDeserialize, protoSerialize } from '@hcengineering/rpc'
import {
ContentTextAdapter,
createInMemoryAdapter,
createServerStorage,
DbConfiguration,
DummyFullTextAdapter,
FullTextAdapter
} from '@hcengineering/server-core'
class ServerStorageWrapper implements ClientConnection {
measureCtx = new MeasureMetricsContext('client', {})
constructor (
private readonly storage: ServerStorage,
private readonly handler: TxHandler
) {}
findAll<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<FindResult<T>> {
const [c, q, o] = protoDeserialize(protoSerialize([_class, query, options], false), false)
return this.storage.findAll(this.measureCtx, c, q, o)
}
async searchFulltext (query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
return { docs: [] }
}
async loadModel (lastModelTx: Timestamp): Promise<Tx[]> {
return await this.storage.findAll(this.measureCtx, core.class.Tx, {
space: core.space.Model,
modifiedOn: { $gt: lastModelTx }
})
}
async getAccount (): Promise<Account> {
return (await this.storage.findAll(this.measureCtx, core.class.Account, {}))[0]
}
async tx (tx: Tx): Promise<TxResult> {
const _tx = protoDeserialize(protoSerialize(tx, false), false)
const [result, derived] = await this.storage.tx(this.measureCtx, _tx)
for (const tx of derived) {
this.handler(tx)
}
return result
}
async close (): Promise<void> {}
async loadChunk (domain: Domain, idx?: number): Promise<DocChunk> {
return { idx: -1, docs: [], finished: true }
}
async closeChunk (idx: number): Promise<void> {}
async loadDocs (domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
return []
}
async upload (domain: Domain, docs: Doc[]): Promise<void> {}
async clean (domain: Domain, docs: Ref<Doc>[]): Promise<void> {}
async measure (operationName: string): Promise<MeasureDoneOperation> {
return async () => ({ time: 0, serverTime: 0 })
}
async sendForceClose (): Promise<void> {}
}
async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
return new DummyFullTextAdapter()
}
async function createNullContentTextAdapter (): Promise<ContentTextAdapter> {
return {
async content (name: string, type: string, doc) {
return ''
},
metrics () {
return new MeasureMetricsContext('', {})
}
}
}
export async function connect (handler: (tx: Tx) => void): Promise<ClientConnection> {
const conf: DbConfiguration = {
domains: {
[DOMAIN_TX]: 'InMemoryTx'
},
defaultAdapter: 'InMemory',
adapters: {
InMemoryTx: {
factory: createInMemoryTxAdapter,
url: ''
},
InMemory: {
factory: createInMemoryAdapter,
url: ''
}
},
metrics: new MeasureMetricsContext('', {}),
fulltextAdapter: {
factory: createNullFullTextAdapter,
url: '',
stages: () => []
},
contentAdapters: {
default: {
factory: createNullContentTextAdapter,
contentType: '',
url: ''
}
},
serviceAdapters: {},
defaultContentAdapter: 'default',
workspace: { ...getWorkspaceId(''), workspaceUrl: '', workspaceName: '' }
}
const ctx = new MeasureMetricsContext('client', {})
const serverStorage = await createServerStorage(ctx, conf, {
upgrade: false
})
setMetadata(devmodel.metadata.DevModel, serverStorage)
return new ServerStorageWrapper(serverStorage, handler)
}

View File

@ -1,61 +0,0 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import clientPlugin from '@hcengineering/client'
import core, { AccountClient, createClient, groupByArray, MigrationState } from '@hcengineering/core'
import { migrateOperations } from '@hcengineering/model-all'
import { getMetadata, getResource } from '@hcengineering/platform'
import { connect } from './connection'
let client: AccountClient
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => {
return {
function: {
GetClient: async (): Promise<AccountClient> => {
if (client === undefined) {
client = await createClient(connect)
const states = await client.findAll<MigrationState>(core.class.MigrationState, {})
const migrateState = new Map(
Array.from(groupByArray(states, (it) => it.plugin).entries()).map((it) => [
it[0],
new Set(it[1].map((q) => q.state))
])
)
;(client as any).migrateState = migrateState
for (const op of migrateOperations) {
console.log('Migrate', op[0])
await op[1].upgrade(client as any, {
log (msg, data) {
console.log(msg, data)
},
error (msg, data) {
console.error(msg, data)
}
})
}
}
// Check if we had dev hook for client.
const hook = getMetadata(clientPlugin.metadata.ClientHook)
if (hook !== undefined) {
const hookProc = await getResource(hook)
client = await hookProc(client)
}
return client
}
}
}
}

View File

@ -1,10 +0,0 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}

View File

@ -1,51 +0,0 @@
version: '3.5'
services:
etcd:
container_name: milvus-etcd
image: quay.io/coreos/etcd:v3.5.0
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_SNAPSHOT_COUNT=50000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2022-03-17T06-34-49Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
ports:
- "9001:9001"
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
command: minio server /minio_data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
standalone:
container_name: milvus-standalone
image: milvusdb/milvus:v2.2.2
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- "etcd"
- "minio"
networks:
default:
name: milvus

View File

@ -58,7 +58,6 @@
"@hcengineering/login-assets": "^0.6.0",
"@hcengineering/login-resources": "^0.6.2",
"@hcengineering/client": "^0.6.14",
"@hcengineering/dev-client-resources": "^0.6.1",
"@hcengineering/workbench": "^0.6.9",
"@hcengineering/workbench-resources": "^0.6.1",
"@hcengineering/view": "^0.6.9",

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -1,184 +0,0 @@
{
"name": "@hcengineering/dev-server",
"entries": [
{
"version": "0.6.10",
"tag": "@hcengineering/dev-server_v0.6.10",
"date": "Sat, 14 Aug 2021 09:13:32 GMT",
"comments": {
"patch": [
{
"comment": "ping"
}
]
}
},
{
"version": "0.6.9",
"tag": "@hcengineering/dev-server_v0.6.9",
"date": "Wed, 11 Aug 2021 10:10:44 GMT",
"comments": {
"patch": [
{
"comment": "server ping"
}
]
}
},
{
"version": "0.6.8",
"tag": "@hcengineering/dev-server_v0.6.8",
"date": "Wed, 11 Aug 2021 09:37:04 GMT",
"comments": {
"patch": [
{
"comment": "Server support for workspaces"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/dev-storage\" from `~0.6.3` to `~0.6.6`"
},
{
"comment": "Updating dependency \"@hcengineering/server-ws\" from `~0.6.6` to `~0.6.8`"
},
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.8` to `~0.6.10`"
}
]
}
},
{
"version": "0.6.6",
"tag": "@hcengineering/dev-server_v0.6.6",
"date": "Sun, 08 Aug 2021 21:05:26 GMT",
"comments": {
"patch": [
{
"comment": "Fix server connection"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/server-ws\" from `~0.6.4` to `~0.6.5`"
},
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.7` to `~0.6.8`"
}
]
}
},
{
"version": "0.6.5",
"tag": "@hcengineering/dev-server_v0.6.5",
"date": "Wed, 04 Aug 2021 21:18:44 GMT",
"comments": {
"patch": [
{
"comment": "fix"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/dev-storage\" from `~0.6.2` to `~0.6.3`"
},
{
"comment": "Updating dependency \"@hcengineering/server-ws\" from `~0.6.3` to `~0.6.4`"
},
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.6` to `~0.6.7`"
},
{
"comment": "Updating dependency \"@hcengineering/platform\" from `~0.6.2` to `~0.6.3`"
}
]
}
},
{
"version": "0.6.4",
"tag": "@hcengineering/dev-server_v0.6.4",
"date": "Wed, 04 Aug 2021 21:00:14 GMT",
"comments": {
"patch": [
{
"comment": "npmignore"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/dev-storage\" from `~0.6.1` to `~0.6.2`"
},
{
"comment": "Updating dependency \"@hcengineering/server-ws\" from `~0.6.2` to `~0.6.3`"
},
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.5` to `~0.6.6`"
}
]
}
},
{
"version": "0.6.3",
"tag": "@hcengineering/dev-server_v0.6.3",
"date": "Wed, 04 Aug 2021 20:48:46 GMT",
"comments": {
"patch": [
{
"comment": "Logging"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/server-ws\" from `~0.6.1` to `~0.6.2`"
},
{
"comment": "Updating dependency \"@hcengineering/platform\" from `~0.6.1` to `~0.6.2`"
}
]
}
},
{
"version": "0.6.2",
"tag": "@hcengineering/dev-server_v0.6.2",
"date": "Wed, 04 Aug 2021 20:26:15 GMT",
"comments": {
"patch": [
{
"comment": "Export start function as defauilt"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.4` to `~0.6.5`"
}
]
}
},
{
"version": "0.6.1",
"tag": "@hcengineering/dev-server_v0.6.1",
"date": "Wed, 04 Aug 2021 17:38:30 GMT",
"comments": {
"patch": [
{
"comment": "Minor changes for publish"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/dev-storage\" from `~0.6.0` to `~0.6.1`"
},
{
"comment": "Updating dependency \"@hcengineering/server-ws\" from `~0.6.0` to `~0.6.1`"
},
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.0` to `~0.6.1`"
},
{
"comment": "Updating dependency \"@hcengineering/platform\" from `~0.6.0` to `~0.6.1`"
}
]
}
}
]
}

View File

@ -1,67 +0,0 @@
# Change Log - @hcengineering/dev-server
This log was last generated on Sat, 14 Aug 2021 09:13:32 GMT and should not be manually modified.
## 0.6.10
Sat, 14 Aug 2021 09:13:32 GMT
### Patches
- ping
## 0.6.9
Wed, 11 Aug 2021 10:10:44 GMT
### Patches
- server ping
## 0.6.8
Wed, 11 Aug 2021 09:37:04 GMT
### Patches
- Server support for workspaces
## 0.6.6
Sun, 08 Aug 2021 21:05:26 GMT
### Patches
- Fix server connection
## 0.6.5
Wed, 04 Aug 2021 21:18:44 GMT
### Patches
- fix
## 0.6.4
Wed, 04 Aug 2021 21:00:14 GMT
### Patches
- npmignore
## 0.6.3
Wed, 04 Aug 2021 20:48:46 GMT
### Patches
- Logging
## 0.6.2
Wed, 04 Aug 2021 20:26:15 GMT
### Patches
- Export start function as defauilt
## 0.6.1
Wed, 04 Aug 2021 17:38:30 GMT
### Patches
- Minor changes for publish

View File

@ -1,4 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -1,44 +0,0 @@
{
"name": "@hcengineering/dev-server",
"version": "0.6.10",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"start": "ts-node src/__start.ts",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"ts-node": "^10.8.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/dev-storage": "^0.6.6",
"@hcengineering/server-ws": "^0.6.11",
"@hcengineering/core": "^0.6.28",
"@hcengineering/platform": "^0.6.9",
"jwt-simple": "^0.5.6",
"@hcengineering/server-core": "^0.6.1"
}
}

View File

@ -1,25 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { start } from '.'
import { encode } from 'jwt-simple'
const token = encode({ email: 'rosamund@hc.engineering', workspace: 'trx40' }, 'secret')
console.log(token)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
start(3333)

View File

@ -1,17 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
export { start } from './server'

View File

@ -1,88 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { DOMAIN_TX, MeasureMetricsContext } from '@hcengineering/core'
import { createInMemoryTxAdapter } from '@hcengineering/dev-storage'
import {
ContentTextAdapter,
createInMemoryAdapter,
createPipeline,
DbConfiguration,
DummyFullTextAdapter,
FullTextAdapter
} from '@hcengineering/server-core'
import { ClientSession, startHttpServer, start as startJsonRpc } from '@hcengineering/server-ws'
async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
return new DummyFullTextAdapter()
}
async function createNullContentTextAdapter (): Promise<ContentTextAdapter> {
return {
async content (name: string, type: string, doc) {
return ''
},
metrics: () => new MeasureMetricsContext('', {})
}
}
/**
* @public
*/
export async function start (port: number, host?: string): Promise<void> {
const ctx = new MeasureMetricsContext('server', {})
startJsonRpc(ctx, {
pipelineFactory: (ctx, workspaceId) => {
const conf: DbConfiguration = {
domains: {
[DOMAIN_TX]: 'InMemoryTx'
},
defaultAdapter: 'InMemory',
adapters: {
InMemoryTx: {
factory: createInMemoryTxAdapter,
url: ''
},
InMemory: {
factory: createInMemoryAdapter,
url: ''
}
},
fulltextAdapter: {
factory: createNullFullTextAdapter,
url: '',
stages: () => []
},
metrics: new MeasureMetricsContext('', {}),
contentAdapters: {
default: {
factory: createNullContentTextAdapter,
contentType: '',
url: ''
}
},
serviceAdapters: {},
defaultContentAdapter: 'default',
workspace: workspaceId
}
return createPipeline(ctx, conf, [], false, () => {})
},
sessionFactory: (token, pipeline, broadcast) => new ClientSession(broadcast, token, pipeline),
port,
productId: '',
serverFactory: startHttpServer,
accountsUrl: ''
})
}

View File

@ -1,10 +0,0 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -1,79 +0,0 @@
{
"name": "@hcengineering/dev-storage",
"entries": [
{
"version": "0.6.6",
"tag": "@hcengineering/dev-storage_v0.6.6",
"date": "Wed, 11 Aug 2021 09:37:04 GMT",
"comments": {
"patch": [
{
"comment": "Server support for workspaces"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.8` to `~0.6.10`"
}
]
}
},
{
"version": "0.6.3",
"tag": "@hcengineering/dev-storage_v0.6.3",
"date": "Wed, 04 Aug 2021 21:18:44 GMT",
"comments": {
"patch": [
{
"comment": "fix"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.6` to `~0.6.7`"
},
{
"comment": "Updating dependency \"@hcengineering/platform\" from `~0.6.2` to `~0.6.3`"
}
]
}
},
{
"version": "0.6.2",
"tag": "@hcengineering/dev-storage_v0.6.2",
"date": "Wed, 04 Aug 2021 21:00:14 GMT",
"comments": {
"patch": [
{
"comment": "npmignore"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.5` to `~0.6.6`"
}
]
}
},
{
"version": "0.6.1",
"tag": "@hcengineering/dev-storage_v0.6.1",
"date": "Wed, 04 Aug 2021 17:38:30 GMT",
"comments": {
"patch": [
{
"comment": "Minor changes for publish"
}
],
"dependency": [
{
"comment": "Updating dependency \"@hcengineering/core\" from `~0.6.0` to `~0.6.1`"
},
{
"comment": "Updating dependency \"@hcengineering/platform\" from `~0.6.0` to `~0.6.1`"
}
]
}
}
]
}

View File

@ -1,32 +0,0 @@
# Change Log - @hcengineering/dev-storage
This log was last generated on Wed, 11 Aug 2021 09:37:04 GMT and should not be manually modified.
## 0.6.6
Wed, 11 Aug 2021 09:37:04 GMT
### Patches
- Server support for workspaces
## 0.6.3
Wed, 04 Aug 2021 21:18:44 GMT
### Patches
- fix
## 0.6.2
Wed, 04 Aug 2021 21:00:14 GMT
### Patches
- npmignore
## 0.6.1
Wed, 04 Aug 2021 17:38:30 GMT
### Patches
- Minor changes for publish

View File

@ -1,4 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -1,40 +0,0 @@
{
"name": "@hcengineering/dev-storage",
"version": "0.6.6",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/core": "^0.6.28",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/model-all": "^0.6.0"
}
}

View File

@ -1,40 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import core, { TxFactory } from '@hcengineering/core'
import { createStorage } from '../storage'
describe('client', () => {
it('should create storage', async () => {
const storage = await createStorage()
const txes = await storage.findAll(core.class.Tx, {})
expect(txes.length).toBe(64)
})
it('should create space', async () => {
const storage = await createStorage()
const factory = new TxFactory(core.account.System)
const tx = factory.createTxCreateDoc(core.class.Space, core.space.Model, {
name: 'xxx',
description: 'desc',
private: false,
members: []
})
await storage.tx(tx)
const txes = await storage.findAll(core.class.Space, { name: 'xxx' })
expect(txes.length).toBe(1)
})
})

View File

@ -1,17 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
export * from './storage'

View File

@ -1,72 +0,0 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type {
Class,
Doc,
DocumentQuery,
FindOptions,
FindResult,
MeasureContext,
Ref,
Tx,
TxResult,
WorkspaceId
} from '@hcengineering/core'
import { Hierarchy, TxDb } from '@hcengineering/core'
import builder from '@hcengineering/model-all'
import { DummyDbAdapter, TxAdapter } from '@hcengineering/server-core'
class InMemoryTxAdapter extends DummyDbAdapter implements TxAdapter {
private readonly txdb: TxDb
constructor (hierarchy: Hierarchy) {
super()
this.txdb = new TxDb(hierarchy)
}
async findAll<T extends Doc>(
ctx: MeasureContext,
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<FindResult<T>> {
return await this.txdb.findAll(_class, query, options)
}
async tx (ctx: MeasureContext, ...tx: Tx[]): Promise<TxResult[]> {
const r: TxResult[] = []
for (const t of tx) {
r.push(await this.txdb.tx(t))
}
return r
}
async getModel (): Promise<Tx[]> {
return builder().getTxes()
}
}
/**
* @public
*/
export async function createInMemoryTxAdapter (
ctx: MeasureContext,
hierarchy: Hierarchy,
url: string,
workspace: WorkspaceId
): Promise<TxAdapter> {
return new InMemoryTxAdapter(hierarchy)
}

View File

@ -1,10 +0,0 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}

View File

@ -61,7 +61,6 @@
"@hcengineering/client-resources": "^0.6.23",
"@hcengineering/contact": "^0.6.20",
"@hcengineering/core": "^0.6.28",
"@hcengineering/dev-storage": "^0.6.6",
"@hcengineering/elastic": "^0.6.0",
"@hcengineering/lead": "^0.6.0",
"@hcengineering/minio": "^0.6.0",

View File

@ -16,7 +16,7 @@
import { LoadModelResponse } from '.'
import type { Class, Doc, Domain, Ref, Timestamp } from './classes'
import { Hierarchy } from './hierarchy'
import { MeasureContext } from './measurements'
import { MeasureContext, type FullParamsType, type ParamsType } from './measurements'
import { ModelDb } from './memdb'
import type {
DocumentQuery,
@ -45,6 +45,22 @@ export interface StorageIterator {
close: (ctx: MeasureContext) => Promise<void>
}
export interface SessionOperationContext {
ctx: MeasureContext
// A parts of derived data to deal with after operation will be complete
derived: {
derived: Tx[]
target?: string[]
}[]
with: <T>(
name: string,
params: ParamsType,
op: (ctx: SessionOperationContext) => T | Promise<T>,
fullParams?: FullParamsType
) => Promise<T>
}
/**
* @public
*/
@ -78,8 +94,8 @@ export interface ServerStorage extends LowLevelStorage {
}
) => Promise<FindResult<T>>
searchFulltext: (ctx: MeasureContext, query: SearchQuery, options: SearchOptions) => Promise<SearchResult>
tx: (ctx: MeasureContext, tx: Tx) => Promise<[TxResult, Tx[]]>
apply: (ctx: MeasureContext, tx: Tx[], broadcast: boolean) => Promise<TxResult>
tx: (ctx: SessionOperationContext, tx: Tx) => Promise<TxResult>
apply: (ctx: SessionOperationContext, tx: Tx[], broadcast: boolean) => Promise<TxResult>
close: () => Promise<void>
loadModel: (last: Timestamp, hash?: string) => Promise<Tx[] | LoadModelResponse>
}

View File

@ -142,7 +142,6 @@ export interface TxApplyIf extends Tx {
export interface TxApplyResult {
success: boolean
derived: Tx[] // Some derived transactions to handle.
}
/**

View File

@ -36,7 +36,6 @@ import core, {
Timestamp,
Tx,
TxApplyIf,
TxApplyResult,
TxHandler,
TxResult,
TxWorkspaceEvent,
@ -442,8 +441,7 @@ class Connection implements ClientConnection {
params: [],
id: -1,
binary: useBinary,
compression: useCompression,
broadcast: true
compression: useCompression
}
this.websocket?.send(serialize(helloRequest, false))
}
@ -623,20 +621,7 @@ class Connection implements ClientConnection {
return (await this.findAll(core.class.Tx, { _id: (tx as TxApplyIf).txes[0]._id }, { limit: 1 })).length === 0
}
return (await this.findAll(core.class.Tx, { _id: tx._id }, { limit: 1 })).length === 0
},
handleResult:
tx._class === core.class.TxApplyIf
? async (result) => {
if (tx._class === core.class.TxApplyIf) {
// We need to check extra broadcast's and perform them before
const r = result as TxApplyResult
const dr = r?.derived ?? []
if (dr.length > 0) {
this.handler(...dr)
}
}
}
: undefined
}
})
}

View File

@ -90,7 +90,6 @@ import { serverViewId } from '@hcengineering/server-view'
import {
ClientSession,
start as startJsonRpc,
type BroadcastCall,
type PipelineFactory,
type ServerFactory,
type Session
@ -373,11 +372,11 @@ export function start (
return createPipeline(ctx, conf, middlewares, upgrade, broadcast)
}
const sessionFactory = (token: Token, pipeline: Pipeline, broadcast: BroadcastCall): Session => {
const sessionFactory = (token: Token, pipeline: Pipeline): Session => {
if (token.extra?.mode === 'backup') {
return new BackupClientSession(broadcast, token, pipeline)
return new BackupClientSession(token, pipeline)
}
return new ClientSession(broadcast, token, pipeline)
return new ClientSession(token, pipeline)
}
return startJsonRpc(getMetricsContext(), {

View File

@ -51,7 +51,6 @@
"@hcengineering/login-assets": "^0.6.0",
"@hcengineering/login-resources": "^0.6.2",
"@hcengineering/client": "^0.6.6",
"@hcengineering/dev-client-resources": "^0.6.1",
"@hcengineering/workbench": "^0.6.2",
"@hcengineering/workbench-resources": "^0.6.1",
"@hcengineering/view": "^0.6.2",

View File

@ -481,16 +481,6 @@
"projectFolder": "server/uws",
"shouldPublish": false
},
{
"packageName": "@hcengineering/dev-storage",
"projectFolder": "dev/storage",
"shouldPublish": false
},
{
"packageName": "@hcengineering/dev-server",
"projectFolder": "dev/server",
"shouldPublish": false
},
{
"packageName": "@hcengineering/theme",
"projectFolder": "packages/theme",
@ -516,11 +506,6 @@
"projectFolder": "dev/prod",
"shouldPublish": false
},
// {
// "packageName": "@hcengineering/prod-tracker",
// "projectFolder": "products/tracker",
// "shouldPublish": false
// },
{
"packageName": "@hcengineering/server-core",
"projectFolder": "server/core",
@ -661,11 +646,6 @@
"projectFolder": "plugins/task-resources",
"shouldPublish": false
},
{
"packageName": "@hcengineering/dev-client-resources",
"projectFolder": "dev/client-resources",
"shouldPublish": false
},
{
"packageName": "@hcengineering/model-workbench",
"projectFolder": "models/workbench",
@ -741,11 +721,6 @@
"projectFolder": "models/server-core",
"shouldPublish": false
},
{
"packageName": "@hcengineering/dev-account",
"projectFolder": "dev/account",
"shouldPublish": false
},
{
"packageName": "@hcengineering/server-attachment",
"projectFolder": "server-plugins/attachment",

View File

@ -35,7 +35,6 @@ import { type DbConfiguration } from './configuration'
import { createServerStorage } from './server'
import {
type BroadcastFunc,
type HandledBroadcastFunc,
type Middleware,
type MiddlewareCreator,
type Pipeline,
@ -52,30 +51,16 @@ export async function createPipeline (
upgrade: boolean,
broadcast: BroadcastFunc
): Promise<Pipeline> {
let broadcastHook: HandledBroadcastFunc = (): Tx[] => {
return []
}
const storage = await ctx.with(
'create-server-storage',
{},
async (ctx) =>
await createServerStorage(ctx, conf, {
upgrade,
broadcast: (tx: Tx[], targets?: string[]) => {
const sendTx = broadcastHook?.(tx, targets) ?? tx
broadcast(sendTx, targets)
}
broadcast
})
)
const pipelineResult = await PipelineImpl.create(
ctx.newChild('pipeline-operations', {}),
storage,
constructors,
broadcast
)
broadcastHook = (tx, targets) => {
return pipelineResult.handleBroadcast(tx, targets)
}
const pipelineResult = await PipelineImpl.create(ctx.newChild('pipeline-operations', {}), storage, constructors)
return pipelineResult
}
@ -86,30 +71,21 @@ class PipelineImpl implements Pipeline {
this.modelDb = storage.modelDb
}
handleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
return this.head?.handleBroadcast(tx, targets) ?? tx
}
static async create (
ctx: MeasureContext,
storage: ServerStorage,
constructors: MiddlewareCreator[],
broadcast: BroadcastFunc
constructors: MiddlewareCreator[]
): Promise<PipelineImpl> {
const pipeline = new PipelineImpl(storage)
pipeline.head = await pipeline.buildChain(ctx, constructors, broadcast)
pipeline.head = await pipeline.buildChain(ctx, constructors)
return pipeline
}
private async buildChain (
ctx: MeasureContext,
constructors: MiddlewareCreator[],
broadcast: BroadcastFunc
): Promise<Middleware | undefined> {
private async buildChain (ctx: MeasureContext, constructors: MiddlewareCreator[]): Promise<Middleware | undefined> {
let current: Middleware | undefined
for (let index = constructors.length - 1; index >= 0; index--) {
const element = constructors[index]
current = await ctx.with('build chain', {}, async (ctx) => await element(ctx, broadcast, this.storage, current))
current = await ctx.with('build chain', {}, async (ctx) => await element(ctx, this.storage, current))
}
return current
}
@ -122,19 +98,18 @@ class PipelineImpl implements Pipeline {
): Promise<FindResult<T>> {
return this.head !== undefined
? await this.head.findAll(ctx, _class, query, options)
: await this.storage.findAll(ctx, _class, query, options)
: await this.storage.findAll(ctx.ctx, _class, query, options)
}
async searchFulltext (ctx: SessionContext, query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
return this.head !== undefined
? await this.head.searchFulltext(ctx, query, options)
: await this.storage.searchFulltext(ctx, query, options)
: await this.storage.searchFulltext(ctx.ctx, query, options)
}
async tx (ctx: SessionContext, tx: Tx): Promise<[TxResult, Tx[], string[] | undefined]> {
async tx (ctx: SessionContext, tx: Tx): Promise<TxResult> {
if (this.head === undefined) {
const res = await this.storage.tx(ctx, tx)
return [...res, undefined]
return await this.storage.tx(ctx, tx)
} else {
return await this.head.tx(ctx, tx)
}

View File

@ -144,7 +144,7 @@ export async function createServerStorage (
space: core.space.DerivedTx,
params: evt
}
options.broadcast?.([tx])
options.broadcast([tx])
}
)
return new FullTextIndex(

View File

@ -15,15 +15,19 @@
//
import core, {
type Account,
type AttachedDoc,
type Class,
ClassifierKind,
type Client,
type Collection,
DOMAIN_MODEL,
DOMAIN_TRANSIENT,
DOMAIN_TX,
TxFactory,
TxProcessor,
cutObjectArray,
toFindResult,
type Account,
type AttachedDoc,
type Class,
type Client,
type Collection,
type Doc,
type DocumentQuery,
type DocumentUpdate,
@ -40,22 +44,19 @@ import core, {
type SearchQuery,
type SearchResult,
type ServerStorage,
type SessionOperationContext,
type StorageIterator,
type Timestamp,
type Tx,
type TxApplyIf,
type TxCUD,
type TxCollectionCUD,
TxFactory,
TxProcessor,
type TxRemoveDoc,
type TxResult,
type TxUpdateDoc,
type WorkspaceIdWithUrl,
cutObjectArray,
toFindResult
type WorkspaceIdWithUrl
} from '@hcengineering/core'
import { type Metadata, getResource } from '@hcengineering/platform'
import { getResource, type Metadata } from '@hcengineering/platform'
import { LiveQuery as LQ } from '@hcengineering/query'
import crypto from 'node:crypto'
import { type DbAdapter } from '../adapter'
@ -555,7 +556,7 @@ export class TServerStorage implements ServerStorage {
}
private async processDerived (
ctx: MeasureContext,
ctx: SessionOperationContext,
txes: Tx[],
triggerFx: Effects,
findAll: ServerStorage['findAll'],
@ -570,11 +571,13 @@ export class TServerStorage implements ServerStorage {
): Promise<FindResult<T>> =>
findAll(mctx, clazz, query, options)
const removed = await ctx.with('process-remove', {}, (ctx) => this.processRemove(ctx, txes, findAll, removedMap))
const collections = await ctx.with('process-collection', {}, (ctx) =>
this.processCollection(ctx, txes, findAll, removedMap)
const removed = await ctx.with('process-remove', {}, (ctx) =>
this.processRemove(ctx.ctx, txes, findAll, removedMap)
)
const moves = await ctx.with('process-move', {}, (ctx) => this.processMove(ctx, txes, findAll))
const collections = await ctx.with('process-collection', {}, (ctx) =>
this.processCollection(ctx.ctx, txes, findAll, removedMap)
)
const moves = await ctx.with('process-move', {}, (ctx) => this.processMove(ctx.ctx, txes, findAll))
const triggerControl: Omit<TriggerControl, 'txFactory' | 'ctx' | 'result'> = {
removedMap,
@ -594,7 +597,7 @@ export class TServerStorage implements ServerStorage {
serviceFx: (f) => {
triggerFx.fx(() => f(this.serviceAdaptersManager))
},
findAll: fAll(ctx),
findAll: fAll(ctx.ctx),
findAllCtx: findAll,
modelDb: this.modelDb,
hierarchy: this.hierarchy,
@ -614,8 +617,8 @@ export class TServerStorage implements ServerStorage {
result.push(
...(await this.triggers.apply(ctx, txes, {
...triggerControl,
ctx,
findAll: fAll(ctx),
ctx: ctx.ctx,
findAll: fAll(ctx.ctx),
result
}))
)
@ -629,14 +632,14 @@ export class TServerStorage implements ServerStorage {
private async processDerivedTxes (
derived: Tx[],
ctx: MeasureContext,
ctx: SessionOperationContext,
triggerFx: Effects,
findAll: ServerStorage['findAll'],
removedMap: Map<Ref<Doc>, Doc>
): Promise<Tx[]> {
derived.sort((a, b) => a.modifiedOn - b.modifiedOn)
await ctx.with('derived-route-tx', {}, (ctx) => this.routeTx(ctx, removedMap, ...derived))
await ctx.with('derived-route-tx', {}, (ctx) => this.routeTx(ctx.ctx, removedMap, ...derived))
const nestedTxes: Tx[] = []
if (derived.length > 0) {
@ -692,17 +695,8 @@ export class TServerStorage implements ServerStorage {
return { passed, onEnd }
}
async apply (ctx: MeasureContext, txes: Tx[], broadcast: boolean, target?: string[]): Promise<TxResult[]> {
const result = await this.processTxes(ctx, txes)
let derived: Tx[] = []
derived = result[1]
if (broadcast) {
this.options?.broadcast?.([...txes, ...derived], target)
}
return result[0]
async apply (ctx: SessionOperationContext, txes: Tx[], broadcast: boolean, target?: string[]): Promise<TxResult> {
return await this.processTxes(ctx, txes, broadcast, target)
}
fillTxes (txes: Tx[], txToStore: Tx[], modelTx: Tx[], txToProcess: Tx[], applyTxes: Tx[]): void {
@ -727,7 +721,12 @@ export class TServerStorage implements ServerStorage {
}
}
async processTxes (ctx: MeasureContext, txes: Tx[]): Promise<[TxResult[], Tx[]]> {
async processTxes (
ctx: SessionOperationContext,
txes: Tx[],
broadcast: boolean,
target?: string[]
): Promise<TxResult> {
// store tx
const _findAll: ServerStorage['findAll'] = async <T extends Doc>(
ctx: MeasureContext,
@ -745,14 +744,14 @@ export class TServerStorage implements ServerStorage {
const removedMap = new Map<Ref<Doc>, Doc>()
const onEnds: (() => void)[] = []
const result: TxResult[] = []
let derived: Tx[] = []
const derived: Tx[] = [...txes].filter((it) => it._class !== core.class.TxApplyIf)
try {
this.fillTxes(txes, txToStore, modelTx, txToProcess, applyTxes)
for (const tx of applyTxes) {
const applyIf = tx as TxApplyIf
// Wait for scope promise if found
const passed = await this.verifyApplyIf(ctx, applyIf, _findAll)
const passed = await this.verifyApplyIf(ctx.ctx, applyIf, _findAll)
onEnds.push(passed.onEnd)
if (passed.passed) {
result.push({
@ -760,7 +759,7 @@ export class TServerStorage implements ServerStorage {
success: true
})
this.fillTxes(applyIf.txes, txToStore, modelTx, txToProcess, applyTxes)
derived = [...applyIf.txes]
derived.push(...applyIf.txes)
} else {
result.push({
derived: [],
@ -776,35 +775,37 @@ export class TServerStorage implements ServerStorage {
await this.triggers.tx(tx)
await this.modelDb.tx(tx)
}
await ctx.with('domain-tx', {}, async (ctx) => await this.getAdapter(DOMAIN_TX).tx(ctx, ...txToStore))
result.push(...(await ctx.with('apply', {}, (ctx) => this.routeTx(ctx, removedMap, ...txToProcess))))
await ctx.with('domain-tx', {}, async (ctx) => await this.getAdapter(DOMAIN_TX).tx(ctx.ctx, ...txToStore))
result.push(...(await ctx.with('apply', {}, (ctx) => this.routeTx(ctx.ctx, removedMap, ...txToProcess))))
// invoke triggers and store derived objects
derived = derived.concat(await this.processDerived(ctx, txToProcess, triggerFx, _findAll, removedMap))
derived.push(...(await this.processDerived(ctx, txToProcess, triggerFx, _findAll, removedMap)))
// index object
await ctx.with('fulltext-tx', {}, async (ctx) => {
await this.fulltext.tx(ctx, [...txToProcess, ...derived])
await this.fulltext.tx(ctx.ctx, [...txToProcess, ...derived])
})
for (const fx of triggerFx.effects) {
await fx()
}
} catch (err: any) {
ctx.error('error process tx', { error: err })
ctx.ctx.error('error process tx', { error: err })
throw err
} finally {
onEnds.forEach((p) => {
p()
})
}
return [result, derived]
if (derived.length > 0 && broadcast) {
ctx.derived.push({ derived, target })
}
return result[0]
}
async tx (ctx: MeasureContext, tx: Tx): Promise<[TxResult, Tx[]]> {
async tx (ctx: SessionOperationContext, tx: Tx): Promise<TxResult> {
return await ctx.with('client-tx', { _class: tx._class }, async (ctx) => {
const result = await this.processTxes(ctx, [tx])
return [result[0][0], result[1]]
return await this.processTxes(ctx, [tx], true)
})
}

View File

@ -15,22 +15,22 @@
//
import core, {
TxFactory,
matchQuery,
type AttachedDoc,
type Class,
type Doc,
type DocumentQuery,
type Hierarchy,
type MeasureContext,
type Obj,
type Ref,
type SessionOperationContext,
type Tx,
type TxCollectionCUD,
type TxCreateDoc,
TxFactory,
matchQuery
type TxCreateDoc
} from '@hcengineering/core'
import { type Resource, getResource } from '@hcengineering/platform'
import { getResource, type Resource } from '@hcengineering/platform'
import type { Trigger, TriggerControl, TriggerFunc } from './types'
import serverCore from './plugin'
@ -58,7 +58,7 @@ export class Triggers {
}
}
async apply (ctx: MeasureContext, tx: Tx[], ctrl: Omit<TriggerControl, 'txFactory'>): Promise<Tx[]> {
async apply (ctx: SessionOperationContext, tx: Tx[], ctrl: Omit<TriggerControl, 'txFactory'>): Promise<Tx[]> {
const result: Tx[] = []
for (const [query, trigger, resource] of this.triggers) {
let matches = tx
@ -73,9 +73,9 @@ export class Triggers {
result.push(
...(await trigger(tx, {
...ctrl,
ctx,
ctx: ctx.ctx,
txFactory: new TxFactory(tx.modifiedBy, true),
findAll: async (clazz, query, options) => await ctrl.findAllCtx(ctx, clazz, query, options),
findAll: async (clazz, query, options) => await ctrl.findAllCtx(ctx.ctx, clazz, query, options),
apply: async (tx, broadcast, target) => {
return await ctrl.applyCtx(ctx, tx, broadcast, target)
},

View File

@ -14,6 +14,7 @@
//
import {
MeasureMetricsContext,
type Account,
type Class,
type Doc,
@ -23,7 +24,6 @@ import {
type Hierarchy,
type LowLevelStorage,
type MeasureContext,
MeasureMetricsContext,
type ModelDb,
type Obj,
type Ref,
@ -31,6 +31,7 @@ import {
type SearchQuery,
type SearchResult,
type ServerStorage,
type SessionOperationContext,
type Space,
type Storage,
type Timestamp,
@ -48,7 +49,7 @@ import { type StorageAdapter } from './storage'
/**
* @public
*/
export interface SessionContext extends MeasureContext {
export interface SessionContext extends SessionOperationContext {
userEmail: string
sessionId: string
admin?: boolean
@ -66,8 +67,6 @@ export interface Middleware {
options?: FindOptions<T>
) => Promise<FindResult<T>>
searchFulltext: (ctx: SessionContext, query: SearchQuery, options: SearchOptions) => Promise<SearchResult>
handleBroadcast: (tx: Tx[], targets?: string[]) => Tx[]
}
/**
@ -82,17 +81,12 @@ export type HandledBroadcastFunc = (tx: Tx[], targets?: string[]) => Tx[]
/**
* @public
*/
export type MiddlewareCreator = (
ctx: MeasureContext,
broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
) => Promise<Middleware>
export type MiddlewareCreator = (ctx: MeasureContext, storage: ServerStorage, next?: Middleware) => Promise<Middleware>
/**
* @public
*/
export type TxMiddlewareResult = [TxResult, Tx[], string[] | undefined]
export type TxMiddlewareResult = TxResult
/**
* @public
@ -107,7 +101,7 @@ export interface Pipeline extends LowLevelStorage {
options?: FindOptions<T>
) => Promise<FindResult<T>>
searchFulltext: (ctx: SessionContext, query: SearchQuery, options: SearchOptions) => Promise<SearchResult>
tx: (ctx: SessionContext, tx: Tx) => Promise<[TxResult, Tx[], string[] | undefined]>
tx: (ctx: SessionContext, tx: Tx) => Promise<TxResult>
close: () => Promise<void>
}
@ -137,7 +131,7 @@ export interface TriggerControl {
serviceFx: (f: (adapter: ServiceAdaptersManager) => Promise<void>) => void
// Bulk operations in case trigger require some
apply: (tx: Tx[], broadcast: boolean, target?: string[]) => Promise<TxResult>
applyCtx: (ctx: MeasureContext, tx: Tx[], broadcast: boolean, target?: string[]) => Promise<TxResult>
applyCtx: (ctx: SessionOperationContext, tx: Tx[], broadcast: boolean, target?: string[]) => Promise<TxResult>
// Will create a live query if missing and return values immediately if already asked.
queryFind: <T extends Doc>(
@ -411,7 +405,7 @@ export interface ServerStorageOptions {
// Indexing is not required to be started for upgrade mode.
upgrade: boolean
broadcast?: BroadcastFunc
broadcast: BroadcastFunc
}
export interface ServiceAdapter {

View File

@ -54,15 +54,7 @@ export abstract class BaseMiddleware {
if (this.next !== undefined) {
return await this.next.tx(ctx, tx)
}
const res = await this.storage.tx(ctx, tx)
return [res[0], res[1], undefined]
}
provideHandleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
if (this.next !== undefined) {
return this.next.handleBroadcast(tx, targets)
}
return tx
return await this.storage.tx(ctx, tx)
}
protected async provideFindAll<T extends Doc>(
@ -74,7 +66,7 @@ export abstract class BaseMiddleware {
if (this.next !== undefined) {
return await this.next.findAll(ctx, _class, query, options)
}
return await this.storage.findAll(ctx, _class, query, options)
return await this.storage.findAll(ctx.ctx, _class, query, options)
}
protected async provideSearchFulltext (
@ -85,6 +77,6 @@ export abstract class BaseMiddleware {
if (this.next !== undefined) {
return await this.next.searchFulltext(ctx, query, options)
}
return await this.storage.searchFulltext(ctx, query, options)
return await this.storage.searchFulltext(ctx.ctx, query, options)
}
}

View File

@ -29,7 +29,7 @@ import core, {
TxCUD
} from '@hcengineering/core'
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { BaseMiddleware } from './base'
const configurationAccountEmail = '#configurator@hc.engineering'
@ -45,7 +45,6 @@ export class ConfigurationMiddleware extends BaseMiddleware implements Middlewar
static async create (
ctx: MeasureContext,
broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
): Promise<ConfigurationMiddleware> {
@ -68,10 +67,6 @@ export class ConfigurationMiddleware extends BaseMiddleware implements Middlewar
return await this.provideTx(ctx, tx)
}
handleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
return this.provideHandleBroadcast(tx, targets)
}
override async findAll<T extends Doc>(
ctx: SessionContext,
_class: Ref<Class<T>>,

View File

@ -26,7 +26,7 @@ import {
clone,
toFindResult
} from '@hcengineering/core'
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { BaseMiddleware } from './base'
/**
@ -37,12 +37,7 @@ export class LookupMiddleware extends BaseMiddleware implements Middleware {
super(storage, next)
}
static async create (
ctx: MeasureContext,
broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
): Promise<LookupMiddleware> {
static async create (ctx: MeasureContext, storage: ServerStorage, next?: Middleware): Promise<LookupMiddleware> {
return new LookupMiddleware(storage, next)
}
@ -50,10 +45,6 @@ export class LookupMiddleware extends BaseMiddleware implements Middleware {
return await this.provideTx(ctx, tx)
}
handleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
return this.provideHandleBroadcast(tx, targets)
}
override async findAll<T extends Doc>(
ctx: SessionContext,
_class: Ref<Class<T>>,

View File

@ -13,8 +13,8 @@
// limitations under the License.
//
import core, { MeasureContext, ServerStorage, Tx, systemAccountEmail, TxCollectionCUD } from '@hcengineering/core'
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import core, { MeasureContext, ServerStorage, Tx, TxCollectionCUD, systemAccountEmail } from '@hcengineering/core'
import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { BaseMiddleware } from './base'
/**
@ -25,19 +25,10 @@ export class ModifiedMiddleware extends BaseMiddleware implements Middleware {
super(storage, next)
}
static async create (
ctx: MeasureContext,
broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
): Promise<ModifiedMiddleware> {
static async create (ctx: MeasureContext, storage: ServerStorage, next?: Middleware): Promise<ModifiedMiddleware> {
return new ModifiedMiddleware(storage, next)
}
handleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
return this.provideHandleBroadcast(tx, targets)
}
async tx (ctx: SessionContext, tx: Tx): Promise<TxMiddlewareResult> {
if (tx.modifiedBy !== core.account.System && ctx.userEmail !== systemAccountEmail) {
tx.modifiedOn = Date.now()

View File

@ -30,7 +30,7 @@ import core, {
systemAccountEmail
} from '@hcengineering/core'
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { DOMAIN_PREFERENCE } from '@hcengineering/server-preference'
import { BaseMiddleware } from './base'
import { getUser, mergeTargets } from './utils'
@ -45,12 +45,7 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware {
super(storage, next)
}
static async create (
ctx: MeasureContext,
broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
): Promise<PrivateMiddleware> {
static async create (ctx: MeasureContext, storage: ServerStorage, next?: Middleware): Promise<PrivateMiddleware> {
return new PrivateMiddleware(storage, next)
}
@ -72,11 +67,11 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware {
}
}
const res = await this.provideTx(ctx, tx)
return [res[0], res[1], mergeTargets(target, res[2])]
}
handleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
return this.provideHandleBroadcast(tx, targets)
// Add target to all broadcasts
ctx.derived.forEach((it) => {
it.target = mergeTargets(target, it.target)
})
return res
}
override async findAll<T extends Doc>(

View File

@ -24,7 +24,7 @@ import {
ServerStorage,
Tx
} from '@hcengineering/core'
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { BaseMiddleware } from './base'
import { deepEqual } from 'fast-equals'
@ -47,12 +47,7 @@ export class QueryJoinMiddleware extends BaseMiddleware implements Middleware {
super(storage, next)
}
static async create (
ctx: MeasureContext,
broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
): Promise<QueryJoinMiddleware> {
static async create (ctx: MeasureContext, storage: ServerStorage, next?: Middleware): Promise<QueryJoinMiddleware> {
return new QueryJoinMiddleware(storage, next)
}
@ -60,10 +55,6 @@ export class QueryJoinMiddleware extends BaseMiddleware implements Middleware {
return await this.provideTx(ctx, tx)
}
handleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
return this.provideHandleBroadcast(tx, targets)
}
override async findAll<T extends Doc>(
ctx: SessionContext,
_class: Ref<Class<T>>,

View File

@ -37,7 +37,7 @@ import core, {
TypedSpace
} from '@hcengineering/core'
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { BaseMiddleware } from './base'
import { getUser } from './utils'
@ -54,7 +54,6 @@ export class SpacePermissionsMiddleware extends BaseMiddleware implements Middle
static async create (
ctx: MeasureContext,
broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
): Promise<SpacePermissionsMiddleware> {
@ -317,16 +316,14 @@ export class SpacePermissionsMiddleware extends BaseMiddleware implements Middle
await this.processPermissionsUpdatesFromTx(ctx, tx)
await this.checkPermissions(ctx, tx)
const res = await this.provideTx(ctx, tx)
for (const tx of res[1]) {
await this.processPermissionsUpdatesFromTx(ctx, tx)
for (const txd of ctx.derived) {
for (const tx of txd.derived) {
await this.processPermissionsUpdatesFromTx(ctx, tx)
}
}
return res
}
handleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
return this.provideHandleBroadcast(tx, targets)
}
protected async checkPermissions (ctx: SessionContext, tx: Tx): Promise<void> {
if (tx._class === core.class.TxApplyIf) {
const applyTx = tx as TxApplyIf

View File

@ -45,7 +45,7 @@ import core, {
systemAccountEmail
} from '@hcengineering/core'
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { BaseMiddleware } from './base'
import { getUser, isOwner, isSystem, mergeTargets } from './utils'
@ -74,21 +74,16 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
core.space.Tx
]
private constructor (
private readonly broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
) {
private constructor (storage: ServerStorage, next?: Middleware) {
super(storage, next)
}
static async create (
ctx: MeasureContext,
broadcast: BroadcastFunc,
storage: ServerStorage,
next?: Middleware
): Promise<SpaceSecurityMiddleware> {
const res = new SpaceSecurityMiddleware(broadcast, storage, next)
const res = new SpaceSecurityMiddleware(storage, next)
res.spaceMeasureCtx = ctx.newChild('space chain', {})
res.spaceSecurityInit = res.init(res.spaceMeasureCtx)
return res
@ -180,6 +175,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
}
private async pushMembersHandle (
ctx: SessionContext,
addedMembers: Ref<Account> | Position<Ref<Account>>,
space: Ref<Space>
): Promise<void> {
@ -187,14 +183,15 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
for (const member of addedMembers.$each) {
this.addMemberSpace(member, space)
}
await this.brodcastEvent(addedMembers.$each, space)
await this.brodcastEvent(ctx, addedMembers.$each, space)
} else {
this.addMemberSpace(addedMembers, space)
await this.brodcastEvent([addedMembers], space)
await this.brodcastEvent(ctx, [addedMembers], space)
}
}
private async pullMembersHandle (
ctx: SessionContext,
removedMembers: Partial<Ref<Account>> | PullArray<Ref<Account>>,
space: Ref<Space>
): Promise<void> {
@ -204,15 +201,15 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
for (const member of $in) {
this.removeMemberSpace(member, space)
}
await this.brodcastEvent($in, space)
await this.brodcastEvent(ctx, $in, space)
}
} else {
this.removeMemberSpace(removedMembers, space)
await this.brodcastEvent([removedMembers], space)
await this.brodcastEvent(ctx, [removedMembers], space)
}
}
private async syncMembers (members: Ref<Account>[], space: SpaceWithMembers): Promise<void> {
private async syncMembers (ctx: SessionContext, members: Ref<Account>[], space: SpaceWithMembers): Promise<void> {
const oldMembers = new Set(space.members)
const newMembers = new Set(members)
const changed: Ref<Account>[] = []
@ -229,11 +226,11 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
}
}
if (changed.length > 0) {
await this.brodcastEvent(changed, space._id)
await this.brodcastEvent(ctx, changed, space._id)
}
}
private async brodcastEvent (users: Ref<Account>[], space?: Ref<Space>): Promise<void> {
private async brodcastEvent (ctx: SessionContext, users: Ref<Account>[], space?: Ref<Space>): Promise<void> {
const targets = await this.getTargets(users)
const tx: TxWorkspaceEvent = {
_class: core.class.TxWorkspaceEvent,
@ -245,12 +242,16 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
space: core.space.DerivedTx,
params: null
}
this.broadcast([tx], targets)
ctx.derived.push({
derived: [tx],
target: targets
})
}
private async broadcastNonMembers (space: SpaceWithMembers): Promise<void> {
private async broadcastNonMembers (ctx: SessionContext, space: SpaceWithMembers): Promise<void> {
const users = await this.storage.modelDb.findAll(core.class.Account, { _id: { $nin: space?.members } })
await this.brodcastEvent(
ctx,
users.map((p) => p._id),
space._id
)
@ -268,23 +269,23 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
if (updateDoc.operations.private) {
this.privateSpaces.add(updateDoc.objectId)
this.publicSpaces.delete(updateDoc.objectId)
await this.broadcastNonMembers(space)
await this.broadcastNonMembers(ctx, space)
} else if (!updateDoc.operations.private) {
this.privateSpaces.delete(updateDoc.objectId)
this.publicSpaces.add(updateDoc.objectId)
await this.broadcastNonMembers(space)
await this.broadcastNonMembers(ctx, space)
}
}
if (updateDoc.operations.members !== undefined) {
await this.syncMembers(updateDoc.operations.members, space)
await this.syncMembers(ctx, updateDoc.operations.members, space)
}
if (updateDoc.operations.$push?.members !== undefined) {
await this.pushMembersHandle(updateDoc.operations.$push.members, space._id)
await this.pushMembersHandle(ctx, updateDoc.operations.$push.members, space._id)
}
if (updateDoc.operations.$pull?.members !== undefined) {
await this.pullMembersHandle(updateDoc.operations.$pull.members, space._id)
await this.pullMembersHandle(ctx, updateDoc.operations.$pull.members, space._id)
}
const updatedSpace = TxProcessor.updateDoc2Doc(space as any, updateDoc)
this.spacesMap.set(updateDoc.objectId, updatedSpace)
@ -380,9 +381,9 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
}
await this.processTxSpaceDomain(tx as TxCUD<Doc>)
if (h.isDerived(cudTx.objectClass, core.class.Account) && cudTx._class === core.class.TxUpdateDoc) {
const ctx = cudTx as TxUpdateDoc<Account>
if (ctx.operations.role !== undefined) {
await this.brodcastEvent([ctx.objectId])
const cud = cudTx as TxUpdateDoc<Account>
if (cud.operations.role !== undefined) {
await this.brodcastEvent(ctx, [cud.objectId])
}
}
}
@ -397,23 +398,25 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
await this.processTx(ctx, tx)
const targets = await this.getTxTargets(ctx, tx)
const res = await this.provideTx(ctx, tx)
for (const tx of res[1]) {
await this.processTx(ctx, tx)
for (const txd of ctx.derived) {
for (const tx of txd.derived) {
await this.processTx(ctx, tx)
}
}
return [res[0], res[1], mergeTargets(targets, res[2])]
}
ctx.derived.forEach((it) => {
it.target = mergeTargets(targets, it.target)
})
handleBroadcast (tx: Tx[], targets?: string[]): Tx[] {
const process = async (): Promise<void> => {
await this.waitInit()
for (const t of tx) {
await this.waitInit()
for (const tt of ctx.derived) {
for (const t of tt.derived) {
if (this.storage.hierarchy.isDerived(t._class, core.class.TxCUD)) {
await this.processTxSpaceDomain(t as TxCUD<Doc>)
}
}
}
void process()
return this.provideHandleBroadcast(tx, targets)
return res
}
private getAllAllowedSpaces (account: Account, isData: boolean): Ref<Space>[] {

View File

@ -32,7 +32,10 @@ import core, {
SortingOrder,
type Space,
TxOperations,
type WorkspaceId
type WorkspaceId,
type SessionOperationContext,
type ParamsType,
type FullParamsType
} from '@hcengineering/core'
import {
type ContentTextAdapter,
@ -169,11 +172,26 @@ describe('mongo operations', () => {
storageFactory: () => createNullStorageFactory()
}
const ctx = new MeasureMetricsContext('client', {})
const serverStorage = await createServerStorage(ctx, conf, { upgrade: false })
const serverStorage = await createServerStorage(ctx, conf, {
upgrade: false,
broadcast: () => {}
})
const soCtx: SessionOperationContext = {
ctx,
derived: [],
with: async <T>(
name: string,
params: ParamsType,
op: (ctx: SessionOperationContext) => T | Promise<T>,
fullParams?: FullParamsType
): Promise<T> => {
return await op(soCtx)
}
}
client = await createClient(async (handler) => {
const st: ClientConnection = {
findAll: async (_class, query, options) => await serverStorage.findAll(ctx, _class, query, options),
tx: async (tx) => (await serverStorage.tx(ctx, tx))[0],
tx: async (tx) => await serverStorage.tx(soCtx, tx),
searchFulltext: async () => ({ docs: [] }),
close: async () => {},
loadChunk: async (domain): Promise<DocChunk> => await Promise.reject(new Error('unsupported')),
@ -197,7 +215,7 @@ describe('mongo operations', () => {
})
it('check add', async () => {
jest.setTimeout(50000)
jest.setTimeout(500000)
for (let i = 0; i < 50; i++) {
await operations.createDoc(taskPlugin.class.Task, '' as Ref<Space>, {
name: `my-task-${i}`,

View File

@ -40,7 +40,6 @@ export interface Request<P extends any[]> {
export interface HelloRequest extends Request<any[]> {
binary?: boolean
compression?: boolean
broadcast?: boolean
}
/**
* @public

View File

@ -1,7 +1,7 @@
import { Doc, DocChunk, DocInfo, Domain, MeasureContext, Ref, StorageIterator } from '@hcengineering/core'
import { estimateDocSize, Pipeline } from '@hcengineering/server-core'
import { Doc, DocInfo, Domain, Ref, StorageIterator } from '@hcengineering/core'
import { Pipeline, estimateDocSize } from '@hcengineering/server-core'
import { Token } from '@hcengineering/server-token'
import { BroadcastCall, ClientSession, Session } from '@hcengineering/server-ws'
import { ClientSession, Session, type ClientSessionCtx } from '@hcengineering/server-ws'
const chunkSize = 2 * 1024 * 1024
@ -19,9 +19,9 @@ export interface ChunkInfo {
* @public
*/
export interface BackupSession extends Session {
loadChunk: (ctx: MeasureContext, domain: Domain, idx?: number) => Promise<DocChunk>
closeChunk: (ctx: MeasureContext, idx: number) => Promise<void>
loadDocs: (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]) => Promise<Doc[]>
loadChunk: (ctx: ClientSessionCtx, domain: Domain, idx?: number) => Promise<void>
closeChunk: (ctx: ClientSessionCtx, idx: number) => Promise<void>
loadDocs: (ctx: ClientSessionCtx, domain: Domain, docs: Ref<Doc>[]) => Promise<void>
}
/**
@ -29,19 +29,18 @@ export interface BackupSession extends Session {
*/
export class BackupClientSession extends ClientSession implements BackupSession {
constructor (
protected readonly broadcast: BroadcastCall,
protected readonly token: Token,
protected readonly _pipeline: Pipeline
) {
super(broadcast, token, _pipeline)
super(token, _pipeline)
}
idIndex = 0
chunkInfo = new Map<number, ChunkInfo>()
async loadChunk (ctx: MeasureContext, domain: Domain, idx?: number): Promise<DocChunk> {
async loadChunk (_ctx: ClientSessionCtx, domain: Domain, idx?: number): Promise<void> {
this.lastRequest = Date.now()
return await ctx.with('load-chunk', { domain }, async (ctx) => {
await _ctx.ctx.with('load-chunk', { domain }, async (ctx) => {
idx = idx ?? this.idIndex++
let chunk: ChunkInfo | undefined = this.chunkInfo.get(idx)
if (chunk !== undefined) {
@ -71,37 +70,40 @@ export class BackupClientSession extends ClientSession implements BackupSession
docs.push(doc)
}
return {
await _ctx.sendResponse({
idx,
docs,
finished: chunk.finished
}
})
})
}
async closeChunk (ctx: MeasureContext, idx: number): Promise<void> {
async closeChunk (ctx: ClientSessionCtx, idx: number): Promise<void> {
this.lastRequest = Date.now()
await ctx.with('close-chunk', {}, async () => {
await ctx.ctx.with('close-chunk', {}, async () => {
const chunk = this.chunkInfo.get(idx)
this.chunkInfo.delete(idx)
if (chunk != null) {
await chunk.iterator.close(ctx)
await chunk.iterator.close(ctx.ctx)
}
await ctx.sendResponse({})
})
}
async loadDocs (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
async loadDocs (ctx: ClientSessionCtx, domain: Domain, docs: Ref<Doc>[]): Promise<void> {
this.lastRequest = Date.now()
return await this._pipeline.storage.load(ctx, domain, docs)
await ctx.sendResponse(await this._pipeline.storage.load(ctx.ctx, domain, docs))
}
async upload (ctx: MeasureContext, domain: Domain, docs: Doc[]): Promise<void> {
async upload (ctx: ClientSessionCtx, domain: Domain, docs: Doc[]): Promise<void> {
this.lastRequest = Date.now()
await this._pipeline.storage.upload(ctx, domain, docs)
await this._pipeline.storage.upload(ctx.ctx, domain, docs)
await ctx.sendResponse({})
}
async clean (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<void> {
async clean (ctx: ClientSessionCtx, domain: Domain, docs: Ref<Doc>[]): Promise<void> {
this.lastRequest = Date.now()
await this._pipeline.storage.clean(ctx, domain, docs)
await this._pipeline.storage.clean(ctx.ctx, domain, docs)
await ctx.sendResponse({})
}
}

View File

@ -83,7 +83,7 @@ describe('server', () => {
return { docs: [] }
}
}),
sessionFactory: (token, pipeline, broadcast) => new ClientSession(broadcast, token, pipeline),
sessionFactory: (token, pipeline) => new ClientSession(token, pipeline),
port: 3335,
productId: '',
serverFactory: startHttpServer,
@ -182,7 +182,7 @@ describe('server', () => {
return { docs: [] }
}
}),
sessionFactory: (token, pipeline, broadcast) => new ClientSession(broadcast, token, pipeline),
sessionFactory: (token, pipeline) => new ClientSession(token, pipeline),
port: 3336,
productId: '',
serverFactory: startHttpServer,

View File

@ -19,6 +19,7 @@ import core, {
TxProcessor,
WorkspaceEvent,
generateId,
toIdMap,
type Account,
type BulkUpdateEvent,
type Class,
@ -26,23 +27,47 @@ import core, {
type DocumentQuery,
type FindOptions,
type FindResult,
type LoadModelResponse,
type FullParamsType,
type MeasureContext,
type ParamsType,
type Ref,
type SearchOptions,
type SearchQuery,
type SearchResult,
type SessionOperationContext,
type Timestamp,
type Tx,
type TxApplyIf,
type TxApplyResult,
type TxCUD,
type TxResult,
type TxWorkspaceEvent
} from '@hcengineering/core'
import { type Pipeline, type SessionContext } from '@hcengineering/server-core'
import { type Token } from '@hcengineering/server-token'
import { type BroadcastCall, type Session, type SessionRequest, type StatisticsElement } from './types'
import { type ClientSessionCtx, type Session, type SessionRequest, type StatisticsElement } from './types'
class SessionContextImpl implements SessionContext {
constructor (
readonly ctx: MeasureContext,
readonly userEmail: string,
readonly sessionId: string,
readonly admin: boolean | undefined,
readonly derived: SessionContext['derived']
) {}
with<T>(
name: string,
params: ParamsType,
op: (ctx: SessionOperationContext) => T | Promise<T>,
fullParams?: FullParamsType
): Promise<T> {
return this.ctx.with(
name,
params,
async (ctx) => await op(new SessionContextImpl(ctx, this.userEmail, this.sessionId, this.admin, this.derived)),
fullParams
)
}
}
/**
* @public
@ -52,7 +77,6 @@ export class ClientSession implements Session {
requests = new Map<string, SessionRequest>()
binaryMode: boolean = false
useCompression: boolean = true
useBroadcast: boolean = false
sessionId = ''
lastRequest = Date.now()
@ -62,7 +86,6 @@ export class ClientSession implements Session {
measures: { id: string, message: string, time: 0 }[] = []
constructor (
protected readonly broadcast: BroadcastCall,
protected readonly token: Token,
protected readonly _pipeline: Pipeline
) {}
@ -83,17 +106,22 @@ export class ClientSession implements Session {
return this._pipeline
}
async ping (): Promise<string> {
async ping (ctx: ClientSessionCtx): Promise<void> {
// console.log('ping')
this.lastRequest = Date.now()
return 'pong!'
await ctx.sendResponse('pong!')
}
async loadModel (ctx: MeasureContext, lastModelTx: Timestamp, hash?: string): Promise<Tx[] | LoadModelResponse> {
return await ctx.with('load-model', {}, async () => await this._pipeline.storage.loadModel(lastModelTx, hash))
async loadModel (ctx: ClientSessionCtx, lastModelTx: Timestamp, hash?: string): Promise<void> {
const result = await ctx.ctx.with(
'load-model',
{},
async () => await this._pipeline.storage.loadModel(lastModelTx, hash)
)
await ctx.sendResponse(result)
}
async getAccount (ctx: MeasureContext): Promise<Account> {
async getAccount (ctx: ClientSessionCtx): Promise<void> {
const account = await this._pipeline.modelDb.findAll(core.class.Account, { email: this.token.email })
if (account.length === 0 && this.token.extra?.admin === 'true') {
const systemAccount = await this._pipeline.modelDb.findAll(core.class.Account, {
@ -112,20 +140,24 @@ export class ClientSession implements Session {
},
this.token.email as Ref<Account>
)
const context = ctx as SessionContext
context.userEmail = this.token.email
context.admin = this.token.extra?.admin === 'true'
const context = new SessionContextImpl(
ctx.ctx,
this.token.email,
this.sessionId,
this.token.extra?.admin === 'true',
[]
)
await this._pipeline.tx(context, createTx)
const acc = TxProcessor.createDoc2Doc(createTx)
return acc
await ctx.sendResponse(acc)
} else {
return systemAccount[0]
await ctx.sendResponse(systemAccount[0])
}
}
return account[0]
await ctx.sendResponse(account[0])
}
async findAll<T extends Doc>(
async findAllRaw<T extends Doc>(
ctx: MeasureContext,
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
@ -134,89 +166,162 @@ export class ClientSession implements Session {
this.lastRequest = Date.now()
this.total.find++
this.current.find++
const context = ctx as SessionContext
context.userEmail = this.token.email
context.admin = this.token.extra?.admin === 'true'
const context = new SessionContextImpl(
ctx,
this.token.email,
this.sessionId,
this.token.extra?.admin === 'true',
[]
)
return await this._pipeline.findAll(context, _class, query, options)
}
async searchFulltext (ctx: MeasureContext, query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
this.lastRequest = Date.now()
const context = ctx as SessionContext
context.userEmail = this.token.email
context.admin = this.token.extra?.admin === 'true'
return await this._pipeline.searchFulltext(context, query, options)
async findAll<T extends Doc>(
ctx: ClientSessionCtx,
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<void> {
await ctx.sendResponse(await this.findAllRaw(ctx.ctx, _class, query, options))
}
async tx (ctx: MeasureContext, tx: Tx): Promise<TxResult> {
async searchFulltext (ctx: ClientSessionCtx, query: SearchQuery, options: SearchOptions): Promise<void> {
this.lastRequest = Date.now()
const context = new SessionContextImpl(
ctx.ctx,
this.token.email,
this.sessionId,
this.token.extra?.admin === 'true',
[]
)
await ctx.sendResponse(await this._pipeline.searchFulltext(context, query, options))
}
async txRaw (ctx: MeasureContext, tx: Tx): Promise<void> {
// Just do Tx and do not send anything
await this.tx({ ctx, sendResponse: async () => {}, send: async () => {}, sendError: async () => {} }, tx)
}
async tx (ctx: ClientSessionCtx, tx: Tx): Promise<void> {
this.lastRequest = Date.now()
this.total.tx++
this.current.tx++
const context = ctx as SessionContext
context.userEmail = this.token.email
context.admin = this.token.extra?.admin === 'true'
const [result, derived, target] = await this._pipeline.tx(context, tx)
const context = new SessionContextImpl(
ctx.ctx,
this.token.email,
this.sessionId,
this.token.extra?.admin === 'true',
[]
)
let shouldBroadcast = true
const result = await this._pipeline.tx(context, tx)
if (tx._class === core.class.TxApplyIf) {
const apply = tx as TxApplyIf
shouldBroadcast = apply.notify ?? true
// Send result immideately
await ctx.sendResponse(result)
// We need to combine all derived data and check if we need to send it
// Combine targets by sender
const toSendTarget = new Map<string, Tx[]>()
const getTxes = (key: string): Tx[] => {
let txes = toSendTarget.get(key)
if (txes === undefined) {
txes = []
toSendTarget.set(key, txes)
}
return txes
}
if (tx._class !== core.class.TxApplyIf) {
this.broadcast(null, this.token.workspace, { result: tx }, target)
}
if (shouldBroadcast) {
if (this.useBroadcast) {
if (derived.length > 250) {
const classes = new Set<Ref<Class<Doc>>>()
for (const dtx of derived) {
if (this._pipeline.storage.hierarchy.isDerived(dtx._class, core.class.TxCUD)) {
classes.add((dtx as TxCUD<Doc>).objectClass)
}
const etx = TxProcessor.extractTx(dtx)
if (this._pipeline.storage.hierarchy.isDerived(etx._class, core.class.TxCUD)) {
classes.add((etx as TxCUD<Doc>).objectClass)
}
}
console.log('Broadcasting bulk', derived.length)
const bevent = this.createBroadcastEvent(Array.from(classes))
if (tx._class === core.class.TxApplyIf) {
;(result as TxApplyResult).derived.push(bevent)
}
this.broadcast(null, this.token.workspace, { result: bevent }, target)
} else {
if (tx._class === core.class.TxApplyIf) {
;(result as TxApplyResult).derived.push(...derived)
}
// Let's send after our response will go out
setImmediate(() => {
while (derived.length > 0) {
const part = derived.splice(0, 250)
console.log('Broadcasting part', part.length, derived.length)
this.broadcast(null, this.token.workspace, { result: part }, target)
}
})
// Put current user as send target
toSendTarget.set(this.getUser(), [])
for (const txd of context.derived) {
if (txd.target === undefined) {
getTxes('').push(...txd.derived)
// Also add to all other targeted sends
for (const v of toSendTarget.values()) {
v.push(...txd.derived)
}
} else {
// Let's send after our response will go out
setImmediate(() => {
while (derived.length > 0) {
const part = derived.splice(0, 250)
this.broadcast(null, this.token.workspace, { result: part }, target)
}
})
for (const t of txd.target) {
getTxes(t).push(...txd.derived)
}
}
}
if (tx._class === core.class.TxApplyIf) {
const apply = tx as TxApplyIf
if (apply.extraNotify !== undefined && apply.extraNotify.length > 0) {
;(result as TxApplyResult).derived.push(this.createBroadcastEvent(apply.extraNotify))
const handleSend = async (derived: Tx[], target?: string, exclude?: string[]): Promise<void> => {
if (derived.length === 0) {
return
}
if (derived.length > 10000) {
await this.sendWithPart(derived, ctx, target, exclude)
} else {
// Let's send after our response will go out
console.log('Broadcasting', derived.length, derived.length)
await ctx.send(derived, target, exclude)
}
}
return result
const toSendAll = toSendTarget.get('') ?? []
toSendTarget.delete('')
// Send original Txes first.
if (tx._class === core.class.TxApplyIf && (result as TxApplyResult).success) {
const txMap = toIdMap((tx as TxApplyIf).txes as Tx[])
for (const [k, derived] of toSendTarget.entries()) {
// good, we could send apply transactions first.
const part1 = derived.filter((it) => txMap.has(it._id))
await ctx.send(part1, k, undefined)
toSendTarget.set(
k,
derived.filter((it) => !txMap.has(it._id))
)
}
}
if (tx._class !== core.class.TxApplyIf) {
for (const [k, derived] of toSendTarget.entries()) {
// good, we could send apply transactions first.
const part1 = derived.filter((it) => it._id === tx._id)
await ctx.send(part1, k, undefined)
toSendTarget.set(
k,
derived.filter((it) => it._id !== tx._id)
)
}
}
// Then send targeted and all other
for (const [k, v] of toSendTarget.entries()) {
void handleSend(v, k)
}
// Send all other except us.
void handleSend(toSendAll, undefined, Array.from(toSendTarget.keys()))
}
private async sendWithPart (
derived: Tx[],
ctx: ClientSessionCtx,
target: string | undefined,
exclude: string[] | undefined
): Promise<void> {
const classes = new Set<Ref<Class<Doc>>>()
for (const dtx of derived) {
if (this._pipeline.storage.hierarchy.isDerived(dtx._class, core.class.TxCUD)) {
classes.add((dtx as TxCUD<Doc>).objectClass)
}
const etx = TxProcessor.extractTx(dtx)
if (this._pipeline.storage.hierarchy.isDerived(etx._class, core.class.TxCUD)) {
classes.add((etx as TxCUD<Doc>).objectClass)
}
}
console.log('Broadcasting compact bulk', derived.length)
const bevent = this.createBroadcastEvent(Array.from(classes))
await ctx.send([bevent], target, exclude)
}
private createBroadcastEvent (classes: Ref<Class<Doc>>[]): TxWorkspaceEvent<BulkUpdateEvent> {

View File

@ -18,6 +18,7 @@ import core, {
TxFactory,
WorkspaceEvent,
generateId,
getWorkspaceId,
systemAccountEmail,
toWorkspaceString,
versionToString,
@ -28,14 +29,14 @@ import core, {
type TxWorkspaceEvent,
type WorkspaceId
} from '@hcengineering/core'
import { unknownError } from '@hcengineering/platform'
import { unknownError, type Status } from '@hcengineering/platform'
import { type HelloRequest, type HelloResponse, type Request, type Response } from '@hcengineering/rpc'
import type { Pipeline, SessionContext } from '@hcengineering/server-core'
import type { Pipeline } from '@hcengineering/server-core'
import { type Token } from '@hcengineering/server-token'
import {
LOGGING_ENABLED,
type BroadcastCall,
type ClientSessionCtx,
type ConnectionSocket,
type PipelineFactory,
type ServerFactory,
@ -67,10 +68,6 @@ function timeoutPromise (time: number): { promise: Promise<void>, cancelHandle:
}
}
function onNextTick (op: () => void): void {
setImmediate(op)
}
/**
* @public
*/
@ -95,7 +92,7 @@ class TSessionManager implements SessionManager {
constructor (
readonly ctx: MeasureContext,
readonly sessionFactory: (token: Token, pipeline: Pipeline, broadcast: BroadcastCall) => Session,
readonly sessionFactory: (token: Token, pipeline: Pipeline) => Session,
readonly timeouts: Timeouts
) {
this.checkInterval = setInterval(() => {
@ -175,7 +172,7 @@ class TSessionManager implements SessionManager {
this.ctx.warn('session hang, closing...', { wsId, user: s[1].session.getUser() })
// Force close workspace if only one client and it hang.
void this.close(s[1].socket, wsId)
void this.close(this.ctx, s[1].socket, wsId)
continue
}
if (diff > 20000 && diff < 60000 && this.ticks % 10 === 0) {
@ -218,7 +215,7 @@ class TSessionManager implements SessionManager {
}
createSession (token: Token, pipeline: Pipeline): Session {
return this.sessionFactory(token, pipeline, this.broadcast.bind(this))
return this.sessionFactory(token, pipeline)
}
@withContext('get-workspace-info')
@ -449,7 +446,7 @@ class TSessionManager implements SessionManager {
function send (): void {
for (const session of sessions) {
try {
sendResponse(ctx, session.session, session.socket, { result: tx })
void sendResponse(ctx, session.session, session.socket, { result: tx })
} catch (err: any) {
Analytics.handleError(err)
ctx.error('error during send', { error: err })
@ -459,7 +456,52 @@ class TSessionManager implements SessionManager {
}
if (sessions.length > 0) {
// We need to send broadcast after our client response so put it after all IO
onNextTick(send)
send()
} else {
ctx.end()
}
}
broadcast (
from: Session | null,
workspaceId: WorkspaceId,
resp: Tx[],
target: string | undefined,
exclude?: string[]
): void {
const workspace = this.workspaces.get(toWorkspaceString(workspaceId))
if (workspace === undefined) {
this.ctx.error('internal: cannot find sessions', {
workspaceId: workspaceId.name,
target,
userId: from?.getUser() ?? '$unknown'
})
return
}
if (workspace?.upgrade ?? false) {
return
}
if (LOGGING_ENABLED) {
this.ctx.info('server broadcasting to clients...', {
workspace: workspaceId.name,
count: workspace.sessions.size
})
}
const sessions = [...workspace.sessions.values()]
const ctx = this.ctx.newChild('📭 broadcast', {})
function send (): void {
for (const sessionRef of sessions) {
const tt = sessionRef.session.getUser()
if ((target === undefined && !(exclude ?? []).includes(tt)) || (target?.includes(tt) ?? false)) {
void sendResponse(ctx, sessionRef.session, sessionRef.socket, { result: resp })
}
}
ctx.end()
}
if (sessions.length > 0) {
// We need to send broadcast after our client response so put it after all IO
send()
} else {
ctx.end()
}
@ -521,28 +563,37 @@ class TSessionManager implements SessionManager {
)
)[0]
if (user === undefined) return
const status = (await session.findAll(ctx, core.class.UserStatus, { user: user._id }, { limit: 1 }))[0]
const status = (await session.findAllRaw(ctx, core.class.UserStatus, { user: user._id }, { limit: 1 }))[0]
const txFactory = new TxFactory(user._id, true)
if (status === undefined) {
const tx = txFactory.createTxCreateDoc(core.class.UserStatus, core.space.Space, {
online,
user: user._id
})
await session.tx(ctx, tx)
await session.txRaw(ctx, tx)
} else if (status.online !== online) {
const tx = txFactory.createTxUpdateDoc(status._class, status.space, status._id, {
online
})
await session.tx(ctx, tx)
await session.txRaw(ctx, tx)
}
} catch {}
}
async close (ws: ConnectionSocket, wsid: string): Promise<void> {
async close (ctx: MeasureContext, ws: ConnectionSocket, wsid: string): Promise<void> {
const workspace = this.workspaces.get(wsid)
const sessionRef = this.sessions.get(ws.id)
if (sessionRef !== undefined) {
ctx.info('bye happen', {
workspace: workspace?.workspaceName,
user: sessionRef.session.getUser(),
binary: sessionRef.session.binaryMode,
compression: sessionRef.session.useCompression,
totalTime: Date.now() - sessionRef.session.createTime,
workspaceUsers: workspace?.sessions?.size,
totalUsers: this.sessions.size
})
this.sessions.delete(ws.id)
if (workspace !== undefined) {
workspace.sessions.delete(sessionRef.session.sessionId)
@ -705,57 +756,16 @@ class TSessionManager implements SessionManager {
}
}
broadcast (from: Session | null, workspaceId: WorkspaceId, resp: Response<any>, target?: string[]): void {
const workspace = this.workspaces.get(toWorkspaceString(workspaceId))
if (workspace === undefined) {
this.ctx.error('internal: cannot find sessions', {
workspaceId: workspaceId.name,
target,
userId: from?.getUser() ?? '$unknown'
})
return
}
if (workspace?.upgrade ?? false) {
return
}
if (LOGGING_ENABLED) {
this.ctx.info('server broadcasting to clients...', {
workspace: workspaceId.name,
count: workspace.sessions.size
})
}
const sessions = [...workspace.sessions.values()]
const ctx = this.ctx.newChild('📭 broadcast', {})
function send (): void {
for (const sessionRef of sessions) {
if (sessionRef !== undefined && sessionRef.session.sessionId !== from?.sessionId) {
if (target === undefined || target.includes(sessionRef.session.getUser())) {
sendResponse(ctx, sessionRef.session, sessionRef.socket, resp)
}
}
}
ctx.end()
}
if (sessions.length > 0) {
// We need to send broadcast after our client response so put it after all IO
onNextTick(send)
} else {
ctx.end()
}
}
async handleRequest<S extends Session>(
handleRequest<S extends Session>(
requestCtx: MeasureContext,
service: S,
ws: ConnectionSocket,
request: Request<any>,
workspace: string
): Promise<Response<any> | undefined> {
): void {
const userCtx = requestCtx.newChild('📞 client', {
workspace: '🧲 ' + workspace
}) as SessionContext
userCtx.sessionId = service.sessionInstanceId ?? ''
})
// Calculate total number of clients
const reqId = generateId()
@ -763,7 +773,7 @@ class TSessionManager implements SessionManager {
const st = Date.now()
try {
const backupMode = 'loadChunk' in service
return await userCtx.with(`🧭 ${backupMode ? 'handleBackup' : 'handleRequest'}`, {}, async (ctx) => {
void userCtx.with(`🧭 ${backupMode ? 'handleBackup' : 'handleRequest'}`, {}, async (ctx) => {
if (request.time != null) {
const delta = Date.now() - request.time
userCtx.measure('receive msg', delta)
@ -790,7 +800,6 @@ class TSessionManager implements SessionManager {
const hello = request as HelloRequest
service.binaryMode = hello.binary ?? false
service.useCompression = hello.compression ?? false
service.useBroadcast = hello.broadcast ?? false
if (LOGGING_ENABLED) {
ctx.info('hello happen', {
@ -818,8 +827,36 @@ class TSessionManager implements SessionManager {
await ws.send(ctx, helloResponse, false, false)
return
}
const opContext = (ctx: MeasureContext): ClientSessionCtx => ({
sendResponse: async (msg) => {
await sendResponse(ctx, service, ws, {
id: request.id,
result: msg,
time: Date.now() - st,
bfst: Date.now(),
queue: service.requests.size
})
userCtx.end()
},
ctx,
send: async (msg, target, exclude) => {
this.broadcast(service, getWorkspaceId(workspace), msg, target, exclude)
},
sendError: async (msg, error: Status) => {
await sendResponse(ctx, service, ws, {
id: request.id,
result: msg,
error,
time: Date.now() - st,
bfst: Date.now(),
queue: service.requests.size
})
}
})
if (request.method === 'measure' || request.method === 'measure-done') {
return await this.handleMeasure<S>(service, request, ctx, ws)
await this.handleMeasure<S>(service, request, opContext(ctx))
return
}
service.requests.set(reqId, {
id: reqId,
@ -835,28 +872,24 @@ class TSessionManager implements SessionManager {
try {
const params = [...request.params]
const result =
service.measureCtx?.ctx !== undefined
? await f.apply(service, [service.measureCtx?.ctx, ...params])
: await ctx.with('🧨 process', {}, async (callTx) => f.apply(service, [callTx, ...params]))
return {
id: request.id,
result,
time: Date.now() - st,
bfst: Date.now(),
queue: service.requests.size
}
service.measureCtx?.ctx !== undefined
? await f.apply(service, [opContext(service.measureCtx?.ctx), ...params])
: await ctx.with('🧨 process', {}, async (callTx) => f.apply(service, [opContext(callTx), ...params]))
} catch (err: any) {
Analytics.handleError(err)
if (LOGGING_ENABLED) {
this.ctx.error('error handle request', { error: err, request })
}
return {
id: request.id,
error: unknownError(err),
result: JSON.parse(JSON.stringify(err?.stack))
}
await ws.send(
ctx,
{
id: request.id,
error: unknownError(err),
result: JSON.parse(JSON.stringify(err?.stack))
},
service.binaryMode,
service.useCompression
)
}
})
} finally {
@ -868,12 +901,11 @@ class TSessionManager implements SessionManager {
private async handleMeasure<S extends Session>(
service: S,
request: Request<any[]>,
ctx: MeasureContext,
ws: ConnectionSocket
): Promise<Response<any> | undefined> {
ctx: ClientSessionCtx
): Promise<void> {
let serverTime = 0
if (request.method === 'measure') {
service.measureCtx = { ctx: ctx.newChild('📶 ' + request.params[0], {}), time: Date.now() }
service.measureCtx = { ctx: ctx.ctx.newChild('📶 ' + request.params[0], {}), time: Date.now() }
} else {
if (service.measureCtx !== undefined) {
serverTime = Date.now() - service.measureCtx.time
@ -881,17 +913,13 @@ class TSessionManager implements SessionManager {
}
}
try {
return { id: request.id, result: request.method === 'measure' ? 'started' : serverTime }
await ctx.sendResponse(request.method === 'measure' ? 'started' : serverTime)
} catch (err: any) {
Analytics.handleError(err)
if (LOGGING_ENABLED) {
ctx.error('error handle measure', { error: err, request })
}
return {
id: request.id,
error: unknownError(err),
result: JSON.parse(JSON.stringify(err?.stack))
ctx.ctx.error('error handle measure', { error: err, request })
}
await ctx.sendError(JSON.parse(JSON.stringify(err?.stack)), unknownError(err))
}
}
}
@ -904,7 +932,7 @@ export function start (
opt: {
port: number
pipelineFactory: PipelineFactory
sessionFactory: (token: Token, pipeline: Pipeline, broadcast: BroadcastCall) => Session
sessionFactory: (token: Token, pipeline: Pipeline) => Session
productId: string
serverFactory: ServerFactory
enableCompression?: boolean
@ -917,7 +945,9 @@ export function start (
})
return opt.serverFactory(
sessions,
(rctx, service, ws, msg, workspace) => sessions.handleRequest(rctx, service, ws, msg, workspace),
(rctx, service, ws, msg, workspace) => {
sessions.handleRequest(rctx, service, ws, msg, workspace)
},
ctx,
opt.pipelineFactory,
opt.port,

View File

@ -255,7 +255,7 @@ export function startHttpServer (
doSessionOp(webSocketData, (s) => {
if (!(s.session.workspaceClosed ?? false)) {
// remove session after 1seconds, give a time to reconnect.
void sessions.close(cs, toWorkspaceString(token.workspace))
void sessions.close(ctx, cs, toWorkspaceString(token.workspace))
}
})
})
@ -352,7 +352,7 @@ function createWebsocketClientSocket (
}
const smsg = serialize(msg, binary)
while (ws.bufferedAmount > 128 && ws.readyState === ws.OPEN) {
while (ws.bufferedAmount > 16 * 1024 && ws.readyState === ws.OPEN) {
await new Promise<void>((resolve) => {
setImmediate(resolve)
})

View File

@ -177,7 +177,11 @@ export function startUWebsocketServer (
doSessionOp(data, (s) => {
if (!(s.session.workspaceClosed ?? false)) {
// remove session after 1seconds, give a time to reconnect.
void sessions.close(data.connectionSocket as ConnectionSocket, toWorkspaceString(data.payload.workspace))
void sessions.close(
ctx,
data.connectionSocket as ConnectionSocket,
toWorkspaceString(data.payload.workspace)
)
}
})
}

View File

@ -7,7 +7,6 @@ import {
type MeasureContext,
type Ref,
type Tx,
type TxResult,
type WorkspaceId,
type WorkspaceIdWithUrl
} from '@hcengineering/core'
@ -31,6 +30,17 @@ export interface StatisticsElement {
find: number
tx: number
}
export interface ClientSessionCtx {
ctx: MeasureContext
sendResponse: (msg: any) => Promise<void>
sendError: (msg: any, error: any) => Promise<void>
// target === undefined, send to all except exclude
// target !== undefined, send to selected target only, exclude is not used.
send: (msg: Tx[], target?: string, exclude?: string[]) => Promise<void>
}
/**
* @public
*/
@ -38,14 +48,21 @@ export interface Session {
createTime: number
getUser: () => string
pipeline: () => Pipeline
ping: () => Promise<string>
ping: (ctx: ClientSessionCtx) => Promise<void>
findAll: <T extends Doc>(
ctx: ClientSessionCtx,
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
) => Promise<void>
findAllRaw: <T extends Doc>(
ctx: MeasureContext,
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
) => Promise<FindResult<T>>
tx: (ctx: MeasureContext, tx: Tx) => Promise<TxResult>
tx: (ctx: ClientSessionCtx, tx: Tx) => Promise<void>
txRaw: (ctx: MeasureContext, tx: Tx) => Promise<void>
// Session restore information
sessionId: string
@ -56,8 +73,6 @@ export interface Session {
binaryMode: boolean
useCompression: boolean
useBroadcast: boolean
total: StatisticsElement
current: StatisticsElement
mins5: StatisticsElement
@ -71,16 +86,6 @@ export interface Session {
getMode: () => string
}
/**
* @public
*/
export type BroadcastCall = (
from: Session | null,
workspaceId: WorkspaceId,
resp: Response<any>,
target?: string[]
) => void
/**
* @public
*/
@ -161,7 +166,7 @@ export interface SessionManager {
broadcastAll: (workspace: Workspace, tx: Tx[], targets?: string[]) => void
close: (ws: ConnectionSocket, workspaceId: string) => Promise<void>
close: (ctx: MeasureContext, ws: ConnectionSocket, workspaceId: string) => Promise<void>
closeAll: (
wsId: string,
@ -175,8 +180,6 @@ export interface SessionManager {
closeWorkspaces: (ctx: MeasureContext) => Promise<void>
broadcast: (from: Session | null, workspaceId: WorkspaceId, resp: Response<any>, target?: string[]) => void
scheduleMaintenance: (timeMinutes: number) => void
}
@ -189,8 +192,7 @@ export type HandleRequestFunction = <S extends Session>(
ws: ConnectionSocket,
msg: Request<any>,
workspaceId: string
) => Promise<Response<any> | undefined>
) => void
/**
* @public
*/

View File

@ -35,25 +35,21 @@ export function processRequest (
handleRequest: HandleRequestFunction
): void {
const request = readRequest(buff, session.binaryMode)
void handleRequest(context, session, cs, request, workspaceId).then((resp) => {
if (resp !== undefined) {
void handleSend(context, cs, resp, 32 * 1024, session.binaryMode, session.useCompression)
}
})
handleRequest(context, session, cs, request, workspaceId)
}
export function sendResponse (
export async function sendResponse (
ctx: MeasureContext,
session: Session,
socket: ConnectionSocket,
resp: Response<any>
): void {
void handleSend(ctx, socket, resp, 32 * 1024, session.binaryMode, session.useCompression)
): Promise<void> {
await handleSend(ctx, socket, resp, 32 * 1024, session.binaryMode, session.useCompression)
}
function waitNextTick (): Promise<void> | undefined {
return new Promise<void>((resolve) => {
setImmediate(resolve)
setTimeout(resolve)
})
}
export async function handleSend (