mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-11 12:57:59 +00:00
Allow Use client from client-resources from NodeJS (#545)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
420fc93967
commit
beecb23c79
@ -112,7 +112,6 @@ specifiers:
|
||||
'@types/minio': ^7.0.10
|
||||
'@types/toposort': ^2.0.3
|
||||
'@types/uuid': ^8.3.1
|
||||
'@types/ws': ^7.4.7
|
||||
'@typescript-eslint/eslint-plugin': ^5.4.0
|
||||
autoprefixer: ^10.2.6
|
||||
commander: ^8.1.0
|
||||
@ -272,7 +271,6 @@ dependencies:
|
||||
'@types/minio': 7.0.10
|
||||
'@types/toposort': 2.0.3
|
||||
'@types/uuid': 8.3.1
|
||||
'@types/ws': 7.4.7
|
||||
'@typescript-eslint/eslint-plugin': 5.4.0_eslint@7.32.0+typescript@4.4.3
|
||||
autoprefixer: 10.3.7_postcss@8.3.9
|
||||
commander: 8.2.0
|
||||
@ -1816,6 +1814,12 @@ packages:
|
||||
'@types/node': 16.10.3
|
||||
dev: false
|
||||
|
||||
/@types/ws/8.2.1:
|
||||
resolution: {integrity: sha512-SqQ+LhVZaJi7c7sYVkjWALDigi/Wy7h7Iu72gkQp8Y8OWw/DddEVBrTSKu86pQftV2+Gm8lYM61hadPKqyaIeg==}
|
||||
dependencies:
|
||||
'@types/node': 16.10.3
|
||||
dev: false
|
||||
|
||||
/@types/yargs-parser/20.2.1:
|
||||
resolution: {integrity: sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==}
|
||||
dev: false
|
||||
@ -9733,7 +9737,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/chunter.tgz:
|
||||
resolution: {integrity: sha512-D/Ar+jZ9VwOidVBlO8Kh18DjmojP9ILhHojkN4NsoZ9t1ZoJ8/qiiq3TrEm81KHDqb1NyOm+Bcr8TlhkjchB2g==, tarball: file:projects/chunter.tgz}
|
||||
resolution: {integrity: sha512-xOX07B2Da1f9nijib0VIqW0GNHn3xNwZI3vr7djeIXyvjLCOIXxTATPF0J6q36/AApKTG5SQyRpiFQ1InU9pHA==, tarball: file:projects/chunter.tgz}
|
||||
name: '@rush-temp/chunter'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -9799,7 +9803,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/contact-resources.tgz_476f694f64637160ae71e12ff57815b9:
|
||||
resolution: {integrity: sha512-7wNt7czMMNeogqSzQY1zCG/iOrTKKY6R52gyFcBxxBGUJtRWj7b7s8JtJ137tP0jZTcLlzhd99utKewQ/BkxQQ==, tarball: file:projects/contact-resources.tgz}
|
||||
resolution: {integrity: sha512-I563510HapGECL9TC1qo8OQsLhKaMl/ttdIWN6vL+XOPcJY+YnmLrMmo0UNMcbCo8liNfvrNmjgbnjpSdl2ikg==, tarball: file:projects/contact-resources.tgz}
|
||||
id: file:projects/contact-resources.tgz
|
||||
name: '@rush-temp/contact-resources'
|
||||
version: 0.0.0
|
||||
@ -10250,7 +10254,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-chunter.tgz_typescript@4.4.3:
|
||||
resolution: {integrity: sha512-iB/GGhZOSuGpbKDb2jrxcj5dvndn1Hy61BV3dSxrZf/lBCoaA4SZfd66gWsdBsv17AOMmaWUvFJxxR5S6J2yfg==, tarball: file:projects/model-chunter.tgz}
|
||||
resolution: {integrity: sha512-NoUF0E2WHE7tfG42VyE11efk61UzUHi0Y2pv4QziQVN6uskIzrnS3xUbSRPyEYff9HvffxDJEB5+Ac73rtuPZQ==, tarball: file:projects/model-chunter.tgz}
|
||||
id: file:projects/model-chunter.tgz
|
||||
name: '@rush-temp/model-chunter'
|
||||
version: 0.0.0
|
||||
@ -10271,7 +10275,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-contact.tgz_typescript@4.4.3:
|
||||
resolution: {integrity: sha512-85D68dBZHoPJbRnEythE2CGzp1qbwJ3ynLnBaf3TtNvlgukH1NT15fp2zD8W/4RK9QIscv65yO9gAjTeqjxHUw==, tarball: file:projects/model-contact.tgz}
|
||||
resolution: {integrity: sha512-qZPBOQC2Oi/Qxpjt91OM1tGBPwShe2SLIHKmDNGSsk79zzdmW3+ZDrge4VVrK/ngc5P2fJQwpQRXgOkNgz2vnA==, tarball: file:projects/model-contact.tgz}
|
||||
id: file:projects/model-contact.tgz
|
||||
name: '@rush-temp/model-contact'
|
||||
version: 0.0.0
|
||||
@ -10334,7 +10338,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-recruit.tgz_typescript@4.4.3:
|
||||
resolution: {integrity: sha512-Ja9/9aH8v2JSQzsEtuxG7DFOOB405EoASN2ex9S31NYhYqgIqM8ZaRi5McthYz1e4gkyWFWtOCTHbBccNNOvLg==, tarball: file:projects/model-recruit.tgz}
|
||||
resolution: {integrity: sha512-LgqXtVecQ6ZdlAb4ufvWI+kDIeeMFtm/5mXvUXBbGsmg07rCa4tsqfm9uwecj6TvpaPYC3mlyYv/N316VeKeGA==, tarball: file:projects/model-recruit.tgz}
|
||||
id: file:projects/model-recruit.tgz
|
||||
name: '@rush-temp/model-recruit'
|
||||
version: 0.0.0
|
||||
@ -10479,7 +10483,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-task.tgz_typescript@4.4.3:
|
||||
resolution: {integrity: sha512-AjQvZTFE3GR6hQkU4jFA9M0bJhQhiiBIaZdW+2ALaj3xwAzolD6eSStCUWVWIZgj9uJTSfT8oGTiIw+iISB61Q==, tarball: file:projects/model-task.tgz}
|
||||
resolution: {integrity: sha512-WzEdSzLqN2Fj+TXMdy9pLEsBPMjqlz6eT2nQLwwQgRyPcT6S6qAbEgqTiPZmk10vW2/qA5X6DDAf82yC/8zVJA==, tarball: file:projects/model-task.tgz}
|
||||
id: file:projects/model-task.tgz
|
||||
name: '@rush-temp/model-task'
|
||||
version: 0.0.0
|
||||
@ -11333,7 +11337,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/tool.tgz:
|
||||
resolution: {integrity: sha512-FFa7jbNSC9W0iRlRnNLYvlhWQUYLgCBbgIFsG9Bw0aWy6s8h1EGfJi5cC4b4z31MprdgXh9egR/cpi29fs7bHg==, tarball: file:projects/tool.tgz}
|
||||
resolution: {integrity: sha512-klTUXsaa1dlq4wR1u+u3vJ8Vh8Z+StBadUgTZ1r0aqYjVXx9npJFqF+PHucl2LbZ0gDeHqC7crbxfGuui9na1Q==, tarball: file:projects/tool.tgz}
|
||||
name: '@rush-temp/tool'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -11341,6 +11345,7 @@ packages:
|
||||
'@types/heft-jest': 1.0.2
|
||||
'@types/minio': 7.0.10
|
||||
'@types/node': 16.10.3
|
||||
'@types/ws': 8.2.1
|
||||
'@typescript-eslint/eslint-plugin': 5.4.0_87dbf04088b125598d0271706532eaf3
|
||||
'@typescript-eslint/parser': 5.4.0_eslint@7.32.0+typescript@4.4.3
|
||||
commander: 8.2.0
|
||||
@ -11356,10 +11361,13 @@ packages:
|
||||
prettier: 2.4.1
|
||||
ts-node: 10.2.1_8304ecd715830f7c190b4d1dea90b100
|
||||
typescript: 4.4.3
|
||||
ws: 8.2.3
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- '@swc/wasm'
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
file:projects/ui.tgz_476f694f64637160ae71e12ff57815b9:
|
||||
|
@ -13,14 +13,14 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Tx, Storage, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxHander, ServerStorage, TxResult } from '@anticrm/core'
|
||||
import type { Class, ClientConnection, Doc, DocumentQuery, FindOptions, FindResult, Ref, ServerStorage, Tx, TxHander, TxResult } from '@anticrm/core'
|
||||
import { DOMAIN_TX } from '@anticrm/core'
|
||||
import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage'
|
||||
import { createServerStorage, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
|
||||
import { protoDeserialize, protoSerialize } from '@anticrm/platform'
|
||||
import type { DbConfiguration } from '@anticrm/server-core'
|
||||
import { protoSerialize, protoDeserialize } from '@anticrm/platform'
|
||||
import { createServerStorage, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
|
||||
|
||||
class ServerStorageWrapper implements Storage {
|
||||
class ServerStorageWrapper implements ClientConnection {
|
||||
constructor (private readonly storage: ServerStorage, private readonly handler: TxHander) {}
|
||||
|
||||
findAll <T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<FindResult<T>> {
|
||||
@ -34,6 +34,8 @@ class ServerStorageWrapper implements Storage {
|
||||
for (const tx of derived) { this.handler(tx) }
|
||||
return result
|
||||
}
|
||||
|
||||
async close (): Promise<void> {}
|
||||
}
|
||||
|
||||
class NullFullTextAdapter implements FullTextAdapter {
|
||||
@ -54,7 +56,7 @@ async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
|
||||
return new NullFullTextAdapter()
|
||||
}
|
||||
|
||||
export async function connect (handler: (tx: Tx) => void): Promise<Storage> {
|
||||
export async function connect (handler: (tx: Tx) => void): Promise<ClientConnection> {
|
||||
const conf: DbConfiguration = {
|
||||
domains: {
|
||||
[DOMAIN_TX]: 'InMemoryTx'
|
||||
|
@ -32,20 +32,23 @@
|
||||
"eslint-config-standard-with-typescript": "^21.0.1",
|
||||
"prettier": "^2.4.1",
|
||||
"@rushstack/heft": "^0.41.1",
|
||||
"typescript": "^4.3.5"
|
||||
"typescript": "^4.3.5",
|
||||
"@types/ws": "^8.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"mongodb": "^4.1.1",
|
||||
"commander": "^8.1.0",
|
||||
"@anticrm/account": "~0.6.0",
|
||||
"jwt-simple": "^0.5.6",
|
||||
"@anticrm/contrib": "~0.6.0",
|
||||
"@anticrm/core": "~0.6.11",
|
||||
"@anticrm/contact": "~0.6.2",
|
||||
"@anticrm/workspace": "~0.6.0",
|
||||
"minio": "^7.0.19",
|
||||
"@anticrm/model-all": "~0.6.0",
|
||||
"@anticrm/model-telegram": "~0.6.0",
|
||||
"@anticrm/telegram": "~0.6.0"
|
||||
"@anticrm/telegram": "~0.6.0",
|
||||
"@anticrm/client-resources": "~0.6.4",
|
||||
"ws": "^8.2.0",
|
||||
"@anticrm/client": "~0.6.1",
|
||||
"@anticrm/platform": "~0.6.5"
|
||||
}
|
||||
}
|
||||
|
24
dev/tool/src/connect.ts
Normal file
24
dev/tool/src/connect.ts
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
import client from '@anticrm/client'
|
||||
import clientResources from '@anticrm/client-resources'
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import { setMetadata } from '@anticrm/platform'
|
||||
import { encode } from 'jwt-simple'
|
||||
|
||||
// eslint-disable-next-line
|
||||
const WebSocket = require('ws')
|
||||
|
||||
export async function connect (transactorUrl: string, workspace: string): Promise<{ connection: TxOperations, close: () => Promise<void>}> {
|
||||
console.log('connecting to transactor...')
|
||||
const token = encode({ email: 'anticrm@hc.engineering', workspace }, 'secret')
|
||||
|
||||
// We need to override default factory with 'ws' one.
|
||||
setMetadata(client.metadata.ClientSocketFactory, (url) => new WebSocket(url))
|
||||
const connection = await (await clientResources()).function.GetClient(token, transactorUrl)
|
||||
return {
|
||||
connection: new TxOperations(connection, core.account.System),
|
||||
close: async () => {
|
||||
await connection.close()
|
||||
}
|
||||
}
|
||||
}
|
@ -14,26 +14,17 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { program } from 'commander'
|
||||
import { MongoClient, Db } from 'mongodb'
|
||||
import {
|
||||
getAccount,
|
||||
createAccount,
|
||||
assignWorkspace,
|
||||
createWorkspace,
|
||||
ACCOUNT_DB,
|
||||
dropWorkspace,
|
||||
dropAccount,
|
||||
listWorkspaces
|
||||
ACCOUNT_DB, assignWorkspace, createAccount, createWorkspace, dropAccount, dropWorkspace, getAccount, listWorkspaces
|
||||
} from '@anticrm/account'
|
||||
import { createContributingClient } from '@anticrm/contrib'
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import { encode } from 'jwt-simple'
|
||||
import { Client } from 'minio'
|
||||
import { initWorkspace, upgradeWorkspace, dumpWorkspace } from './workspace'
|
||||
|
||||
import contact, { combineName } from '@anticrm/contact'
|
||||
import core from '@anticrm/core'
|
||||
import { program } from 'commander'
|
||||
import { Client } from 'minio'
|
||||
import { Db, MongoClient } from 'mongodb'
|
||||
import { connect } from './connect'
|
||||
import { clearTelegramHistory } from './telegram'
|
||||
import { dumpWorkspace, initWorkspace, upgradeWorkspace } from './workspace'
|
||||
|
||||
const mongodbUri = process.env.MONGO_URL
|
||||
if (mongodbUri === undefined) {
|
||||
@ -112,28 +103,24 @@ program
|
||||
await assignWorkspace(db, email, workspace)
|
||||
|
||||
console.log('connecting to transactor...')
|
||||
const token = encode({ email: 'anticrm@hc.engineering', workspace }, 'secret')
|
||||
const url = new URL(`/${token}`, transactorUrl)
|
||||
const contrib = await createContributingClient(url.href)
|
||||
const txop = new TxOperations(contrib, core.account.System)
|
||||
const { connection, close } = await connect(transactorUrl, workspace)
|
||||
|
||||
const name = combineName(account.first, account.last)
|
||||
|
||||
console.log('create user in target workspace...')
|
||||
const employee = await txop.createDoc(contact.class.Employee, contact.space.Employee, {
|
||||
const employee = await connection.createDoc(contact.class.Employee, contact.space.Employee, {
|
||||
name,
|
||||
city: 'Mountain View',
|
||||
channels: []
|
||||
})
|
||||
|
||||
console.log('create account in target workspace...')
|
||||
await txop.createDoc(contact.class.EmployeeAccount, core.space.Model, {
|
||||
await connection.createDoc(contact.class.EmployeeAccount, core.space.Model, {
|
||||
email,
|
||||
employee,
|
||||
name
|
||||
})
|
||||
|
||||
contrib.close()
|
||||
await close()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -14,24 +14,22 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MongoClient, Document } from 'mongodb'
|
||||
import core, { DOMAIN_TX, Tx } from '@anticrm/core'
|
||||
import { createContributingClient } from '@anticrm/contrib'
|
||||
import { encode } from 'jwt-simple'
|
||||
import { BucketItem, Client } from 'minio'
|
||||
import contact from '@anticrm/contact'
|
||||
|
||||
import core, { DOMAIN_TX, Tx } from '@anticrm/core'
|
||||
import builder from '@anticrm/model-all'
|
||||
import { existsSync } from 'fs'
|
||||
import { mkdir, writeFile } from 'fs/promises'
|
||||
import { BucketItem, Client } from 'minio'
|
||||
import { Document, MongoClient } from 'mongodb'
|
||||
import { join } from 'path'
|
||||
import { connect } from './connect'
|
||||
|
||||
const txes = JSON.parse(JSON.stringify(builder.getTxes())) as Tx[]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function initWorkspace (mongoUrl: string, dbName: string, clientUrl: string, minio: Client): Promise<void> {
|
||||
export async function initWorkspace (mongoUrl: string, dbName: string, transactorUrl: string, minio: Client): Promise<void> {
|
||||
const client = new MongoClient(mongoUrl)
|
||||
try {
|
||||
await client.connect()
|
||||
@ -47,13 +45,12 @@ export async function initWorkspace (mongoUrl: string, dbName: string, clientUrl
|
||||
|
||||
console.log('creating data...')
|
||||
const data = txes.filter((tx) => tx.objectSpace !== core.space.Model)
|
||||
const token = encode({ email: 'anticrm@hc.engineering', workspace: dbName }, 'secret')
|
||||
const url = new URL(`/${token}`, clientUrl)
|
||||
const contrib = await createContributingClient(url.href)
|
||||
|
||||
const { connection, close } = await connect(transactorUrl, dbName)
|
||||
for (const tx of data) {
|
||||
await contrib.tx(tx)
|
||||
await connection.tx(tx)
|
||||
}
|
||||
contrib.close()
|
||||
await close()
|
||||
|
||||
console.log('create minio bucket')
|
||||
if (!(await minio.bucketExists(dbName))) {
|
||||
@ -70,7 +67,7 @@ export async function initWorkspace (mongoUrl: string, dbName: string, clientUrl
|
||||
export async function upgradeWorkspace (
|
||||
mongoUrl: string,
|
||||
dbName: string,
|
||||
clientUrl: string,
|
||||
transactorUrl: string,
|
||||
minio: Client
|
||||
): Promise<void> {
|
||||
const client = new MongoClient(mongoUrl)
|
||||
|
@ -13,16 +13,17 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Storage, DocumentQuery, FindResult, TxResult } from '../storage'
|
||||
import type { Class, Doc, Ref } from '../classes'
|
||||
import type { Tx } from '../tx'
|
||||
import { ClientConnection } from '../client'
|
||||
import core from '../component'
|
||||
import { Hierarchy } from '../hierarchy'
|
||||
import { ModelDb, TxDb } from '../memdb'
|
||||
import type { DocumentQuery, FindResult, TxResult } from '../storage'
|
||||
import type { Tx } from '../tx'
|
||||
import { DOMAIN_TX } from '../tx'
|
||||
import { genMinModel } from './minmodel'
|
||||
|
||||
export async function connect (handler: (tx: Tx) => void): Promise<Storage> {
|
||||
export async function connect (handler: (tx: Tx) => void): Promise<ClientConnection> {
|
||||
const txes = genMinModel()
|
||||
|
||||
const hierarchy = new Hierarchy()
|
||||
@ -50,6 +51,7 @@ export async function connect (handler: (tx: Tx) => void): Promise<Storage> {
|
||||
const result = await Promise.all([model.tx(tx), transactions.tx(tx)])
|
||||
return result[0]
|
||||
// handler(tx) - we have only one client, should not update?
|
||||
}
|
||||
},
|
||||
close: async () => {}
|
||||
}
|
||||
}
|
||||
|
@ -41,12 +41,20 @@ export interface Client extends Storage {
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
) => Promise<WithLookup<T> | undefined>
|
||||
close: () => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ClientConnection extends Storage {
|
||||
close: () => Promise<void>
|
||||
}
|
||||
|
||||
class ClientImpl implements Client {
|
||||
notify?: (tx: Tx) => void
|
||||
|
||||
constructor (private readonly hierarchy: Hierarchy, private readonly model: ModelDb, private readonly conn: Storage) {
|
||||
constructor (private readonly hierarchy: Hierarchy, private readonly model: ModelDb, private readonly conn: ClientConnection) {
|
||||
}
|
||||
|
||||
getHierarchy (): Hierarchy { return this.hierarchy }
|
||||
@ -90,13 +98,17 @@ class ClientImpl implements Client {
|
||||
}
|
||||
this.notify?.(tx)
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
await this.conn.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createClient (
|
||||
connect: (txHandler: TxHander) => Promise<Storage>
|
||||
connect: (txHandler: TxHander) => Promise<ClientConnection>
|
||||
): Promise<Client> {
|
||||
let client: ClientImpl | null = null
|
||||
let txBuffer: Tx[] | undefined = []
|
||||
|
@ -1,15 +1,15 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
@ -19,7 +19,7 @@ import { onDestroy } from 'svelte'
|
||||
import { Doc, Ref, Class, DocumentQuery, FindOptions, Client, Hierarchy, Tx, getCurrentAccount, ModelDb, TxResult, TxOperations, AnyAttribute, RefTo } from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
import { LiveQuery as LQ } from '@anticrm/query'
|
||||
import { getMetadata } from '@anticrm/platform'
|
||||
import { getMetadata } from '@anticrm/platform'
|
||||
|
||||
import login from '@anticrm/login'
|
||||
|
||||
@ -27,12 +27,11 @@ let liveQuery: LQ
|
||||
let client: Client & TxOperations
|
||||
|
||||
class UIClient extends TxOperations implements Client {
|
||||
|
||||
constructor (private readonly client: Client, private readonly liveQuery: LQ) {
|
||||
super(client, getCurrentAccount()._id)
|
||||
}
|
||||
|
||||
getHierarchy (): Hierarchy {
|
||||
getHierarchy (): Hierarchy {
|
||||
return this.client.getHierarchy()
|
||||
}
|
||||
|
||||
@ -40,53 +39,57 @@ class UIClient extends TxOperations implements Client {
|
||||
return this.client.getModel()
|
||||
}
|
||||
|
||||
tx(tx: Tx): Promise<TxResult> {
|
||||
async tx (tx: Tx): Promise<TxResult> {
|
||||
// return Promise.all([super.tx(tx), this.liveQuery.tx(tx)]) as unknown as Promise<void>
|
||||
return super.tx(tx)
|
||||
return await super.tx(tx)
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
|
||||
export function getClient(): Client & TxOperations {
|
||||
export function getClient (): Client & TxOperations {
|
||||
return client
|
||||
}
|
||||
|
||||
export function setClient(_client: Client) {
|
||||
export function setClient (_client: Client): void {
|
||||
liveQuery = new LQ(_client)
|
||||
client = new UIClient(_client, liveQuery)
|
||||
_client.notify = (tx: Tx) => {
|
||||
liveQuery.tx(tx)
|
||||
liveQuery.tx(tx).catch(err => console.log(err))
|
||||
}
|
||||
}
|
||||
|
||||
export class LiveQuery {
|
||||
private unsubscribe = () => {}
|
||||
|
||||
constructor() {
|
||||
constructor () {
|
||||
onDestroy(() => { console.log('onDestroy query'); this.unsubscribe() })
|
||||
}
|
||||
|
||||
query<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, callback: (result: T[]) => void, options?: FindOptions<T>) {
|
||||
query<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, callback: (result: T[]) => void, options?: FindOptions<T>): void {
|
||||
this.unsubscribe()
|
||||
this.unsubscribe = liveQuery.query(_class, query, callback, options)
|
||||
}
|
||||
}
|
||||
|
||||
export function createQuery() { return new LiveQuery() }
|
||||
export function createQuery (): LiveQuery { return new LiveQuery() }
|
||||
|
||||
export function getFileUrl(file: string): string {
|
||||
export function getFileUrl (file: string): string {
|
||||
const uploadUrl = getMetadata(login.metadata.UploadUrl)
|
||||
const token = getMetadata(login.metadata.LoginToken)
|
||||
const url = `${uploadUrl}?file=${file}&token=${token}`
|
||||
const url = `${uploadUrl as string}?file=${file}&token=${token as string}`
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getAttributePresenterClass (attribute: AnyAttribute): Ref<Class<Doc>> {
|
||||
export function getAttributePresenterClass (attribute: AnyAttribute): Ref<Class<Doc>> {
|
||||
let attrClass = attribute.type._class
|
||||
if (attrClass === core.class.RefTo) {
|
||||
attrClass = (attribute.type as RefTo<Doc>).to
|
||||
}
|
||||
return attrClass
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ export async function connect (handler: (tx: Tx) => void): Promise<Client> {
|
||||
// Not required, since handled in client.
|
||||
// handler(tx)
|
||||
return {}
|
||||
}
|
||||
},
|
||||
close: async () => {}
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,10 @@ export class LiveQuery extends TxProcessor implements Client {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
return await this.client.close()
|
||||
}
|
||||
|
||||
getHierarchy (): Hierarchy {
|
||||
return this.client.getHierarchy()
|
||||
}
|
||||
|
33
plugins/client-resources/readme.md
Normal file
33
plugins/client-resources/readme.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Overview
|
||||
|
||||
Package allow to create a client to interact with running platform.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import clientResources from '@anticrm/client-resources'
|
||||
import core, { Client } from '@anticrm/core'
|
||||
|
||||
// ...
|
||||
|
||||
const token = ... // Token obtained somehow.
|
||||
|
||||
const connection: Client = await (await clientResources()).function.GetClient(token, transactorUrl)
|
||||
|
||||
// Now client is usable
|
||||
|
||||
// Use close, to shutdown connection.
|
||||
await connection.close()
|
||||
```
|
||||
|
||||
## Node JS
|
||||
|
||||
For NodeJS enviornment it is required to configure ClientSocketFactory using 'ws' package.
|
||||
|
||||
```ts
|
||||
// We need to override default WebSocket factory with 'ws' one.
|
||||
setMetadata(client.metadata.ClientSocketFactory, (url) => new WebSocket(url))
|
||||
|
||||
const connection: Client = await (await clientResources()).function.GetClient(token, transactorUrl)
|
||||
...
|
||||
```
|
@ -14,9 +14,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Doc, DocumentQuery, FindOptions, FindResult, Ref, Storage, Tx, TxHander, TxResult } from '@anticrm/core'
|
||||
import type { ReqId } from '@anticrm/platform'
|
||||
import { serialize, readResponse } from '@anticrm/platform'
|
||||
import client, { ClientSocket } from '@anticrm/client'
|
||||
import type { Class, ClientConnection, Doc, DocumentQuery, FindOptions, FindResult, Ref, Tx, TxHander, TxResult } from '@anticrm/core'
|
||||
import { getMetadata, readResponse, ReqId, serialize } from '@anticrm/platform'
|
||||
|
||||
class DeferredPromise {
|
||||
readonly promise: Promise<any>
|
||||
@ -30,22 +30,30 @@ class DeferredPromise {
|
||||
}
|
||||
}
|
||||
|
||||
class Connection implements Storage {
|
||||
private websocket: WebSocket | null = null
|
||||
class Connection implements ClientConnection {
|
||||
private websocket: ClientSocket | null = null
|
||||
private readonly requests = new Map<ReqId, DeferredPromise>()
|
||||
private lastId = 0
|
||||
private readonly interval: number
|
||||
|
||||
constructor (private readonly url: string, private readonly handler: TxHander) {
|
||||
console.log('connection created')
|
||||
setInterval(() => {
|
||||
console.log('ping')
|
||||
this.interval = setInterval(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.sendRequest('ping')
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
private openConnection (): Promise<WebSocket> {
|
||||
const websocket = new WebSocket(this.url)
|
||||
async close (): Promise<void> {
|
||||
clearInterval(this.interval)
|
||||
this.websocket?.close()
|
||||
}
|
||||
|
||||
private openConnection (): Promise<ClientSocket> {
|
||||
// Use defined factory or browser default one.
|
||||
const clientSocketFactory = getMetadata(client.metadata.ClientSocketFactory) ?? ((url: string) => new WebSocket(url) as ClientSocket)
|
||||
|
||||
const websocket = clientSocketFactory(this.url)
|
||||
websocket.onmessage = (event: MessageEvent) => {
|
||||
const resp = readResponse(event.data)
|
||||
if (resp.id !== undefined) {
|
||||
@ -71,7 +79,7 @@ class Connection implements Storage {
|
||||
websocket.onopen = () => {
|
||||
resolve(websocket)
|
||||
}
|
||||
websocket.onerror = (event) => {
|
||||
websocket.onerror = (event: any) => {
|
||||
console.log('client websocket error', event)
|
||||
reject(new Error('websocket error'))
|
||||
}
|
||||
@ -103,6 +111,6 @@ class Connection implements Storage {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function connect (url: string, handler: TxHander): Promise<Storage> {
|
||||
export async function connect (url: string, handler: TxHander): Promise<ClientConnection> {
|
||||
return new Connection(url, handler)
|
||||
}
|
||||
|
@ -30,6 +30,25 @@ export const clientId = 'client' as Plugin
|
||||
*/
|
||||
export type ClientHook = (client: Client) => Promise<Client>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type ClientSocketFactory = (url: string) => ClientSocket
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ClientSocket {
|
||||
onmessage?: ((this: ClientSocket, ev: MessageEvent) => any) | null
|
||||
onclose?: ((this: ClientSocket, ev: CloseEvent) => any) | null
|
||||
onopen?: ((this: ClientSocket, ev: Event) => any) | null
|
||||
onerror?: ((this: ClientSocket, ev: Event) => any) | null
|
||||
|
||||
send: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void
|
||||
|
||||
close: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -38,7 +57,8 @@ export type ClientFactory = (token: string, endpoint: string) => Promise<Client>
|
||||
export default plugin(clientId,
|
||||
{
|
||||
metadata: {
|
||||
ClientHook: '' as Metadata<Resource<ClientHook>>
|
||||
ClientHook: '' as Metadata<Resource<ClientHook>>,
|
||||
ClientSocketFactory: '' as Metadata<ClientSocketFactory>
|
||||
},
|
||||
function: {
|
||||
GetClient: '' as Resource<ClientFactory>
|
||||
|
@ -78,6 +78,10 @@ class ModelClient implements Client {
|
||||
transactions.push({ tx, result })
|
||||
return result
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
export async function Hook (client: Client): Promise<Client> {
|
||||
console.info('devmodel# Client HOOKED by DevModel')
|
||||
|
@ -56,6 +56,8 @@ class NullDbAdapter implements DbAdapter {
|
||||
async tx (tx: Tx): Promise<TxResult> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async close (): Promise<void> {}
|
||||
}
|
||||
|
||||
async function createNullAdapter (hierarchy: Hierarchy, url: string, db: string, modelDb: ModelDb): Promise<DbAdapter> {
|
||||
@ -155,7 +157,11 @@ describe('mongo operations', () => {
|
||||
const serverStorage = await createServerStorage(conf)
|
||||
|
||||
client = await createClient(async (handler) => {
|
||||
return await Promise.resolve(serverStorage)
|
||||
return {
|
||||
findAll: async (_class, query, options) => await serverStorage.findAll(_class, query, options),
|
||||
tx: async (tx) => await serverStorage.tx(tx),
|
||||
close: async () => {}
|
||||
}
|
||||
})
|
||||
|
||||
operations = new TxOperations(client, core.account.System)
|
||||
|
Loading…
Reference in New Issue
Block a user