Allow Use client from client-resources from NodeJS (#545)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2021-12-06 23:57:35 +07:00 committed by GitHub
parent 420fc93967
commit beecb23c79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 207 additions and 93 deletions

View File

@ -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:

View File

@ -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'

View File

@ -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
View 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()
}
}
}

View File

@ -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()
})
})

View File

@ -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)

View File

@ -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 () => {}
}
}

View File

@ -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 = []

View File

@ -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
}
}

View File

@ -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 () => {}
}
}

View File

@ -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()
}

View 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)
...
```

View File

@ -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)
}

View File

@ -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>

View File

@ -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')

View File

@ -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)