diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index aee29c8e4f..1be94976bb 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -6,9 +6,9 @@ name: CI
 on:
   # Triggers the workflow on push or pull request events but only for the main branch
   push:
-    branches: [ main ]
+    branches: [ main, develop ]
   pull_request:
-    branches: [ main ]
+    branches: [ main, develop ]
 
   # Allows you to run this workflow manually from the Actions tab
   workflow_dispatch:
diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 90e74ba03a..95c692a447 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -9548,7 +9548,7 @@ packages:
     dev: false
 
   file:projects/activity-resources.tgz_e1367da94684b005adf08f025c517b1a:
-    resolution: {integrity: sha512-WDQxnDVBhDhLcJ0+gZveR64mMJBdC+hx7LFp8RmiL7SvBN5YqO+freVJ/jwhWfeN4RHmCsKsuuAFHlE9PEw/kg==, tarball: file:projects/activity-resources.tgz}
+    resolution: {integrity: sha512-+QzdZodn7EX1tvSLWZXDEpivgYNYeFbALbr28PO81fXSHSbiwhodZE7J6teILOdZxW3wS/7TPiokvb5zndPCog==, tarball: file:projects/activity-resources.tgz}
     id: file:projects/activity-resources.tgz
     name: '@rush-temp/activity-resources'
     version: 0.0.0
@@ -10222,7 +10222,7 @@ packages:
     dev: false
 
   file:projects/model-telegram.tgz_typescript@4.4.3:
-    resolution: {integrity: sha512-X4Ao5ADNaxuup/qZv0FeOLSEqXKHVqkqEEdUvHwcMHeHr1SvibSMhlumfTdPGCq70iX2AL2R37FI0DGlx0EnUw==, tarball: file:projects/model-telegram.tgz}
+    resolution: {integrity: sha512-Pe+NBKGYALA2zA0qUm3yJ7G0c2ZI9WfKIjKDrhtJgMPauf8cgdykpjMyBMgKCKom1jNOzF3RGRQnWh86Q7+MtA==, tarball: file:projects/model-telegram.tgz}
     id: file:projects/model-telegram.tgz
     name: '@rush-temp/model-telegram'
     version: 0.0.0
@@ -10474,22 +10474,28 @@ packages:
     dev: false
 
   file:projects/query.tgz_typescript@4.4.3:
-    resolution: {integrity: sha512-v0KEf+fc7kni8uGlxU33qQQV9N8fLv85+Yaty64+giXdyla+2CmmEyVyCHtjuhiL786luoAY5Bz6UZjxfCV/vA==, tarball: file:projects/query.tgz}
+    resolution: {integrity: sha512-b9sIE2RoX3GNvHPqcd3DBUS/wuYYkdCqmebq0AJ8t190Z1n1nyQOQy7xjO7tXgOVd85BMpCg/qVKuG/mv5+bvA==, tarball: file:projects/query.tgz}
     id: file:projects/query.tgz
     name: '@rush-temp/query'
     version: 0.0.0
     dependencies:
+      '@rushstack/heft': 0.41.1
+      '@rushstack/heft-jest-plugin': 0.1.36_@rushstack+heft@0.41.1
       '@types/heft-jest': 1.0.2
       '@typescript-eslint/eslint-plugin': 4.33.0_eslint@7.32.0+typescript@4.4.3
       eslint: 7.32.0
       eslint-plugin-import: 2.25.3_eslint@7.32.0
       eslint-plugin-node: 11.1.0_eslint@7.32.0
       eslint-plugin-promise: 4.3.1
+      just-clone: 3.2.1
       simplytyped: 3.3.0_typescript@4.4.3
     transitivePeerDependencies:
       - '@typescript-eslint/parser'
+      - bufferutil
+      - canvas
       - supports-color
       - typescript
+      - utf-8-validate
     dev: false
 
   file:projects/recruit-assets.tgz:
@@ -10841,7 +10847,7 @@ packages:
     dev: false
 
   file:projects/telegram-resources.tgz_e1367da94684b005adf08f025c517b1a:
-    resolution: {integrity: sha512-sbIaaqkc2pyh2dXKHF6S3gj+VZRzBjFngvY1b+RYQ+a0v7bF74kvA+uR7moeRYFf1p8VX3Rqg0kUvC3bSY2lWA==, tarball: file:projects/telegram-resources.tgz}
+    resolution: {integrity: sha512-c6QScub4eTkKdIFSwTwERbTGMTEC6WPTPQGR6OeuQbdkuZpF5aUtnkx6rRcROlkcIZuq8sWBOJljlRl9ch+ZoQ==, tarball: file:projects/telegram-resources.tgz}
     id: file:projects/telegram-resources.tgz
     name: '@rush-temp/telegram-resources'
     version: 0.0.0
@@ -10871,7 +10877,7 @@ packages:
     dev: false
 
   file:projects/telegram.tgz_typescript@4.4.3:
-    resolution: {integrity: sha512-tUpx+Ryw6IrNxyKxPL99sEfzDhXAy2Q8YAYrBro6lCNrvH12qLHZ3va5wZHtRT0mnvMg8oM0Q+zgaitWfCDQ8w==, tarball: file:projects/telegram.tgz}
+    resolution: {integrity: sha512-b0V40ZxHl36k6O/ukvRnBNF3hL/tcSleVNqrrdRMjGBtu9pGQFNTyYpJfUJ9z5keujRNL6Yc9wmxZ3F6JBUolw==, tarball: file:projects/telegram.tgz}
     id: file:projects/telegram.tgz
     name: '@rush-temp/telegram'
     version: 0.0.0
diff --git a/packages/core/src/memdb.ts b/packages/core/src/memdb.ts
index b5eb83efc8..046114a26c 100644
--- a/packages/core/src/memdb.ts
+++ b/packages/core/src/memdb.ts
@@ -17,7 +17,7 @@ import { PlatformError, Severity, Status } from '@anticrm/platform'
 import clone from 'just-clone'
 import type { Class, Doc, Ref } from './classes'
 import core from './component'
-import type { Hierarchy } from './hierarchy'
+import { Hierarchy } from './hierarchy'
 import { findProperty, resultSort } from './query'
 import type { DocumentQuery, FindOptions, FindResult, LookupData, Refs, Storage, TxResult, WithLookup } from './storage'
 import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
diff --git a/packages/core/src/tx.ts b/packages/core/src/tx.ts
index 73535d0927..207151c1ed 100644
--- a/packages/core/src/tx.ts
+++ b/packages/core/src/tx.ts
@@ -18,7 +18,7 @@ import type { Class, Data, Doc, Domain, Ref, Account, Space, Arr, Mixin, Propert
 import type { DocumentQuery, FindOptions, FindResult, Storage, WithLookup, TxResult } from './storage'
 import core from './component'
 import { generateId } from './utils'
-import { _getOperator } from '.'
+import { _getOperator } from './operator'
 
 /**
  * @public
diff --git a/packages/query/.eslintrc.js b/packages/query/.eslintrc.js
index 0dd1690d11..5da5872d4a 100644
--- a/packages/query/.eslintrc.js
+++ b/packages/query/.eslintrc.js
@@ -4,4 +4,4 @@ module.exports = {
     tsconfigRootDir: __dirname,
     project: './tsconfig.json'
   }
-}
\ No newline at end of file
+}
diff --git a/packages/query/package.json b/packages/query/package.json
index 1a525ea94b..bac46cec10 100644
--- a/packages/query/package.json
+++ b/packages/query/package.json
@@ -6,6 +6,7 @@
   "license": "EPL-2.0",
   "scripts": {
     "build": "heft build",
+    "test": "heft test",
     "build:watch": "tsc",
     "lint:fix": "eslint --fix src"
   },
@@ -17,10 +18,13 @@
     "eslint-plugin-promise":"4",
     "eslint-plugin-node":"11",
     "eslint":"^7.32.0",
-    "simplytyped": "^3.3.0"
+    "simplytyped": "^3.3.0",
+    "@rushstack/heft-jest-plugin":"^0.1.15",
+    "@rushstack/heft":"^0.41.1"
   },
   "dependencies": {
     "@anticrm/platform": "~0.6.5",
-    "@anticrm/core": "~0.6.11"
+    "@anticrm/core": "~0.6.11",
+    "just-clone": "^3.2.1"
   }
 }
diff --git a/packages/query/src/__tests__/connection.txt b/packages/query/src/__tests__/connection.ts
similarity index 61%
rename from packages/query/src/__tests__/connection.txt
rename to packages/query/src/__tests__/connection.ts
index 279d621538..87cc119013 100644
--- a/packages/query/src/__tests__/connection.txt
+++ b/packages/query/src/__tests__/connection.ts
@@ -13,12 +13,11 @@
 // limitations under the License.
 //
 
-import type { Tx, Storage, Ref, Doc, Class, DocumentQuery, FindResult } from '@anticrm/core'
-import core, { ModelDb, TxDb, Hierarchy, DOMAIN_TX } from '@anticrm/core'
+import type { Class, Client, Doc, DocumentQuery, FindOptions, FindResult, Ref, Tx, TxResult } from '@anticrm/core'
+import core, { DOMAIN_TX, Hierarchy, ModelDb, TxDb } from '@anticrm/core'
+import { genMinModel } from './minmodel'
 
-import { genMinModel } from '@anticrm/core/src/__tests__/minmodel'
-
-export async function connect (handler: (tx: Tx) => void): Promise<Storage> {
+export async function connect (handler: (tx: Tx) => void): Promise<Client> {
   const txes = genMinModel()
 
   const hierarchy = new Hierarchy()
@@ -31,20 +30,29 @@ export async function connect (handler: (tx: Tx) => void): Promise<Storage> {
     await model.tx(tx)
   }
 
-  async function findAll<T extends Doc> (_class: Ref<Class<T>>, query: DocumentQuery<T>): Promise<FindResult<T>> {
+  async function findAll<T extends Doc> (
+    _class: Ref<Class<T>>,
+    query: DocumentQuery<T>,
+    options?: FindOptions<T>
+  ): Promise<FindResult<T>> {
     const domain = hierarchy.getClass(_class).domain
-    if (domain === DOMAIN_TX) return await transactions.findAll(_class, query)
-    return await model.findAll(_class, query)
+    if (domain === DOMAIN_TX) return await transactions.findAll(_class, query, options)
+    return await model.findAll(_class, query, options)
   }
 
   return {
     findAll,
-    tx: async (tx: Tx): Promise<void> => {
+    findOne: async (_class, query, options) => (await findAll(_class, query, { ...options, limit: 1 })).shift(),
+    getHierarchy: () => hierarchy,
+    getModel: () => model,
+    tx: async (tx: Tx): Promise<TxResult> => {
       if (tx.objectSpace === core.space.Model) {
         hierarchy.tx(tx)
       }
       await Promise.all([model.tx(tx), transactions.tx(tx)])
-      handler(tx)
+      // Not required, since handled in client.
+      // handler(tx)
+      return {}
     }
   }
 }
diff --git a/packages/query/src/__tests__/minmodel.ts b/packages/query/src/__tests__/minmodel.ts
new file mode 100644
index 0000000000..1cfa3ab798
--- /dev/null
+++ b/packages/query/src/__tests__/minmodel.ts
@@ -0,0 +1,113 @@
+//
+// 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 type { Account, Arr, Class, Data, Doc, Mixin, Obj, Ref, TxCreateDoc, TxCUD } from '@anticrm/core'
+import core, { AttachedDoc, ClassifierKind, DOMAIN_MODEL, DOMAIN_TX, TxFactory } from '@anticrm/core'
+import type { IntlString, Plugin } from '@anticrm/platform'
+import { plugin } from '@anticrm/platform'
+
+const txFactory = new TxFactory(core.account.System)
+
+function createClass (_class: Ref<Class<Obj>>, attributes: Data<Class<Obj>>): TxCreateDoc<Doc> {
+  return txFactory.createTxCreateDoc(core.class.Class, core.space.Model, attributes, _class)
+}
+
+/**
+ * @public
+ */
+export function createDoc<T extends Doc> (_class: Ref<Class<T>>, attributes: Data<T>, id?: Ref<T>,
+  modifiedBy?: Ref<Account>): TxCreateDoc<Doc> {
+  const result = txFactory.createTxCreateDoc(_class, core.space.Model, attributes, id)
+  if (modifiedBy !== undefined) {
+    result.modifiedBy = modifiedBy
+  }
+  return result
+}
+
+/**
+ * @public
+ */
+export interface TestMixin extends Doc {
+  arr: Arr<string>
+}
+
+/**
+ * @public
+ */
+export interface AttachedComment extends AttachedDoc {
+  message: string
+}
+
+/**
+ * @public
+ */
+export const test = plugin('test' as Plugin, {
+  mixin: {
+    TestMixin: '' as Ref<Mixin<TestMixin>>
+  },
+  class: {
+    TestComment: '' as Ref<Class<AttachedComment>>
+  }
+})
+
+/**
+ * @public
+ * Generate minimal model for testing purposes.
+ * @returns R
+ */
+export function genMinModel (): TxCUD<Doc>[] {
+  const txes = []
+  // Fill Tx'es with basic model classes.
+  txes.push(createClass(core.class.Obj, { label: 'Obj' as IntlString, kind: ClassifierKind.CLASS }))
+  txes.push(createClass(core.class.Doc, { label: 'Doc' as IntlString, extends: core.class.Obj, kind: ClassifierKind.CLASS }))
+  txes.push(createClass(core.class.AttachedDoc, { label: 'AttachedDoc' as IntlString, extends: core.class.Doc, kind: ClassifierKind.MIXIN }))
+  txes.push(createClass(core.class.Class, { label: 'Class' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_MODEL }))
+  txes.push(createClass(core.class.Space, { label: 'Space' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_MODEL }))
+  txes.push(createClass(core.class.Account, { label: 'Account' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_MODEL }))
+
+  txes.push(createClass(core.class.Tx, { label: 'Tx' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_TX }))
+  txes.push(createClass(core.class.TxCUD, { label: 'TxCUD' as IntlString, extends: core.class.Tx, kind: ClassifierKind.CLASS, domain: DOMAIN_TX }))
+  txes.push(createClass(core.class.TxCreateDoc, { label: 'TxCreateDoc' as IntlString, extends: core.class.TxCUD, kind: ClassifierKind.CLASS }))
+  txes.push(createClass(core.class.TxUpdateDoc, { label: 'TxUpdateDoc' as IntlString, extends: core.class.TxCUD, kind: ClassifierKind.CLASS }))
+  txes.push(createClass(core.class.TxRemoveDoc, { label: 'TxRemoveDoc' as IntlString, extends: core.class.TxCUD, kind: ClassifierKind.CLASS }))
+  txes.push(createClass(core.class.TxCollectionCUD, { label: 'TxCollectionCUD' as IntlString, extends: core.class.TxCUD, kind: ClassifierKind.CLASS }))
+
+  txes.push(createClass(test.mixin.TestMixin, { label: 'TestMixin' as IntlString, extends: core.class.Doc, kind: ClassifierKind.MIXIN }))
+
+  txes.push(createClass(test.class.TestComment, { label: 'TestComment' as IntlString, extends: core.class.AttachedDoc, kind: ClassifierKind.CLASS }))
+
+  const u1 = 'User1' as Ref<Account>
+  const u2 = 'User2' as Ref<Account>
+  txes.push(
+    createDoc(core.class.Account, { email: 'user1@site.com' }, u1),
+    createDoc(core.class.Account, { email: 'user2@site.com' }, u2),
+    createDoc(core.class.Space, {
+      name: 'Sp1',
+      description: '',
+      private: false,
+      members: [u1, u2]
+    })
+  )
+
+  txes.push(
+    createDoc(core.class.Space, {
+      name: 'Sp2',
+      description: '',
+      private: false,
+      members: [u1]
+    })
+  )
+  return txes
+}
diff --git a/packages/query/src/__tests__/query.test.ts b/packages/query/src/__tests__/query.test.ts
new file mode 100644
index 0000000000..4e9a0cf67e
--- /dev/null
+++ b/packages/query/src/__tests__/query.test.ts
@@ -0,0 +1,379 @@
+//
+// Copyright © 2021 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, { createClient, Doc, SortingOrder, Space, Tx, TxCreateDoc, TxOperations } from '@anticrm/core'
+import { genMinModel } from './minmodel'
+import { LiveQuery } from '..'
+import { connect } from './connection'
+
+interface Channel extends Space {
+  x: number
+}
+
+async function getClient (): Promise<{liveQuery: LiveQuery, factory: TxOperations}> {
+  const storage = await createClient(connect)
+  const liveQuery = new LiveQuery(storage)
+  storage.notify = (tx: Tx) => {
+    liveQuery.tx(tx).catch(err => console.log(err))
+  }
+  return { liveQuery, factory: new TxOperations(storage, core.account.System) }
+}
+
+describe('query', () => {
+  it('findAll', async () => {
+    const { liveQuery } = await getClient()
+    const result = await liveQuery.findAll<Space>(core.class.Space, {})
+    expect(result).toHaveLength(2)
+  })
+
+  it('query with param', async () => {
+    const { liveQuery } = await getClient()
+
+    let expectedLength = 0
+    const txes = genMinModel()
+    for (let i = 0; i < txes.length; i++) {
+      if (liveQuery.getHierarchy().isDerived((txes[i] as TxCreateDoc<Doc>).objectClass, core.class.Space)) {
+        expectedLength++
+      }
+    }
+
+    await new Promise((resolve) => {
+      liveQuery.query<Space>(core.class.Space, { private: false }, (result) => {
+        expect(result).toHaveLength(expectedLength)
+        resolve(null)
+      })
+    })
+  })
+
+  it('query should be live', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    let expectedLength = 0
+    const txes = genMinModel()
+    for (let i = 0; i < txes.length; i++) {
+      if (liveQuery.getHierarchy().isDerived((txes[i] as TxCreateDoc<Doc>).objectClass, core.class.Space)) {
+        expectedLength++
+      }
+    }
+
+    let attempt = 0
+    const pp = new Promise((resolve) => {
+      liveQuery.query<Space>(core.class.Space, { private: false }, (result) => {
+        console.log('query result attempt', result, attempt)
+        expect(result).toHaveLength(expectedLength + attempt)
+        if (attempt > 0) {
+          expect((result[expectedLength + attempt - 1] as any).x).toBe(attempt)
+        }
+        if (attempt++ === 3) {
+          // check underlying storage received all data.
+          liveQuery
+            .findAll<Space>(core.class.Space, { private: false })
+            .then((result) => {
+              expect(result).toHaveLength(expectedLength + attempt - 1)
+              resolve(null)
+            })
+            .catch((err) => expect(err).toBeUndefined())
+        }
+      })
+    })
+
+    await factory.createDoc(core.class.Account, core.space.Model, {
+      email: 'user1@site.com'
+    })
+    await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
+      private: true,
+      name: '#0',
+      description: '',
+      members: [],
+      x: 0
+    })
+    await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
+      private: false,
+      name: '#1',
+      description: '',
+      members: [],
+      x: 1
+    })
+    await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
+      private: false,
+      name: '#2',
+      description: '',
+      members: [],
+      x: 2
+    })
+    await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
+      private: false,
+      name: '#3',
+      description: '',
+      members: [],
+      x: 3
+    })
+    await pp
+  })
+
+  it('unsubscribe query', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    let expectedLength = 0
+    const txes = genMinModel()
+    for (let i = 0; i < txes.length; i++) {
+      if (liveQuery.getHierarchy().isDerived((txes[i] as TxCreateDoc<Doc>).objectClass, core.class.Space)) {
+        expectedLength++
+      }
+    }
+
+    const unsubscribe = liveQuery.query<Space>(core.class.Space, { private: false }, (result) => {
+      expect(result).toHaveLength(expectedLength)
+    })
+
+    unsubscribe()
+
+    await factory.createDoc(core.class.Space, core.space.Model, {
+      private: false,
+      name: '#1',
+      description: '',
+      members: []
+    })
+    await factory.createDoc(core.class.Space, core.space.Model, {
+      private: false,
+      name: '#2',
+      description: '',
+      members: []
+    })
+    await factory.createDoc(core.class.Space, core.space.Model, {
+      private: false,
+      name: '#3',
+      description: '',
+      members: []
+    })
+  })
+
+  it('query against core client', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    const expectedLength = 2
+    let attempt = 0
+    const pp = new Promise((resolve) => {
+      liveQuery.query<Space>(core.class.Space, { private: false }, (result) => {
+        expect(result).toHaveLength(expectedLength + attempt)
+        if (attempt > 0) {
+          expect((result[expectedLength + attempt - 1] as any).x).toBe(attempt)
+        }
+        if (attempt++ === 1) resolve(null)
+      })
+    })
+
+    await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
+      x: 1,
+      private: false,
+      name: '#1',
+      description: '',
+      members: []
+    })
+    await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
+      x: 2,
+      private: false,
+      name: '#2',
+      description: '',
+      members: []
+    })
+    await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
+      x: 3,
+      private: false,
+      name: '#3',
+      description: '',
+      members: []
+    })
+    await pp
+  })
+
+  it('limit and sorting', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    const limit = 1
+    let attempt = -1
+    let doneCount = 0
+
+    const pp1 = new Promise((resolve) => {
+      liveQuery.query<Space>(
+        core.class.Space,
+        { private: true },
+        (result) => {
+          if (attempt === 0 && result.length > 0) {
+            expect(result.length).toEqual(limit)
+            expect(result[0].name).toMatch('0')
+          }
+          if (attempt === 0) doneCount++
+          if (doneCount === 2) resolve(null)
+        },
+        { limit: limit, sort: { name: SortingOrder.Ascending } }
+      )
+    })
+
+    const pp2 = new Promise((resolve) => {
+      liveQuery.query<Space>(
+        core.class.Space,
+        { private: true },
+        (result) => {
+          if (attempt > 0 && result.length > 0) {
+            expect(result.length).toEqual(limit)
+            expect(result[0].name).toMatch(attempt.toString())
+          }
+          if (attempt === 9) doneCount++
+          if (doneCount === 2) resolve(null)
+        },
+        { limit: limit, sort: { name: SortingOrder.Descending } }
+      )
+    })
+
+    for (let i = 0; i < 10; i++) {
+      attempt = i
+      await factory.createDoc(core.class.Space, core.space.Model, {
+        private: true,
+        name: i.toString(),
+        description: '',
+        members: []
+      })
+    }
+    await Promise.all([pp1, pp2])
+  })
+
+  it('remove', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    const expectedLength = 2
+    let attempt = 0
+    const pp = new Promise((resolve) => {
+      liveQuery.query<Space>(core.class.Space, { private: false }, (result) => {
+        expect(result).toHaveLength(expectedLength - attempt)
+        if (attempt++ === expectedLength) resolve(null)
+      })
+    })
+
+    const spaces = await liveQuery.findAll(core.class.Space, {})
+    for (const space of spaces) {
+      await factory.removeDoc(space._class, space.space, space._id)
+    }
+    await pp
+  })
+
+  it('remove with limit', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    const expectedLength = 2
+    let attempt = 0
+    const pp = new Promise((resolve) => {
+      liveQuery.query<Space>(
+        core.class.Space,
+        { private: false },
+        (result) => {
+          expect(result).toHaveLength(attempt++ === expectedLength ? 0 : 1)
+          if (attempt === expectedLength) resolve(null)
+        },
+        { limit: 1 }
+      )
+    })
+
+    const spaces = await liveQuery.findAll(core.class.Space, {})
+    for (const space of spaces) {
+      await factory.removeDoc(space._class, space.space, space._id)
+    }
+    await pp
+  })
+
+  it('update', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    const spaces = await liveQuery.findAll(core.class.Space, {})
+    let attempt = 0
+    const pp = new Promise((resolve) => {
+      liveQuery.query<Space>(
+        core.class.Space,
+        { private: false },
+        (result) => {
+          if (attempt > 0) {
+            expect(result[attempt - 1].name === attempt.toString())
+            expect(result[attempt - 1].members.length === 1)
+            if (attempt === spaces.length) resolve(null)
+          }
+        },
+        { sort: { private: SortingOrder.Ascending } }
+      )
+    })
+
+    for (const space of spaces) {
+      attempt++
+      await factory.updateDoc(space._class, space.space, space._id, {
+        name: attempt.toString(),
+        $push: { members: core.account.System }
+      })
+    }
+    await pp
+  })
+
+  it('update with no match query', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    const spaces = await liveQuery.findAll(core.class.Space, {})
+    let attempt = 0
+    const pp = new Promise((resolve) => {
+      liveQuery.query<Space>(
+        core.class.Space,
+        { private: false },
+        (result) => {
+          if (attempt > 0) {
+            expect(result.length === spaces.length - attempt)
+            if (attempt === spaces.length) resolve(null)
+          }
+        },
+        { sort: { private: SortingOrder.Ascending } }
+      )
+    })
+
+    for (const space of spaces) {
+      attempt++
+      await factory.updateDoc(space._class, space.space, space._id, {
+        private: true
+      })
+    }
+    await pp
+  })
+
+  // it('update with over limit', async () => {
+  //   const { liveQuery, factory } = await getClient()
+
+  //   const spaces = await liveQuery.findAll(core.class.Space, {})
+  //   let attempt = 0
+  //   const pp = new Promise((resolve) => {
+  //     liveQuery.query<Space>(
+  //       core.class.Space,
+  //       {},
+  //       (result) => {
+  //         expect(result[0].name).toEqual(`Sp${++attempt}`)
+  //         if (attempt === spaces.length + 1) resolve(null)
+  //       },
+  //       { sort: { name: SortingOrder.Ascending }, limit: 1 }
+  //     )
+  //   })
+
+  //   for (let index = 0; index < spaces.length; index++) {
+  //     const space = spaces[index]
+  //     await factory.updateDoc(space._class, space.space, space._id, {
+  //       name: `Sp${index + spaces.length + 1}`
+  //     })
+  //   }
+  //   await pp
+  // })
+})
diff --git a/packages/query/src/__tests__/query.test.txt b/packages/query/src/__tests__/query.test.txt
deleted file mode 100644
index c5fe654020..0000000000
--- a/packages/query/src/__tests__/query.test.txt
+++ /dev/null
@@ -1,263 +0,0 @@
-//
-// Copyright © 2021 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 type { Class, Client, Doc, DocumentQuery, FindOptions, FindResult, Obj, Ref, Space, Tx, TxCreateDoc } from '@anticrm/core'
-import core, { createClient, DOMAIN_TX, Hierarchy, ModelDb, TxDb, withOperations, SortingOrder } from '@anticrm/core'
-import { genMinModel as getModel } from '@anticrm/core/src/__tests__/minmodel'
-import { LiveQuery } from '..'
-import { connect } from './connection'
-
-interface Channel extends Space {
-  x: number
-}
-describe('query', () => {
-  it('findAll', async () => {
-    const client = await getClient()
-    const query = withOperations(core.account.System, new LiveQuery(client))
-    const result = await query.findAll<Space>(core.class.Space, {})
-    expect(result).toHaveLength(2)
-  })
-
-  it('query with param', async (done) => {
-    const storage = await getClient()
-
-    let expectedLength = 0
-    const txes = await getModel()
-    for (let i = 0; i < txes.length; i++) {
-      if (storage.getHierarchy().isDerived((txes[i] as TxCreateDoc<Doc>).objectClass, core.class.Space)) {
-        expectedLength++
-      }
-    }
-
-    const query = new LiveQuery(storage)
-    query.query<Space>(core.class.Space, { private: false }, (result) => {
-      expect(result).toHaveLength(expectedLength)
-      done()
-    })
-  })
-
-  it('query should be live', async (done) => {
-    const storage = await getClient()
-
-    let expectedLength = 0
-    const txes = await getModel()
-    for (let i = 0; i < txes.length; i++) {
-      if (storage.getHierarchy().isDerived((txes[i] as TxCreateDoc<Doc>).objectClass, core.class.Space)) {
-        expectedLength++
-      }
-    }
-
-    let attempt = 0
-    const query = withOperations(core.account.System, new LiveQuery(storage))
-    query.query<Space>(core.class.Space, { private: false }, (result) => {
-      expect(result).toHaveLength(expectedLength + attempt)
-      if (attempt > 0) {
-        expect((result[expectedLength + attempt - 1] as any).x).toBe(attempt)
-      }
-      if (attempt++ === 3) {
-        // check underlying storage received all data.
-        storage
-          .findAll<Space>(core.class.Space, { private: false })
-          .then((result) => {
-            expect(result).toHaveLength(expectedLength + attempt - 1)
-            done()
-          })
-          .catch((err) => expect(err).toBeUndefined())
-      }
-    })
-
-    await query.createDoc<Channel>(core.class.Space, core.space.Model, {
-      private: false,
-      name: '#1',
-      description: '',
-      members: [],
-      x: 1
-    })
-    await query.createDoc<Channel>(core.class.Space, core.space.Model, {
-      private: false,
-      name: '#2',
-      description: '',
-      members: [],
-      x: 2
-    })
-    await query.createDoc<Channel>(core.class.Space, core.space.Model, {
-      private: false,
-      name: '#3',
-      description: '',
-      members: [],
-      x: 3
-    })
-  })
-
-  it('unsubscribe query', async () => {
-    const storage = await getClient()
-
-    let expectedLength = 0
-    const txes = await getModel()
-    for (let i = 0; i < txes.length; i++) {
-      if (storage.getHierarchy().isDerived((txes[i] as TxCreateDoc<Doc>).objectClass, core.class.Space)) {
-        expectedLength++
-      }
-    }
-
-    const query = withOperations(core.account.System, new LiveQuery(storage))
-    const unsubscribe = query.query<Space>(core.class.Space, { private: false }, (result) => {
-      expect(result).toHaveLength(expectedLength)
-    })
-
-    unsubscribe()
-
-    await query.createDoc(core.class.Space, core.space.Model, {
-      private: false,
-      name: '#1',
-      description: '',
-      members: []
-    })
-    await query.createDoc(core.class.Space, core.space.Model, {
-      private: false,
-      name: '#2',
-      description: '',
-      members: []
-    })
-    await query.createDoc(core.class.Space, core.space.Model, {
-      private: false,
-      name: '#3',
-      description: '',
-      members: []
-    })
-  })
-
-  it('query against core client', async (done) => {
-    const client = await createClient(connect)
-
-    const expectedLength = 2
-    let attempt = 0
-    const query = withOperations(core.account.System, new LiveQuery(client))
-    query.query<Space>(core.class.Space, { private: false }, (result) => {
-      expect(result).toHaveLength(expectedLength + attempt)
-      if (attempt > 0) {
-        expect((result[expectedLength + attempt - 1] as any).x).toBe(attempt)
-      }
-      if (attempt++ === 1) done()
-    })
-
-    await query.createDoc<Channel>(core.class.Space, core.space.Model, {
-      x: 1,
-      private: false,
-      name: '#1',
-      description: '',
-      members: []
-    })
-    await query.createDoc<Channel>(core.class.Space, core.space.Model, {
-      x: 2,
-      private: false,
-      name: '#2',
-      description: '',
-      members: []
-    })
-    await query.createDoc<Channel>(core.class.Space, core.space.Model, {
-      x: 3,
-      private: false,
-      name: '#3',
-      description: '',
-      members: []
-    })
-  })
-
-  it('limit and sorting', async (done) => {
-    const storage = await getClient()
-
-    const limit = 1
-    let attempt = 0
-    let doneCount = 0
-
-    const query = withOperations(core.account.System, new LiveQuery(storage))
-    query.query<Space>(core.class.Space, { private: true }, (result) => {
-      if (attempt > 0 && result.length > 0) {
-        expect(result.length).toEqual(limit)
-        expect(result[0].name).toMatch('0')
-      }
-      if (attempt === 1) doneCount++
-      if (doneCount === 2) done()
-    }, { limit: limit, sort: { name: SortingOrder.Ascending } })
-
-    query.query<Space>(core.class.Space, { private: true }, (result) => {
-      if (attempt > 0 && result.length > 0) {
-        expect(result.length).toEqual(limit)
-        expect(result[0].name).toMatch(attempt.toString())
-      }
-      if (attempt === 10) doneCount++
-      if (doneCount === 2) done()
-    }, { limit: limit, sort: { name: SortingOrder.Descending } })
-
-    for (let i = 0; i < 10; i++) {
-      attempt++
-      await query.createDoc(core.class.Space, core.space.Model, {
-        private: true,
-        name: i.toString(),
-        description: '',
-        members: []
-      })
-    }
-  })
-  it('remove', async (done) => {
-    const client = await createClient(connect)
-
-    const expectedLength = 2
-    let attempt = 0
-    const query = withOperations(core.account.System, new LiveQuery(client))
-    query.query<Space>(core.class.Space, { private: false }, (result) => {
-      expect(result).toHaveLength(expectedLength - attempt)
-      if (attempt++ === expectedLength) done()
-    })
-
-    const spaces = await query.findAll(core.class.Space, {})
-    for (const space of spaces) {
-      await query.removeDoc(space._class, space.space, space._id)
-    }
-  })
-})
-
-class ClientImpl implements Client {
-  constructor (
-    private readonly hierarchy: Hierarchy,
-    private readonly model: ModelDb,
-    private readonly transactions: TxDb
-  ) {}
-
-  async tx (tx: Tx): Promise<void> {
-    await Promise.all([this.model.tx(tx), this.transactions.tx(tx)])
-  }
-
-  getHierarchy(): Hierarchy {
-    return this.hierarchy
-  }
-
-  async findAll<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<FindResult<T>> {
-    const domain = this.hierarchy.getClass(_class).domain
-    if (domain === DOMAIN_TX) return await this.transactions.findAll(_class, query, options)
-    return await this.model.findAll(_class, query, options)
-  }
-}
-
-async function getClient (): Promise<Client> {
-  const hierarchy = new Hierarchy()
-  const transactions = new TxDb(hierarchy)
-  const model = new ModelDb(hierarchy)
-  const txes = await getModel()
-  for (const tx of txes) hierarchy.tx(tx)
-  for (const tx of txes) await model.tx(tx)
-  return new ClientImpl(hierarchy, model, transactions)
-}
diff --git a/packages/query/src/index.ts b/packages/query/src/index.ts
index 00162ba009..f1543a69ab 100644
--- a/packages/query/src/index.ts
+++ b/packages/query/src/index.ts
@@ -13,12 +13,38 @@
 // limitations under the License.
 //
 
-import {
-  Ref, Class, Doc, Tx, DocumentQuery, TxCreateDoc, TxRemoveDoc, Client,
-  FindOptions, TxUpdateDoc, _getOperator, TxProcessor, resultSort, SortingQuery,
-  FindResult, Hierarchy, Refs, WithLookup, LookupData, TxMixin, TxPutBag, ModelDb, TxBulkWrite, TxResult
+import core, {
+  Ref,
+  Class,
+  Doc,
+  Tx,
+  DocumentQuery,
+  TxCreateDoc,
+  TxRemoveDoc,
+  Client,
+  FindOptions,
+  TxUpdateDoc,
+  _getOperator,
+  TxProcessor,
+  resultSort,
+  SortingQuery,
+  FindResult,
+  Hierarchy,
+  Refs,
+  WithLookup,
+  LookupData,
+  TxMixin,
+  TxPutBag,
+  ModelDb,
+  TxBulkWrite,
+  TxResult,
+  TxCollectionCUD,
+  AttachedDoc,
+  findProperty
 } from '@anticrm/core'
 
+import clone from 'just-clone'
+
 interface Query {
   _class: Ref<Class<Doc>>
   query: DocumentQuery<Doc>
@@ -51,24 +77,40 @@ export class LiveQuery extends TxProcessor implements Client {
     if (!this.getHierarchy().isDerived(doc._class, q._class)) {
       return false
     }
-    for (const key in q.query) {
-      const value = (q.query as any)[key]
-      if ((doc as any)[key] !== value) {
+    const query = q.query
+    for (const key in query) {
+      if (key === '_id' && ((query._id as any)?.$like === undefined || query._id === undefined)) continue
+      const value = (query as any)[key]
+      const result = findProperty([doc], key, value)
+      if (result.length === 0) {
         return false
       }
     }
     return true
   }
 
-  async findAll<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<FindResult<T>> {
+  async findAll<T extends Doc>(
+    _class: Ref<Class<T>>,
+    query: DocumentQuery<T>,
+    options?: FindOptions<T>
+  ): Promise<FindResult<T>> {
     return await this.client.findAll(_class, query, options)
   }
 
-  async findOne<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<WithLookup<T> | undefined> {
+  async findOne<T extends Doc>(
+    _class: Ref<Class<T>>,
+    query: DocumentQuery<T>,
+    options?: FindOptions<T>
+  ): Promise<WithLookup<T> | undefined> {
     return (await this.findAll(_class, query, options))[0]
   }
 
-  query<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, callback: (result: T[]) => void, options?: FindOptions<T>): () => void {
+  query<T extends Doc>(
+    _class: Ref<Class<T>>,
+    query: DocumentQuery<T>,
+    callback: (result: T[]) => void,
+    options?: FindOptions<T>
+  ): () => void {
     const result = this.client.findAll(_class, query, options)
     const q: Query = {
       _class,
@@ -96,7 +138,7 @@ export class LiveQuery extends TxProcessor implements Client {
       if (q.result instanceof Promise) {
         q.result = await q.result
       }
-      const updatedDoc = q.result.find(p => p._id === tx.objectId)
+      const updatedDoc = q.result.find((p) => p._id === tx.objectId)
       if (updatedDoc !== undefined) {
         const doc = updatedDoc as any
         let bag = doc[tx.bag]
@@ -114,80 +156,169 @@ export class LiveQuery extends TxProcessor implements Client {
     throw new Error('Method not implemented.')
   }
 
-  protected async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult> {
-    console.log(`updating ${this.queries.length} queries`)
+  protected async txCollectionCUD (tx: TxCollectionCUD<Doc, AttachedDoc>): Promise<TxResult> {
     for (const q of this.queries) {
-      if (q.result instanceof Promise) {
-        q.result = await q.result
+      if (this.client.getHierarchy().isDerived(q._class, core.class.Tx)) {
+        // handle add since Txes are immutable
+        await this.handleDocAdd(q, tx)
+        continue
       }
-      const updatedDoc = q.result.find(p => p._id === tx.objectId)
-      if (updatedDoc !== undefined) {
-        await this.__updateDoc(q, updatedDoc, tx)
-        this.sort(q, tx)
-        await this.callback(updatedDoc, q)
+
+      if (tx.tx._class === core.class.TxCreateDoc) {
+        const createTx = tx.tx as TxCreateDoc<AttachedDoc>
+        const d: TxCreateDoc<AttachedDoc> = {
+          ...createTx,
+          attributes: {
+            ...createTx.attributes,
+            attachedTo: tx.objectId,
+            attachedToClass: tx.objectClass,
+            collection: tx.collection
+          }
+        }
+        await this.handleDocAdd(q, TxProcessor.createDoc2Doc(d))
+      } else if (tx.tx._class === core.class.TxUpdateDoc) {
+        await this.handleDocUpdate(q, tx.tx as unknown as TxUpdateDoc<Doc>)
+      } else if (tx.tx._class === core.class.TxRemoveDoc) {
+        await this.handleDocRemove(q, tx.tx as unknown as TxRemoveDoc<Doc>)
       }
     }
     return {}
   }
 
+  protected async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult> {
+    for (const q of this.queries) {
+      if (this.client.getHierarchy().isDerived(q._class, core.class.Tx)) {
+        // handle add since Txes are immutable
+        await this.handleDocAdd(q, tx)
+        continue
+      }
+      await this.handleDocUpdate(q, tx)
+    }
+    return {}
+  }
+
+  private async handleDocUpdate (q: Query, tx: TxUpdateDoc<Doc>): Promise<void> {
+    if (q.result instanceof Promise) {
+      q.result = await q.result
+    }
+    const pos = q.result.findIndex((p) => p._id === tx.objectId)
+    if (pos !== -1) {
+      const updatedDoc = q.result[pos]
+      await this.__updateDoc(q, updatedDoc, tx)
+      if (!this.match(q, updatedDoc)) {
+        q.result.splice(pos, 1)
+      } else {
+        q.result[pos] = updatedDoc
+      }
+      this.sort(q, tx)
+      await this.callback(updatedDoc, q)
+    } else if (this.matchQuery(q, tx)) {
+      await this.refresh(q)
+    }
+  }
+
+  private async refresh (q: Query): Promise<void> {
+    const res = await this.client.findAll(q._class, q.query, q.options)
+    q.result = res
+    q.callback(clone(res))
+  }
+
+  // Check if query is partially matched.
+  private matchQuery (q: Query, tx: TxUpdateDoc<Doc>): boolean {
+    if (!this.client.getHierarchy().isDerived(q._class, tx.objectClass)) {
+      return false
+    }
+
+    for (const key in q.query) {
+      const value = (q.query as any)[key]
+      const res = findProperty([tx.operations as unknown as Doc], key, value)
+      if (res.length === 1) {
+        return true
+      }
+    }
+    return false
+  }
+
   private async lookup (doc: Doc, lookup: Refs<Doc>): Promise<void> {
     const result: LookupData<Doc> = {}
     for (const key in lookup) {
       const _class = (lookup as any)[key] as Ref<Class<Doc>>
       const _id = (doc as any)[key] as Ref<Doc>
-      (result as any)[key] = (await this.client.findAll(_class, { _id }))[0]
+      ;(result as any)[key] = (await this.client.findAll(_class, { _id }))[0]
     }
-    (doc as WithLookup<Doc>).$lookup = result
+    ;(doc as WithLookup<Doc>).$lookup = result
   }
 
   protected async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
-    console.log('query tx', tx)
+    const docTx = TxProcessor.createDoc2Doc(tx)
     for (const q of this.queries) {
-      const doc = TxProcessor.createDoc2Doc(tx)
-      if (this.match(q, doc)) {
-        if (q.result instanceof Promise) {
-          q.result = await q.result
-        }
-
-        if (q.options?.lookup !== undefined) await this.lookup(doc, q.options.lookup)
-
-        q.result.push(doc)
-
-        if (q.options?.sort !== undefined) resultSort(q.result, q.options?.sort)
-
-        if (q.options?.limit !== undefined && q.result.length > q.options.limit) {
-          if (q.result.pop()?._id !== doc._id) {
-            q.callback(q.result)
-          }
-        } else {
-          q.callback(q.result)
-        }
-      }
+      const doc = this.client.getHierarchy().isDerived(q._class, core.class.Tx) ? tx : docTx
+      await this.handleDocAdd(q, doc)
     }
     return {}
   }
 
+  private async handleDocAdd (q: Query, doc: Doc): Promise<void> {
+    if (this.match(q, doc)) {
+      if (q.result instanceof Promise) {
+        q.result = await q.result
+      }
+
+      if (q.options?.lookup !== undefined) {
+        await this.lookup(doc, q.options.lookup)
+      }
+
+      q.result.push(doc)
+
+      if (q.options?.sort !== undefined) {
+        resultSort(q.result, q.options?.sort)
+      }
+
+      if (q.options?.limit !== undefined && q.result.length > q.options.limit) {
+        if (q.result.pop()?._id !== doc._id) {
+          q.callback(clone(q.result))
+        }
+      } else {
+        q.callback(clone(q.result))
+      }
+    }
+  }
+
   protected async txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<TxResult> {
     for (const q of this.queries) {
-      if (q.result instanceof Promise) {
-        q.result = await q.result
-      }
-      const index = q.result.findIndex(p => p._id === tx.objectId)
-      if (index > -1) {
-        q.result.splice(index, 1)
-        q.callback(q.result)
+      if (this.client.getHierarchy().isDerived(q._class, core.class.Tx)) {
+        // handle add since Txes are immutable
+        await this.handleDocAdd(q, tx)
+        continue
       }
+      await this.handleDocRemove(q, tx)
     }
     return {}
   }
 
+  private async handleDocRemove (q: Query, tx: TxRemoveDoc<Doc>): Promise<void> {
+    if (q.result instanceof Promise) {
+      q.result = await q.result
+    }
+    const index = q.result.findIndex((p) => p._id === tx.objectId)
+    if (
+      q.options?.limit !== undefined &&
+      q.options.limit === q.result.length &&
+      this.client.getHierarchy().isDerived(q._class, tx.objectClass)
+    ) {
+      return await this.refresh(q)
+    }
+    if (index > -1) {
+      q.result.splice(index, 1)
+      q.callback(clone(q.result))
+    }
+  }
+
   protected override async txBulkWrite (tx: TxBulkWrite): Promise<TxResult> {
-    console.log('query: bulk')
     return await super.txBulkWrite(tx)
   }
 
   async tx (tx: Tx): Promise<TxResult> {
-    console.log('query tx', tx)
     return await super.tx(tx)
   }
 
@@ -199,11 +330,11 @@ export class LiveQuery extends TxProcessor implements Client {
         const operator = _getOperator(key)
         operator(updatedDoc, ops[key])
       } else {
-        (updatedDoc as any)[key] = ops[key]
+        ;(updatedDoc as any)[key] = ops[key]
         if (q.options !== undefined) {
           const lookup = (q.options.lookup as any)?.[key]
           if (lookup !== undefined) {
-            (updatedDoc.$lookup as any)[key] = await this.client.findOne(lookup, { _id: ops[key] })
+            ;(updatedDoc.$lookup as any)[key] = await this.client.findOne(lookup, { _id: ops[key] })
           }
         }
       }
@@ -240,14 +371,11 @@ export class LiveQuery extends TxProcessor implements Client {
 
     if (q.options?.limit !== undefined && q.result.length > q.options.limit) {
       if (q.result[q.options?.limit]._id === updatedDoc._id) {
-        const res = await this.findAll(q._class, q.query, q.options)
-        q.result = res
-        q.callback(res)
-        return
+        return await this.refresh(q)
       }
       if (q.result.pop()?._id !== updatedDoc._id) q.callback(q.result)
     } else {
-      q.callback(q.result)
+      q.callback(clone(q.result))
     }
   }
 }
diff --git a/packages/query/tsconfig.json b/packages/query/tsconfig.json
index aeb0517b13..137ecf0617 100644
--- a/packages/query/tsconfig.json
+++ b/packages/query/tsconfig.json
@@ -3,6 +3,7 @@
 
   "compilerOptions": {
     "rootDir": "./src",
-    "outDir": "./lib"
+    "outDir": "./lib",
+    "esModuleInterop": true
   }
-}
\ No newline at end of file
+}