diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 77a9cff5b9..4046b6da08 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -3,6 +3,7 @@ lockfileVersion: 5.3 specifiers: '@microsoft/api-extractor': ^7.18.4 '@rush-temp/core': file:./projects/core.tgz + '@rush-temp/dev-server': file:./projects/dev-server.tgz '@rush-temp/dev-storage': file:./projects/dev-storage.tgz '@rush-temp/platform': file:./projects/platform.tgz '@rush-temp/platform-rig': file:./projects/platform-rig.tgz @@ -28,6 +29,7 @@ specifiers: dependencies: '@microsoft/api-extractor': 7.18.4 '@rush-temp/core': file:projects/core.tgz_6c259fadfeb3a4b20890aefe87070b8b + '@rush-temp/dev-server': file:projects/dev-server.tgz_6c259fadfeb3a4b20890aefe87070b8b '@rush-temp/dev-storage': file:projects/dev-storage.tgz_6c259fadfeb3a4b20890aefe87070b8b '@rush-temp/platform': file:projects/platform.tgz_6c259fadfeb3a4b20890aefe87070b8b '@rush-temp/platform-rig': file:projects/platform-rig.tgz_6ab28797e7a22071465f7d680ae81ae5 @@ -5201,8 +5203,26 @@ packages: - typescript dev: false + file:projects/dev-server.tgz_6c259fadfeb3a4b20890aefe87070b8b: + resolution: {integrity: sha512-wWD3XTjUjOM0zuwKN3Y8nuzCmWTPR0m554z+Vun/V20HOJ3ykNCe+oYJUIbSgvZfL647XDkomAVGZtCMl7qu/w==, tarball: file:projects/dev-server.tgz} + id: file:projects/dev-server.tgz + name: '@rush-temp/dev-server' + version: 0.0.0 + dependencies: + '@types/heft-jest': 1.0.2 + '@typescript-eslint/eslint-plugin': 4.28.5_a8e83fcad666e1ba86be4b2e27a20aea + eslint: 7.32.0 + eslint-plugin-import: 2.23.4_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 4.3.1 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - supports-color + - typescript + dev: false + file:projects/dev-storage.tgz_6c259fadfeb3a4b20890aefe87070b8b: - resolution: {integrity: sha512-Jf/W4dvCH5kUvK2ItxuijWJM4IGZf8Hi1noFrzk9PLL+2WNrD5t7sPB9c/lv4KzKEhX5gTV4ZkLBHNyD+lo2oQ==, tarball: file:projects/dev-storage.tgz} + resolution: {integrity: sha512-upE4u9M1Pna5G71wXTMBeMd+pGiZbjjyhpXfHZR+lgSthSPnC7avk/Ozb7L2gL87zaqK4ZhKaiI+WNg6FeGQDg==, tarball: file:projects/dev-storage.tgz} id: file:projects/dev-storage.tgz name: '@rush-temp/dev-storage' version: 0.0.0 diff --git a/dev/server/.eslintrc.js b/dev/server/.eslintrc.js new file mode 100644 index 0000000000..89f8151bd4 --- /dev/null +++ b/dev/server/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + project: './tsconfig.json' + } +} \ No newline at end of file diff --git a/dev/server/config/rig.json b/dev/server/config/rig.json new file mode 100644 index 0000000000..af1257a896 --- /dev/null +++ b/dev/server/config/rig.json @@ -0,0 +1,18 @@ +// The "rig.json" file directs tools to look for their config files in an external package. +// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + /** + * (Required) The name of the rig package to inherit from. + * It should be an NPM package name with the "-rig" suffix. + */ + "rigPackageName": "@anticrm/platform-rig" + + /** + * (Optional) Selects a config profile from the rig package. The name must consist of + * lowercase alphanumeric words separated by hyphens, for example "sample-profile". + * If omitted, then the "default" profile will be used." + */ + // "rigProfile": "your-profile-name" +} diff --git a/dev/server/package.json b/dev/server/package.json new file mode 100644 index 0000000000..5866fe2d91 --- /dev/null +++ b/dev/server/package.json @@ -0,0 +1,30 @@ +{ + "name": "@anticrm/dev-server", + "version": "0.6.0", + "main": "lib/index.js", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "heft build", + "build:docs": "api-extractor run --local", + "test": "jest", + "lint": "ts-standard src", + "lint:fix": "eslint --fix ./src", + "format": "prettier --write 'src/**/*.{ts*,js*,yml}' && ts-standard --fix src" + }, + "devDependencies": { + "@anticrm/platform-rig":"~0.6.0", + "@types/heft-jest":"^1.0.2", + "@typescript-eslint/eslint-plugin":"4", + "eslint-plugin-import":"2", + "eslint-plugin-promise":"4", + "eslint-plugin-node":"11", + "eslint":"^7.32.0" + }, + "dependencies": { + "@anticrm/dev-storage": "~0.6.0", + "@anticrm/server-ws": "~0.6.0", + "@anticrm/core": "~0.6.0", + "@anticrm/platform": "~0.6.0" + } +} diff --git a/dev/server/src/index.ts b/dev/server/src/index.ts new file mode 100644 index 0000000000..65ead4648c --- /dev/null +++ b/dev/server/src/index.ts @@ -0,0 +1,17 @@ +// +// 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. +// + +export { start } from './server' diff --git a/dev/server/src/server.ts b/dev/server/src/server.ts new file mode 100644 index 0000000000..7eecf22ac4 --- /dev/null +++ b/dev/server/src/server.ts @@ -0,0 +1,27 @@ +// +// 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 as startJsonRpc } from '@anticrm/server-ws' +import { createStorage } from '@anticrm/dev-storage' +import { DevSession } from './session' + +/** + * @public + */ +export async function start (): Promise { + const storage = await createStorage() + startJsonRpc(server => new DevSession(server, storage), 3333) +} diff --git a/dev/server/src/session.ts b/dev/server/src/session.ts new file mode 100644 index 0000000000..80660e708a --- /dev/null +++ b/dev/server/src/session.ts @@ -0,0 +1,37 @@ +// +// 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 type { Class, Doc, DocumentQuery, FindOptions, FindResult, Ref, Storage, Tx } from '@anticrm/core' +import { JsonRpcServer } from '@anticrm/server-ws' +import type { ServerStorage } from '@anticrm/dev-storage' + +export class DevSession implements Storage { + constructor ( + private readonly server: JsonRpcServer, + private readonly storage: ServerStorage + ) {} + + async findAll (_class: Ref>, query: DocumentQuery, options?: FindOptions): Promise> { + return await this.storage.findAll(_class, query, options) + } + + async tx (tx: Tx): Promise { + const derived = await this.storage.tx(tx) + for (const tx of derived) { + this.server.broadcast(this, { result: tx }) + } + } +} diff --git a/dev/server/tsconfig.json b/dev/server/tsconfig.json new file mode 100644 index 0000000000..aeb0517b13 --- /dev/null +++ b/dev/server/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib" + } +} \ No newline at end of file diff --git a/dev/storage/src/index.ts b/dev/storage/src/index.ts index 6be884967e..f421063726 100644 --- a/dev/storage/src/index.ts +++ b/dev/storage/src/index.ts @@ -14,4 +14,4 @@ // limitations under the License. // -export { createStorage } from './storage' +export { createStorage, ServerStorage } from './storage' diff --git a/dev/storage/src/storage.ts b/dev/storage/src/storage.ts index e9c8fb0a73..c2e4be8f34 100644 --- a/dev/storage/src/storage.ts +++ b/dev/storage/src/storage.ts @@ -15,7 +15,6 @@ import type { Tx, - Storage, Ref, Doc, Class, @@ -26,18 +25,30 @@ import type { import { getResource } from '@anticrm/platform' import core, { ModelDb, TxDb, Hierarchy, DOMAIN_TX, DefaultTxFactory } from '@anticrm/core' +/** + * @public + */ +export interface ServerStorage { + findAll: ( + _class: Ref>, + query: DocumentQuery, + options?: FindOptions + ) => Promise> + tx: (tx: Tx) => Promise +} + async function getModel (): Promise { return import('./model.tx.json') as unknown as Tx[] } -class DevStorage implements Storage { +class DevStorage implements ServerStorage { private readonly txFactory: DefaultTxFactory constructor ( private readonly hierarchy: Hierarchy, private readonly txdb: TxDb, - private readonly modeldb: ModelDb, - private readonly handler: (tx: Tx) => void) { + private readonly modeldb: ModelDb + ) { this.txFactory = new DefaultTxFactory(core.account.System) } @@ -51,7 +62,7 @@ class DevStorage implements Storage { return await this.modeldb.findAll(_class, query, options) } - async tx (tx: Tx): Promise { + async tx (tx: Tx): Promise { if (tx.objectSpace === core.space.Model) { this.hierarchy.tx(tx) } @@ -59,15 +70,18 @@ class DevStorage implements Storage { // invoke triggers const triggers = this.hierarchy.getClass(tx.objectClass).triggers if (triggers !== undefined) { + const derived: Tx[] = [] for (const trigger of triggers) { const impl = await getResource(trigger) const txes = await impl(tx, this.txFactory) + derived.push(...txes) for (const tx of txes) { await Promise.all([this.modeldb.tx(tx), this.txdb.tx(tx)]) - this.handler(tx) } } + return derived } + return [] } } @@ -76,7 +90,7 @@ class DevStorage implements Storage { * @param handler - * @returns */ -export async function createStorage (handler: (tx: Tx) => void): Promise { +export async function createStorage (): Promise { const txes = await getModel() const hierarchy = new Hierarchy() @@ -88,5 +102,5 @@ export async function createStorage (handler: (tx: Tx) => void): Promise (service: S, ws: WebSocket, msg: string): Promis /** * @public - * @param serviceFactory - + */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Session {} + +/** + * @public + */ +export interface JsonRpcServer { + broadcast: (from: Session, resp: Response) => void +} + +/** + * @public + * @param sessionFactory - * @param port - * @param host - */ -export function start (serviceFactory: () => Promise, port: number, host?: string): void { +export function start (sessionFactory: (server: JsonRpcServer) => Session, port: number, host?: string): void { // console.log('starting server on port ' + port + '...') // console.log('host: ' + host) - const connections: S[] = [] + const sessions: [Session, WebSocket][] = [] + + const jsonServer: JsonRpcServer = { + broadcast (from: Session, resp: Response<[]>) { + const msg = serialize(resp) + for (const session of sessions) { + if (session[0] !== from) { session[1].send(msg) } + } + } + } const wss = new Server({ noServer: true }) // eslint-disable-next-line @typescript-eslint/no-misused-promises - wss.on('connection', async (ws: WebSocket, request: any, token: _Token) => { - const service = await serviceFactory() - connections.push(service) + wss.on('connection', (ws: WebSocket, request: any, token: _Token) => { + const service = sessionFactory(jsonServer) + sessions.push([service, ws]) // eslint-disable-next-line @typescript-eslint/no-misused-promises ws.on('message', async (msg: string) => await handleRequest(service, ws, msg)) })