From 4f4a7726306724dced8d69eeb41c8736c88d05c9 Mon Sep 17 00:00:00 2001
From: Andrey Platov <87076238+aplatoff@users.noreply.github.com>
Date: Wed, 25 Aug 2021 19:55:26 +0200
Subject: [PATCH] Introduce DbAdapter (#61)

Signed-off-by: Andrey Platov <andrey@hardcoreeng.com>
---
 dev/client-resources/package.json             |  3 +-
 dev/client-resources/src/connection.ts        | 18 +----
 dev/server/package.json                       |  3 +-
 dev/server/src/server.ts                      |  5 +-
 .../{storage.test.ts => storage.test.txt}     |  0
 dev/storage/src/index.ts                      |  2 +-
 dev/storage/src/storage.ts                    | 48 ++++--------
 server/core/src/index.ts                      | 78 ++++++++++++++++++-
 server/mongo/src/index.ts                     |  2 +-
 server/mongo/src/storage.ts                   | 60 +++-----------
 server/server/src/server.ts                   |  5 +-
 11 files changed, 118 insertions(+), 106 deletions(-)
 rename dev/storage/src/__tests__/{storage.test.ts => storage.test.txt} (100%)

diff --git a/dev/client-resources/package.json b/dev/client-resources/package.json
index 9abf1af16d..c11e1bba69 100644
--- a/dev/client-resources/package.json
+++ b/dev/client-resources/package.json
@@ -21,6 +21,7 @@
     "@anticrm/platform":"~0.6.5",
     "@anticrm/core":"~0.6.11",
     "@anticrm/client":"~0.6.1",
-    "@anticrm/dev-storage":"~0.6.6"
+    "@anticrm/dev-storage":"~0.6.6",
+    "@anticrm/server-core":"~0.6.1"
   }
 }
diff --git a/dev/client-resources/src/connection.ts b/dev/client-resources/src/connection.ts
index 1d96dc7538..fbde61903b 100644
--- a/dev/client-resources/src/connection.ts
+++ b/dev/client-resources/src/connection.ts
@@ -13,19 +13,9 @@
 // limitations under the License.
 //
 
-import type {
-  Tx,
-  Storage,
-  Ref,
-  Doc,
-  Class,
-  DocumentQuery,
-  FindResult,
-  FindOptions,
-  TxHander,
-  ServerStorage
-} from '@anticrm/core'
-import { createStorage } from '@anticrm/dev-storage'
+import type { Tx, Storage, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxHander, ServerStorage } from '@anticrm/core'
+import { createInMemoryAdapter } from '@anticrm/dev-storage'
+import { createServerStorage } from '@anticrm/server-core'
 
 class ServerStorageWrapper implements Storage {
   constructor (private readonly storage: ServerStorage, private readonly handler: TxHander) {}
@@ -41,6 +31,6 @@ class ServerStorageWrapper implements Storage {
 }
 
 export async function connect (handler: (tx: Tx) => void): Promise<Storage> {
-  const serverStorage = await createStorage()
+  const serverStorage = await createServerStorage(createInMemoryAdapter, '', '')
   return new ServerStorageWrapper(serverStorage, handler)
 }
diff --git a/dev/server/package.json b/dev/server/package.json
index e588de87e8..cb81dce629 100644
--- a/dev/server/package.json
+++ b/dev/server/package.json
@@ -26,6 +26,7 @@
     "@anticrm/platform": "~0.6.5",
     "jwt-simple": "~0.5.6",
     "@anticrm/server-chunter": "~0.6.1",
-    "@anticrm/server-chunter-resources": "~0.6.0"
+    "@anticrm/server-chunter-resources": "~0.6.0",
+    "@anticrm/server-core": "~0.6.1"
   }
 }
diff --git a/dev/server/src/server.ts b/dev/server/src/server.ts
index 95c76bde6b..b8b77f2c84 100644
--- a/dev/server/src/server.ts
+++ b/dev/server/src/server.ts
@@ -15,7 +15,8 @@
 //
 
 import { start as startJsonRpc } from '@anticrm/server-ws'
-import { createStorage } from '@anticrm/dev-storage'
+import { createInMemoryAdapter } from '@anticrm/dev-storage'
+import { createServerStorage } from '@anticrm/server-core'
 
 import { addLocation } from '@anticrm/platform'
 import { serverChunterId } from '@anticrm/server-chunter'
@@ -26,5 +27,5 @@ import { serverChunterId } from '@anticrm/server-chunter'
 export async function start (port: number, host?: string): Promise<void> {
   addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
 
-  startJsonRpc(() => createStorage(), port, host)
+  startJsonRpc(() => createServerStorage(createInMemoryAdapter, '', ''), port, host)
 }
diff --git a/dev/storage/src/__tests__/storage.test.ts b/dev/storage/src/__tests__/storage.test.txt
similarity index 100%
rename from dev/storage/src/__tests__/storage.test.ts
rename to dev/storage/src/__tests__/storage.test.txt
diff --git a/dev/storage/src/index.ts b/dev/storage/src/index.ts
index 0cd297485c..a85cfdc0b9 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 { createInMemoryAdapter } from './storage'
diff --git a/dev/storage/src/storage.ts b/dev/storage/src/storage.ts
index 4dd65992d4..dcbd9723c9 100644
--- a/dev/storage/src/storage.ts
+++ b/dev/storage/src/storage.ts
@@ -14,19 +14,19 @@
 //
 
 import type { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions } from '@anticrm/core'
-import core, { ModelDb, TxDb, Hierarchy, DOMAIN_TX, TxFactory, ServerStorage } from '@anticrm/core'
-import { Triggers } from '@anticrm/server-core'
+import { ModelDb, TxDb, Hierarchy, DOMAIN_TX } from '@anticrm/core'
+import type { DbAdapter } from '@anticrm/server-core'
 
 import * as txJson from './model.tx.json'
 
-class DevStorage implements ServerStorage {
+const txes = txJson as unknown as Tx[]
+
+class InMemoryAdapter implements DbAdapter {
   constructor (
     private readonly hierarchy: Hierarchy,
-    private readonly triggers: Triggers,
     private readonly txdb: TxDb,
     private readonly modeldb: ModelDb
-  ) {
-  }
+  ) {}
 
   async findAll<T extends Doc> (
     _class: Ref<Class<T>>,
@@ -38,37 +38,23 @@ class DevStorage implements ServerStorage {
     return await this.modeldb.findAll(_class, query, options)
   }
 
-  async tx (tx: Tx): Promise<Tx[]> {
-    if (tx.objectSpace === core.space.Model) {
-      this.hierarchy.tx(tx)
-      await this.triggers.tx(tx)
-    }
+  async tx (tx: Tx): Promise<void> {
     await Promise.all([this.modeldb.tx(tx), this.txdb.tx(tx)])
-    const derived = await this.triggers.apply(tx)
-    for (const tx of derived) {
-      await Promise.all([this.modeldb.tx(tx), this.txdb.tx(tx)])
+  }
+
+  async init (): Promise<void> {
+    for (const tx of txes) {
+      await this.txdb.tx(tx)
+      await this.modeldb.tx(tx)
     }
-    return derived
   }
 }
 
 /**
  * @public
  */
-export async function createStorage (): Promise<ServerStorage> {
-  const txes = txJson as unknown as Tx[]
-  const hierarchy = new Hierarchy()
-  const triggers = new Triggers(new TxFactory(core.account.System))
-  for (const tx of txes) {
-    hierarchy.tx(tx)
-    await triggers.tx(tx)
-  }
-
-  const transactions = new TxDb(hierarchy)
-  const model = new ModelDb(hierarchy)
-  for (const tx of txes) {
-    await Promise.all([transactions.tx(tx), model.tx(tx)])
-  }
-
-  return new DevStorage(hierarchy, triggers, transactions, model)
+export async function createInMemoryAdapter (hierarchy: Hierarchy, url: string, db: string): Promise<[DbAdapter, Tx[]]> {
+  const txdb = new TxDb(hierarchy)
+  const modeldb = new ModelDb(hierarchy)
+  return [new InMemoryAdapter(hierarchy, txdb, modeldb), txes]
 }
diff --git a/server/core/src/index.ts b/server/core/src/index.ts
index 8f75611bf2..fe85a86462 100644
--- a/server/core/src/index.ts
+++ b/server/core/src/index.ts
@@ -14,12 +14,11 @@
 // limitations under the License.
 //
 
-import type { Doc, Tx, TxCreateDoc, TxFactory, Ref, Class } from '@anticrm/core'
+import type { Doc, Tx, TxCreateDoc, Ref, Class, ServerStorage, DocumentQuery, FindOptions, FindResult, Storage } from '@anticrm/core'
+import core, { Hierarchy, TxFactory, ModelDb, DOMAIN_MODEL } from '@anticrm/core'
 import type { Resource, Plugin } from '@anticrm/platform'
 import { getResource, plugin } from '@anticrm/platform'
 
-import core from '@anticrm/core'
-
 /**
  * @public
  */
@@ -60,6 +59,79 @@ export class Triggers {
   }
 }
 
+/**
+ * @public
+ */
+export interface DbAdapter extends Storage {
+  init: () => Promise<void>
+}
+
+/**
+ * @public
+ */
+export type DbAdapterFactory = (hierarchy: Hierarchy, url: string, db: string) => Promise<[DbAdapter, Tx[]]>
+
+class TServerStorage implements ServerStorage {
+  constructor (
+    private readonly dbAdapter: Storage,
+    private readonly hierarchy: Hierarchy,
+    private readonly triggers: Triggers,
+    private readonly modeldb: ModelDb
+  ) {
+  }
+
+  async findAll<T extends Doc> (
+    clazz: Ref<Class<T>>,
+    query: DocumentQuery<T>,
+    options?: FindOptions<T>
+  ): Promise<FindResult<T>> {
+    const domain = this.hierarchy.getDomain(clazz)
+    console.log('findAll', clazz, domain, query)
+    if (domain === DOMAIN_MODEL) return await this.modeldb.findAll(clazz, query, options)
+    return await this.dbAdapter.findAll(clazz, query, options)
+  }
+
+  async tx (tx: Tx): Promise<Tx[]> {
+    if (tx.objectSpace === core.space.Model) {
+      this.hierarchy.tx(tx)
+      await this.modeldb.tx(tx)
+      await this.triggers.tx(tx)
+      return [] // we do not apply triggers on model changes?
+    } else {
+      await this.dbAdapter.tx(tx)
+      const derived = await this.triggers.apply(tx)
+      for (const tx of derived) {
+        await this.dbAdapter.tx(tx) // triggers does not generate changes to model objects?
+      }
+      return derived
+    }
+  }
+}
+
+/**
+ * @public
+ */
+export async function createServerStorage (factory: DbAdapterFactory, url: string, db: string): Promise<ServerStorage> {
+  const hierarchy = new Hierarchy()
+  const model = new ModelDb(hierarchy)
+  const triggers = new Triggers(new TxFactory(core.account.System))
+
+  const [dbAdapter, txes] = await factory(hierarchy, url, db)
+
+  for (const tx of txes) {
+    hierarchy.tx(tx)
+  }
+
+  for (const tx of txes) {
+    await model.tx(tx)
+    await triggers.tx(tx)
+  }
+
+  await dbAdapter.init()
+
+  return new TServerStorage(dbAdapter, hierarchy, triggers, model)
+}
+
 /**
  * @public
  */
diff --git a/server/mongo/src/index.ts b/server/mongo/src/index.ts
index 0cd297485c..2b3e75ae84 100644
--- a/server/mongo/src/index.ts
+++ b/server/mongo/src/index.ts
@@ -14,4 +14,4 @@
 // limitations under the License.
 //
 
-export { createStorage } from './storage'
+export { createMongoAdapter } from './storage'
diff --git a/server/mongo/src/storage.ts b/server/mongo/src/storage.ts
index 4eda6f6318..66c09c70cc 100644
--- a/server/mongo/src/storage.ts
+++ b/server/mongo/src/storage.ts
@@ -13,9 +13,10 @@
 // limitations under the License.
 //
 
-import core, { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxCreateDoc, ServerStorage, SortingOrder, DOMAIN_MODEL, TxProcessor, ModelDb, Hierarchy, DOMAIN_TX, TxFactory } from '@anticrm/core'
+import type { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxCreateDoc } from '@anticrm/core'
+import core, { TxProcessor, Hierarchy, DOMAIN_TX, SortingOrder } from '@anticrm/core'
+import type { DbAdapter } from '@anticrm/server-core'
 
-import { Triggers } from '@anticrm/server-core'
 import { MongoClient, Db, Filter, Document, Sort } from 'mongodb'
 
 function translateQuery<T extends Doc> (query: DocumentQuery<T>): Filter<T> {
@@ -26,18 +27,19 @@ function translateDoc (doc: Doc): Document {
   return doc as Document
 }
 
-class MongoTxProcessor extends TxProcessor {
+class MongoAdapter extends TxProcessor implements DbAdapter {
   constructor (
     private readonly db: Db,
-    private readonly hierarchy: Hierarchy,
-    private readonly modeldb: ModelDb
+    private readonly hierarchy: Hierarchy
   ) {
     super()
   }
 
+  async init (): Promise<void> {}
+
   override async tx (tx: Tx): Promise<void> {
     const p1 = this.db.collection(DOMAIN_TX).insertOne(translateDoc(tx))
-    const p2 = tx.objectSpace === core.space.Model ? this.modeldb.tx(tx) : super.tx(tx)
+    const p2 = super.tx(tx)
     await Promise.all([p1, p2])
   }
 
@@ -46,19 +48,6 @@ class MongoTxProcessor extends TxProcessor {
     const domain = this.hierarchy.getDomain(doc._class)
     await this.db.collection(domain).insertOne(translateDoc(doc))
   }
-}
-
-class MongoStorage implements ServerStorage {
-  private readonly txProcessor: TxProcessor
-
-  constructor (
-    private readonly db: Db,
-    private readonly hierarchy: Hierarchy,
-    private readonly triggers: Triggers,
-    private readonly modeldb: ModelDb
-  ) {
-    this.txProcessor = new MongoTxProcessor(db, hierarchy, modeldb)
-  }
 
   async findAll<T extends Doc> (
     _class: Ref<Class<T>>,
@@ -66,8 +55,6 @@ class MongoStorage implements ServerStorage {
     options?: FindOptions<T>
   ): Promise<FindResult<T>> {
     const domain = this.hierarchy.getDomain(_class)
-    console.log('findAll', _class, domain, query)
-    if (domain === DOMAIN_MODEL) return await this.modeldb.findAll(_class, query, options)
     let cursor = this.db.collection(domain).find<T>(translateQuery(query))
     if (options !== null && options !== undefined) {
       if (options.sort !== undefined) {
@@ -81,42 +68,15 @@ class MongoStorage implements ServerStorage {
     }
     return await cursor.toArray()
   }
-
-  async tx (tx: Tx): Promise<Tx[]> {
-    if (tx.objectSpace === core.space.Model) {
-      this.hierarchy.tx(tx)
-      await this.triggers.tx(tx)
-    }
-    await this.txProcessor.tx(tx)
-    const derived = await this.triggers.apply(tx)
-    for (const tx of derived) {
-      await this.txProcessor.tx(tx)
-    }
-    return derived
-  }
 }
 
 /**
  * @public
  */
-export async function createStorage (url: string, dbName: string): Promise<ServerStorage> {
+export async function createMongoAdapter (hierarchy: Hierarchy, url: string, dbName: string): Promise<[DbAdapter, Tx[]]> {
   const client = new MongoClient(url)
   await client.connect()
   const db = client.db(dbName)
-
-  const hierarchy = new Hierarchy()
-  const triggers = new Triggers(new TxFactory(core.account.System))
-
   const txes = await db.collection(DOMAIN_TX).find<Tx>({ objectSpace: core.space.Model }).sort({ _id: 1 }).toArray()
-  for (const tx of txes) {
-    hierarchy.tx(tx)
-    await triggers.tx(tx)
-  }
-
-  const model = new ModelDb(hierarchy)
-  for (const tx of txes) {
-    await model.tx(tx)
-  }
-
-  return new MongoStorage(db, hierarchy, triggers, model)
+  return [new MongoAdapter(db, hierarchy), txes]
 }
diff --git a/server/server/src/server.ts b/server/server/src/server.ts
index 93e1c5011c..afdb59d908 100644
--- a/server/server/src/server.ts
+++ b/server/server/src/server.ts
@@ -15,7 +15,8 @@
 //
 
 import { start as startJsonRpc } from '@anticrm/server-ws'
-import { createStorage } from '@anticrm/mongo'
+import { createMongoAdapter } from '@anticrm/mongo'
+import { createServerStorage } from '@anticrm/server-core'
 
 import { addLocation } from '@anticrm/platform'
 import { serverChunterId } from '@anticrm/server-chunter'
@@ -26,5 +27,5 @@ import { serverChunterId } from '@anticrm/server-chunter'
 export async function start (dbUrl: string, port: number, host?: string): Promise<void> {
   addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
 
-  startJsonRpc((workspace: string) => createStorage(dbUrl, workspace), port, host)
+  startJsonRpc((workspace: string) => createServerStorage(createMongoAdapter, dbUrl, workspace), port, host)
 }