mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Generated
+43
-3
@@ -2,6 +2,7 @@ lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
'@microsoft/api-extractor': ~7.18.4
|
||||
'@rush-temp/core': file:./projects/core.tgz
|
||||
'@rush-temp/platform': file:./projects/platform.tgz
|
||||
'@rush-temp/platform-rig': file:./projects/platform-rig.tgz
|
||||
'@rushstack/heft': ^0.35.0
|
||||
@@ -15,12 +16,14 @@ specifiers:
|
||||
eslint-plugin-node: '11'
|
||||
eslint-plugin-promise: '4'
|
||||
intl-messageformat: ^9.7.1
|
||||
simplytyped: ^3.3.0
|
||||
typescript: ^4.3.5
|
||||
|
||||
dependencies:
|
||||
'@microsoft/api-extractor': 7.18.4
|
||||
'@rush-temp/core': file:projects/core.tgz
|
||||
'@rush-temp/platform': file:projects/platform.tgz
|
||||
'@rush-temp/platform-rig': file:projects/platform-rig.tgz
|
||||
'@rush-temp/platform-rig': file:projects/platform-rig.tgz_eslint@7.32.0+typescript@4.3.5
|
||||
'@rushstack/heft': 0.35.0
|
||||
'@rushstack/heft-jest-plugin': 0.1.15_@rushstack+heft@0.35.0
|
||||
'@types/heft-jest': 1.0.2
|
||||
@@ -32,6 +35,7 @@ dependencies:
|
||||
eslint-plugin-node: 11.1.0_eslint@7.32.0
|
||||
eslint-plugin-promise: 4.3.1
|
||||
intl-messageformat: 9.8.1
|
||||
simplytyped: 3.3.0_typescript@4.3.5
|
||||
typescript: 4.3.5
|
||||
|
||||
packages:
|
||||
@@ -4452,6 +4456,14 @@ packages:
|
||||
resolution: {integrity: sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==}
|
||||
dev: false
|
||||
|
||||
/simplytyped/3.3.0_typescript@4.3.5:
|
||||
resolution: {integrity: sha512-mz4RaNdKTZiaKXgi6P1k/cdsxV3gz+y1Wh2NXHWD40dExktLh4Xx/h6MFakmQWODZHj/2rKe59acacpL74ZhQA==}
|
||||
peerDependencies:
|
||||
typescript: '>=2.8.0'
|
||||
dependencies:
|
||||
typescript: 4.3.5
|
||||
dev: false
|
||||
|
||||
/slash/3.0.0:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -5154,18 +5166,46 @@ packages:
|
||||
commander: 2.20.3
|
||||
dev: false
|
||||
|
||||
file:projects/platform-rig.tgz:
|
||||
resolution: {integrity: sha512-bUPKt7uRIPzuShO2D4ncHYn9lqkg7S5IyQkzdL3O06sT027+wkP8SlGdBWPumU/GiPOOB6WIeG4M1/eCjpZvEA==, tarball: file:projects/platform-rig.tgz}
|
||||
file:projects/core.tgz:
|
||||
resolution: {integrity: sha512-t+dgsEkYQSoo/p/wy3ZsCCCL5beZ2p7shTsTSAcvssigjlTIBoCKjnukzHLTAiVdRMwxsObAIf8GXEmtRteyvg==, tarball: file:projects/core.tgz}
|
||||
name: '@rush-temp/core'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/heft-jest': 1.0.2
|
||||
'@typescript-eslint/eslint-plugin': 4.28.5_a8e83fcad666e1ba86be4b2e27a20aea
|
||||
'@typescript-eslint/parser': 4.28.5_eslint@7.32.0+typescript@4.3.5
|
||||
eslint: 7.32.0
|
||||
eslint-config-standard-with-typescript: 20.0.0_2e482f375e273d762fe67cbd5e194b49
|
||||
eslint-plugin-import: 2.23.4_eslint@7.32.0
|
||||
eslint-plugin-node: 11.1.0_eslint@7.32.0
|
||||
eslint-plugin-promise: 4.3.1
|
||||
simplytyped: 3.3.0_typescript@4.3.5
|
||||
typescript: 4.3.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/platform-rig.tgz_eslint@7.32.0+typescript@4.3.5:
|
||||
resolution: {integrity: sha512-TKHuAhvUIcQRtvjoXmexJXdGD9HFljUddiThWwSleLTosIPsXNSUr+88F4V4a1pz/HQV1KpiPchn5HxTXFZ8GA==, tarball: file:projects/platform-rig.tgz}
|
||||
id: file:projects/platform-rig.tgz
|
||||
name: '@rush-temp/platform-rig'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@microsoft/api-extractor': 7.18.4
|
||||
'@rushstack/heft': 0.35.0
|
||||
'@rushstack/heft-jest-plugin': 0.1.15_@rushstack+heft@0.35.0
|
||||
'@typescript-eslint/eslint-plugin': 4.28.5_a8e83fcad666e1ba86be4b2e27a20aea
|
||||
'@typescript-eslint/parser': 4.28.5_eslint@7.32.0+typescript@4.3.5
|
||||
eslint-config-standard-with-typescript: 20.0.0_2e482f375e273d762fe67cbd5e194b49
|
||||
eslint-plugin-import: 2.23.4_eslint@7.32.0
|
||||
eslint-plugin-node: 11.1.0_eslint@7.32.0
|
||||
eslint-plugin-promise: 4.3.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- canvas
|
||||
- eslint
|
||||
- supports-color
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
|
||||
parserOptions: {
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// The "rig.json" file directs tools to look for their config files in an external package.
|
||||
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
|
||||
/**
|
||||
* (Required) The name of the rig package to inherit from.
|
||||
* It should be an NPM package name with the "-rig" suffix.
|
||||
*/
|
||||
"rigPackageName": "@anticrm/platform-rig"
|
||||
|
||||
/**
|
||||
* (Optional) Selects a config profile from the rig package. The name must consist of
|
||||
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
|
||||
* If omitted, then the "default" profile will be used."
|
||||
*/
|
||||
// "rigProfile": "your-profile-name"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@anticrm/core",
|
||||
"version": "0.5.0",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "heft build",
|
||||
"build:docs": "api-extractor run --local",
|
||||
"test": "jest",
|
||||
"lint": "ts-standard src",
|
||||
"lint:fix": "eslint --fix ./src",
|
||||
"format": "prettier --write 'src/**/*.{ts*,js*,yml}' && ts-standard --fix src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anticrm/platform-rig":"~0.5.0",
|
||||
"@types/heft-jest":"^1.0.2",
|
||||
"eslint-plugin-promise":"4",
|
||||
"eslint-plugin-import":"2",
|
||||
"eslint-plugin-node":"11",
|
||||
"@typescript-eslint/eslint-plugin":"4",
|
||||
"typescript":"^4.3.5",
|
||||
"eslint":"^7.32.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser":"^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anticrm/platform": "~0.5.0",
|
||||
"simplytyped": "^3.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering, Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Space } from '../classes'
|
||||
import { createClient } from '../client'
|
||||
import core from '../component'
|
||||
import { withOperations } from '../tx'
|
||||
import { connect } from './connection'
|
||||
|
||||
describe('client', () => {
|
||||
it('client', async () => {
|
||||
const klass = core.class.Space
|
||||
const client = withOperations(core.account.System, await createClient(connect))
|
||||
const result = await client.findAll(klass, {})
|
||||
expect(result).toHaveLength(2)
|
||||
|
||||
await client.createDoc<Space>(klass, core.space.Model, {
|
||||
private: false,
|
||||
name: 'NewSpace',
|
||||
description: '',
|
||||
members: []
|
||||
})
|
||||
const result2 = await client.findAll(klass, {})
|
||||
expect(result2).toHaveLength(3)
|
||||
|
||||
await client.createDoc(klass, core.space.Model, { private: false, name: 'NewSpace', description: '', members: [] })
|
||||
const result3 = await client.findAll(klass, {})
|
||||
expect(result3).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// 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 { Storage, DocumentQuery, FindResult } from '../storage'
|
||||
import type { Class, Doc, Ref, Tx } from '../classes'
|
||||
import core from '../component'
|
||||
import { Hierarchy } from '../hierarchy'
|
||||
import { ModelDb, TxDb } from '../memdb'
|
||||
import { DOMAIN_TX } from '../tx'
|
||||
import { genMinModel } from './minmodel'
|
||||
|
||||
export async function connect (handler: (tx: Tx) => void): Promise<Storage> {
|
||||
const txes = genMinModel()
|
||||
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
|
||||
const transactions = new TxDb(hierarchy)
|
||||
const model = new ModelDb(hierarchy)
|
||||
for (const tx of txes) {
|
||||
await transactions.tx(tx)
|
||||
await model.tx(tx)
|
||||
}
|
||||
|
||||
async function findAll<T extends Doc> (_class: Ref<Class<T>>, query: DocumentQuery<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)
|
||||
}
|
||||
|
||||
return {
|
||||
findAll,
|
||||
tx: async (tx: Tx): Promise<void> => {
|
||||
if (tx.objectSpace === core.space.Model) {
|
||||
hierarchy.tx(tx)
|
||||
}
|
||||
await Promise.all([model.tx(tx), transactions.tx(tx)])
|
||||
handler(tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// 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 { Class, Doc, Obj, Ref, TxCreateDoc } from '../classes'
|
||||
import core from '../component'
|
||||
import { Hierarchy } from '../hierarchy'
|
||||
import { genMinModel } from './minmodel'
|
||||
|
||||
const txes = genMinModel()
|
||||
|
||||
describe('hierarchy', () => {
|
||||
it('should build hierarchy', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const ancestors = hierarchy.getAncestors(core.class.TxCreateDoc)
|
||||
expect(ancestors).toContain(core.class.Tx)
|
||||
})
|
||||
|
||||
it('isDerived', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const derived = hierarchy.isDerived(core.class.Space, core.class.Doc)
|
||||
expect(derived).toBeTruthy()
|
||||
const notDerived = hierarchy.isDerived(core.class.Space, core.class.Class)
|
||||
expect(notDerived).not.toBeTruthy()
|
||||
})
|
||||
|
||||
it('getClass', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const data = hierarchy.getClass(core.class.TxCreateDoc)
|
||||
expect(data).toMatchObject((txes.find((p) => p.objectId === core.class.TxCreateDoc) as TxCreateDoc<Doc>).attributes)
|
||||
const notExistClass = 'class:test.MyClass' as Ref<Class<Obj>>
|
||||
expect(() => hierarchy.getClass(notExistClass)).toThrowError('class not found: ' + notExistClass)
|
||||
})
|
||||
|
||||
it('getDomain', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const txDomain = hierarchy.getDomain(core.class.TxCreateDoc)
|
||||
expect(txDomain).toBe('tx')
|
||||
const modelDomain = hierarchy.getDomain(core.class.Class)
|
||||
expect(modelDomain).toBe('model')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// 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 { Class, Doc, Obj, Ref } from '../classes'
|
||||
import core from '../component'
|
||||
import { Hierarchy } from '../hierarchy'
|
||||
import { ModelDb, TxDb } from '../memdb'
|
||||
import { SortingOrder } from '../storage'
|
||||
import { withOperations } from '../tx'
|
||||
import { genMinModel } from './minmodel'
|
||||
|
||||
const txes = genMinModel()
|
||||
|
||||
describe('memdb', () => {
|
||||
it('should save all tx', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const txDb = new TxDb(hierarchy)
|
||||
for (const tx of txes) await txDb.tx(tx)
|
||||
const result = await txDb.findAll(core.class.Tx, {})
|
||||
expect(result.length).toBe(txes.filter((tx) => tx._class === core.class.TxCreateDoc).length)
|
||||
})
|
||||
|
||||
it('should query model', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) await hierarchy.tx(tx)
|
||||
const model = new ModelDb(hierarchy)
|
||||
for (const tx of txes) await model.tx(tx)
|
||||
const result = await model.findAll(core.class.Class, {})
|
||||
expect(result.length).toBeGreaterThan(5)
|
||||
const result2 = await model.findAll('class:workbench.Application' as Ref<Class<Doc>>, { _id: undefined })
|
||||
expect(result2).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should allow delete', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) await hierarchy.tx(tx)
|
||||
const model = new ModelDb(hierarchy)
|
||||
for (const tx of txes) await model.tx(tx)
|
||||
const result = await model.findAll(core.class.Space, {})
|
||||
expect(result.length).toBe(2)
|
||||
|
||||
const ops = withOperations(core.account.System, model)
|
||||
await ops.removeDoc(result[0]._class, result[0].space, result[0]._id)
|
||||
const result2 = await model.findAll(core.class.Space, {})
|
||||
expect(result2).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should query model with params', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const model = new ModelDb(hierarchy)
|
||||
for (const tx of txes) await model.tx(tx)
|
||||
const first = await model.findAll(core.class.Class, {
|
||||
_id: txes[1].objectId as Ref<Class<Obj>>,
|
||||
kind: 0
|
||||
})
|
||||
expect(first.length).toBe(1)
|
||||
const second = await model.findAll(core.class.Class, {
|
||||
_id: { $in: [txes[1].objectId as Ref<Class<Obj>>, txes[3].objectId as Ref<Class<Obj>>] }
|
||||
})
|
||||
expect(second.length).toBe(2)
|
||||
const incorrectId = await model.findAll(core.class.Class, {
|
||||
_id: (txes[1].objectId + 'test') as Ref<Class<Obj>>
|
||||
})
|
||||
expect(incorrectId.length).toBe(0)
|
||||
const result = await model.findAll(core.class.Class, {
|
||||
_id: txes[1].objectId as Ref<Class<Obj>>,
|
||||
kind: 1
|
||||
})
|
||||
expect(result.length).toBe(0)
|
||||
const multipleParam = await model.findAll(core.class.Doc, {
|
||||
space: { $in: [core.space.Model, core.space.Tx] }
|
||||
})
|
||||
expect(multipleParam.length).toBeGreaterThan(5)
|
||||
})
|
||||
|
||||
it('should query model like params', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const model = new ModelDb(hierarchy)
|
||||
for (const tx of txes) await model.tx(tx)
|
||||
const expectedLength = txes.filter((tx) => tx.objectSpace === core.space.Model).length
|
||||
const without = await model.findAll(core.class.Doc, {
|
||||
space: { $like: core.space.Model }
|
||||
})
|
||||
expect(without).toHaveLength(expectedLength)
|
||||
const begin = await model.findAll(core.class.Doc, {
|
||||
space: { $like: '%Model' }
|
||||
})
|
||||
expect(begin).toHaveLength(expectedLength)
|
||||
const zero = await model.findAll(core.class.Doc, {
|
||||
space: { $like: 'Model' }
|
||||
})
|
||||
expect(zero).toHaveLength(0)
|
||||
const end = await model.findAll(core.class.Doc, {
|
||||
space: { $like: 'core.space.M%' }
|
||||
})
|
||||
expect(end).toHaveLength(expectedLength)
|
||||
const mid = await model.findAll(core.class.Doc, {
|
||||
space: { $like: '%M%de%' }
|
||||
})
|
||||
expect(mid).toHaveLength(expectedLength)
|
||||
const all = await model.findAll(core.class.Doc, {
|
||||
space: { $like: '%Mod%' }
|
||||
})
|
||||
expect(all).toHaveLength(expectedLength)
|
||||
})
|
||||
|
||||
it('should push to array', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) await hierarchy.tx(tx)
|
||||
const model = withOperations(core.account.System, new ModelDb(hierarchy))
|
||||
for (const tx of txes) await model.tx(tx)
|
||||
const space = await model.createDoc(core.class.Space, core.space.Model, {
|
||||
name: 'name',
|
||||
description: 'desc',
|
||||
private: false,
|
||||
members: []
|
||||
})
|
||||
const account = await model.createDoc(core.class.Account, core.space.Model, {})
|
||||
await model.updateDoc(core.class.Space, core.space.Model, space._id, { $push: { members: account._id } })
|
||||
const txSpace = await model.findAll(core.class.Space, { _id: space._id })
|
||||
expect(txSpace[0].members).toEqual(expect.arrayContaining([account._id]))
|
||||
})
|
||||
|
||||
it('limit and sorting', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const model = withOperations(core.account.System, new ModelDb(hierarchy))
|
||||
for (const tx of txes) await model.tx(tx)
|
||||
|
||||
const without = await model.findAll(core.class.Space, {})
|
||||
expect(without).toHaveLength(2)
|
||||
|
||||
const limit = await model.findAll(core.class.Space, {}, { limit: 1 })
|
||||
expect(limit).toHaveLength(1)
|
||||
|
||||
const sortAsc = await model.findAll(core.class.Space, {}, { limit: 1, sort: { name: SortingOrder.Ascending } })
|
||||
expect(sortAsc[0].name).toMatch('Sp1')
|
||||
|
||||
const sortDesc = await model.findAll(core.class.Space, {}, { limit: 1, sort: { name: SortingOrder.Descending } })
|
||||
expect(sortDesc[0].name).toMatch('Sp2')
|
||||
|
||||
const numberSortDesc = await model.findAll(core.class.Doc, {}, { sort: { modifiedOn: SortingOrder.Descending } })
|
||||
expect(numberSortDesc[0].modifiedOn).toBeGreaterThanOrEqual(numberSortDesc[numberSortDesc.length - 1].modifiedOn)
|
||||
|
||||
const numberSort = await model.findAll(core.class.Doc, {}, { sort: { modifiedOn: SortingOrder.Ascending } })
|
||||
expect(numberSort[0].modifiedOn).toBeLessThanOrEqual(numberSort[numberSortDesc.length - 1].modifiedOn)
|
||||
})
|
||||
|
||||
// it('should throw error', async () => {
|
||||
// expect.assertions(1)
|
||||
// const errorTx: TxAddCollection<Doc, Emb> = {
|
||||
// _id: '60b73133d22498e666800cd2' as Ref<TxAddCollection<Doc, Emb>>,
|
||||
// _class: 'class:core.TxAddCollection' as Ref<Class<TxAddCollection<Doc, Emb>>>,
|
||||
// space: core.space.Tx,
|
||||
// modifiedBy: 'xxx' as Ref<Account>,
|
||||
// modifiedOn: 0,
|
||||
// objectId: 'class:test.MyClass' as Ref<Doc>,
|
||||
// objectSpace: core.space.Model,
|
||||
// itemClass: 'class:core.Attribute' as Ref<Class<Doc>>,
|
||||
// collection: 'attributes',
|
||||
// localId: 'name',
|
||||
// attributes: {
|
||||
// _class: 'class:core.Attribute' as Ref<Class<Doc>>,
|
||||
// // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
// __embedded: {
|
||||
// _class: 'class:core.Attribute' as Ref<Class<Doc>>
|
||||
// } as Emb
|
||||
// }
|
||||
// }
|
||||
|
||||
// const hierarchy = new Hierarchy()
|
||||
// for (const tx of txes) hierarchy.tx(tx)
|
||||
// const model = new ModelDb(hierarchy)
|
||||
|
||||
// await model.tx(errorTx).catch((error: Error) => {
|
||||
// expect(error.message).toBe('ERROR: status:core.ObjectNotFound')
|
||||
// })
|
||||
// })
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Account, Class, ClassifierKind, Data, Doc, Domain, DOMAIN_MODEL, Obj, Ref, Tx, TxCreateDoc } from '../classes'
|
||||
import core from '../component'
|
||||
import { DOMAIN_TX } from '../tx'
|
||||
import { generateId } from '../utils'
|
||||
|
||||
export function createClass<T extends Class<Obj>> (_id: Ref<T>, cl: Omit<Data<T>, 'kind'>, domain?: Domain): Tx {
|
||||
const result: TxCreateDoc<Doc> = {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectId: _id,
|
||||
objectClass: core.class.Class,
|
||||
attributes: {
|
||||
kind: ClassifierKind.CLASS,
|
||||
domain: domain ?? DOMAIN_MODEL,
|
||||
...cl
|
||||
},
|
||||
modifiedBy: 'model' as Ref<Account>,
|
||||
modifiedOn: Date.now(),
|
||||
objectSpace: core.space.Model,
|
||||
space: core.space.Model
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function createDoc<T extends Doc> (_class: Ref<Class<T>>, attributes: Data<T>): Tx {
|
||||
const tx: TxCreateDoc<T> = {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxCreateDoc,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: core.account.System,
|
||||
modifiedOn: Date.now(),
|
||||
objectId: generateId(),
|
||||
objectClass: _class,
|
||||
objectSpace: core.space.Model,
|
||||
attributes
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate minimal model for testing purposes.
|
||||
* @returns R
|
||||
*/
|
||||
export function genMinModel (): Tx[] {
|
||||
const txes = []
|
||||
// Fill Tx'es with basic model classes.
|
||||
txes.push(createClass(core.class.Obj, {}))
|
||||
txes.push(createClass(core.class.Doc, { extends: core.class.Obj }))
|
||||
txes.push(createClass(core.class.Class, { extends: core.class.Doc }))
|
||||
txes.push(createClass(core.class.Space, { extends: core.class.Doc }))
|
||||
txes.push(createClass(core.class.Account, { extends: core.class.Doc }))
|
||||
|
||||
txes.push(createClass(core.class.Tx, { extends: core.class.Doc }, DOMAIN_TX))
|
||||
txes.push(createClass(core.class.TxCreateDoc, { extends: core.class.Tx }, DOMAIN_TX))
|
||||
txes.push(createClass(core.class.TxUpdateDoc, { extends: core.class.Tx }, DOMAIN_TX))
|
||||
txes.push(createClass(core.class.TxRemoveDoc, { extends: core.class.Tx }, DOMAIN_TX))
|
||||
|
||||
txes.push(
|
||||
createDoc(core.class.Space, {
|
||||
name: 'Sp1',
|
||||
description: '',
|
||||
private: false,
|
||||
members: []
|
||||
})
|
||||
)
|
||||
|
||||
txes.push(
|
||||
createDoc(core.class.Space, {
|
||||
name: 'Sp2',
|
||||
description: '',
|
||||
private: false,
|
||||
members: []
|
||||
})
|
||||
)
|
||||
return txes
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// Copyright © 2020, 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 { IntlString, Asset, Resource } from '@anticrm/platform'
|
||||
|
||||
export type Ref<T extends Doc> = string & { __ref: T }
|
||||
export type PrimitiveType = number | string | boolean | undefined | Ref<Doc>
|
||||
export type Timestamp = number
|
||||
|
||||
export interface Obj {
|
||||
_class: Ref<Class<this>>
|
||||
}
|
||||
|
||||
export interface Doc extends Obj {
|
||||
_id: Ref<this>
|
||||
space: Ref<Space>
|
||||
modifiedOn: Timestamp
|
||||
modifiedBy: Ref<Account>
|
||||
}
|
||||
|
||||
export type PropertyType = any
|
||||
|
||||
export interface UXObject extends Obj {
|
||||
label?: IntlString
|
||||
icon?: Asset
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export interface Type<T extends PropertyType> extends UXObject {}
|
||||
|
||||
export interface Attribute<T extends PropertyType> extends Doc, UXObject {
|
||||
attributeOf: Ref<Class<Obj>>
|
||||
name: string
|
||||
type: Type<T>
|
||||
}
|
||||
|
||||
export type AnyAttribute = Attribute<Type<any>>
|
||||
|
||||
export enum ClassifierKind {
|
||||
CLASS,
|
||||
MIXIN
|
||||
}
|
||||
|
||||
export interface Classifier extends Doc, UXObject {
|
||||
kind: ClassifierKind
|
||||
}
|
||||
|
||||
export type Domain = string & { __domain: true }
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export interface Class<T extends Obj> extends Classifier {
|
||||
extends?: Ref<Class<Obj>>
|
||||
domain?: Domain
|
||||
triggers?: Trigger[]
|
||||
}
|
||||
|
||||
export type Mixin<T extends Doc> = Class<T>
|
||||
|
||||
// D A T A
|
||||
|
||||
export type Data<T extends Doc> = Omit<T, keyof Doc>
|
||||
|
||||
// T Y P E S
|
||||
|
||||
export interface RefTo<T extends Doc> extends Type<Ref<Class<T>>> {
|
||||
to: Ref<Class<T>>
|
||||
}
|
||||
|
||||
export type Bag<T extends PropertyType> = Record<string, T>
|
||||
|
||||
export interface BagOf<T extends PropertyType> extends Type<Bag<T>> {
|
||||
of: Type<T>
|
||||
}
|
||||
|
||||
export type Arr<T extends PropertyType> = T[]
|
||||
|
||||
export interface ArrOf<T extends PropertyType> extends Type<T[]> {
|
||||
of: Type<T>
|
||||
}
|
||||
|
||||
export const DOMAIN_MODEL = 'model' as Domain
|
||||
|
||||
// S E C U R I T Y
|
||||
|
||||
export interface Space extends Doc {
|
||||
name: string
|
||||
description: string
|
||||
private: boolean
|
||||
members: Arr<Ref<Account>>
|
||||
}
|
||||
|
||||
export interface Account extends Doc {}
|
||||
|
||||
// T X
|
||||
|
||||
export interface TxFactory {
|
||||
createTxCreateDoc: <T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, attributes: Data<T>) => TxCreateDoc<T>
|
||||
createTxMixin: <D extends Doc, M extends D>(objectId: Ref<D>, objectClass: Ref<Class<D>>, mixin: Ref<Mixin<M>>, attributes: ExtendedAttributes<D, M>) => TxMixin<D, M>
|
||||
}
|
||||
|
||||
export type Trigger = Resource<(tx: Tx, txFactory: TxFactory) => Promise<Tx[]>>
|
||||
|
||||
export interface Tx<T extends Doc = Doc> extends Doc {
|
||||
objectId: Ref<T>
|
||||
objectClass: Ref<Class<T>>
|
||||
objectSpace: Ref<Space>
|
||||
}
|
||||
|
||||
export interface TxCreateDoc<T extends Doc> extends Tx<T> {
|
||||
attributes: Data<T>
|
||||
}
|
||||
|
||||
export type ExtendedAttributes<D extends Doc, M extends D> = Omit<M, keyof D>
|
||||
|
||||
export interface TxMixin<D extends Doc, M extends D> extends Tx<D> {
|
||||
mixin: Ref<Mixin<M>>
|
||||
attributes: ExtendedAttributes<D, M>
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// 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 { Doc, Ref, Class, Tx } from './classes'
|
||||
import type { Storage, DocumentQuery, FindOptions, FindResult } from './storage'
|
||||
|
||||
import { Hierarchy } from './hierarchy'
|
||||
import { ModelDb } from './memdb'
|
||||
import { DOMAIN_MODEL } from './classes'
|
||||
import { TxProcessor } from './tx'
|
||||
|
||||
import core from './component'
|
||||
|
||||
type TxHander = (tx: Tx) => void
|
||||
|
||||
export interface Client extends Storage {
|
||||
getHierarchy: () => Hierarchy
|
||||
}
|
||||
|
||||
class ClientImpl extends TxProcessor implements Storage, Client {
|
||||
constructor (private readonly hierarchy: Hierarchy, private readonly model: ModelDb, private readonly conn: Storage) {
|
||||
super()
|
||||
}
|
||||
|
||||
getHierarchy (): Hierarchy { return this.hierarchy }
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<FindResult<T>> {
|
||||
const clazz = this.hierarchy.getClass(_class)
|
||||
if (clazz.domain === DOMAIN_MODEL) {
|
||||
return await this.model.findAll(_class, query, options)
|
||||
}
|
||||
return await this.conn.findAll(_class, query, options)
|
||||
}
|
||||
|
||||
async tx (tx: Tx): Promise<void> {
|
||||
await this.conn.tx(tx)
|
||||
}
|
||||
}
|
||||
|
||||
export async function createClient (
|
||||
connect: (txHandler: TxHander) => Promise<Storage>,
|
||||
notify?: (tx: Tx) => void
|
||||
): Promise<Client> {
|
||||
let client: Client | null = null
|
||||
let txBuffer: Tx[] | undefined = []
|
||||
|
||||
const hierarchy = new Hierarchy()
|
||||
const model = new ModelDb(hierarchy)
|
||||
|
||||
function txHander (tx: Tx): void {
|
||||
if (client === null) {
|
||||
txBuffer?.push(tx)
|
||||
} else {
|
||||
if (tx.objectSpace === core.space.Model) {
|
||||
hierarchy.tx(tx)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
model.tx(tx)
|
||||
}
|
||||
notify?.(tx)
|
||||
}
|
||||
}
|
||||
|
||||
const conn = await connect(txHander)
|
||||
const txes = await conn.findAll(core.class.Tx, { objectSpace: core.space.Model })
|
||||
|
||||
const txMap = new Map<Ref<Tx>, Ref<Tx>>()
|
||||
for (const tx of txes) txMap.set(tx._id, tx._id)
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
for (const tx of txes) await model.tx(tx)
|
||||
|
||||
txBuffer = txBuffer.filter((tx) => txMap.get(tx._id) === undefined)
|
||||
|
||||
client = new ClientImpl(hierarchy, model, conn)
|
||||
|
||||
for (const tx of txBuffer) txHander(tx)
|
||||
txBuffer = undefined
|
||||
|
||||
return client
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright © 2020, 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 { Plugin, StatusCode } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import type { Account, Class, Doc, Obj, Ref, Space, AnyAttribute, Tx, TxCreateDoc, TxMixin } from './classes'
|
||||
import type { TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||
|
||||
export const coreId = 'core' as Plugin
|
||||
|
||||
export default plugin(coreId, {
|
||||
class: {
|
||||
Obj: '' as Ref<Class<Obj>>,
|
||||
Doc: '' as Ref<Class<Doc>>,
|
||||
Class: '' as Ref<Class<Class<Obj>>>,
|
||||
Attribute: '' as Ref<Class<AnyAttribute>>,
|
||||
Tx: '' as Ref<Class<Tx>>,
|
||||
TxCreateDoc: '' as Ref<Class<TxCreateDoc<Doc>>>,
|
||||
TxMixin: '' as Ref<Class<TxMixin<Doc, Doc>>>,
|
||||
TxUpdateDoc: '' as Ref<Class<TxUpdateDoc<Doc>>>,
|
||||
TxRemoveDoc: '' as Ref<Class<TxRemoveDoc<Doc>>>,
|
||||
Space: '' as Ref<Class<Space>>,
|
||||
Account: '' as Ref<Class<Account>>
|
||||
},
|
||||
space: {
|
||||
Tx: '' as Ref<Space>,
|
||||
Model: '' as Ref<Space>
|
||||
},
|
||||
account: {
|
||||
System: '' as Ref<Account>
|
||||
},
|
||||
status: {
|
||||
ObjectNotFound: '' as StatusCode<{ _id: Ref<Doc> }>,
|
||||
ItemNotFound: '' as StatusCode<{ _id: Ref<Doc>, _localId: string }>
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// Copyright © 2020, 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 { Ref, Class, Obj, Domain, Mixin, Doc, AnyAttribute, Tx, TxCreateDoc, TxMixin } from './classes'
|
||||
import { ClassifierKind } from './classes'
|
||||
import { TxProcessor } from './tx'
|
||||
|
||||
import core from './component'
|
||||
|
||||
export class Hierarchy {
|
||||
private readonly classes = new Map<Ref<Class<Obj>>, Class<Obj>>()
|
||||
private readonly attributes = new Map<Ref<Class<Obj>>, Map<string, AnyAttribute>>()
|
||||
private readonly descendants = new Map<Ref<Class<Obj>>, Ref<Class<Obj>>[]>()
|
||||
private readonly ancestors = new Map<Ref<Class<Obj>>, Ref<Class<Obj>>[]>()
|
||||
private readonly proxies = new Map<Ref<Mixin<Doc>>, ProxyHandler<Doc>>()
|
||||
|
||||
private createMixinProxyHandler (mixin: Ref<Mixin<Doc>>): ProxyHandler<Doc> {
|
||||
const value = this.getClass(mixin)
|
||||
const ancestor = this.getClass(value.extends as Ref<Class<Obj>>)
|
||||
const ancestorProxy = ancestor.kind === ClassifierKind.MIXIN ? this.getMixinProxyHandler(ancestor._id) : null
|
||||
return {
|
||||
get (target: any, property: string, receiver: any): any {
|
||||
const value = target[mixin]?.[property]
|
||||
if (value === undefined) { return (ancestorProxy !== null) ? ancestorProxy.get?.(target, property, receiver) : target[property] }
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getMixinProxyHandler (mixin: Ref<Mixin<Doc>>): ProxyHandler<Doc> {
|
||||
const handler = this.proxies.get(mixin)
|
||||
if (handler === undefined) {
|
||||
const handler = this.createMixinProxyHandler(mixin)
|
||||
this.proxies.set(mixin, handler)
|
||||
return handler
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
as<D extends Doc, M extends D>(doc: D, mixin: Ref<Mixin<M>>): M {
|
||||
return new Proxy(doc, this.getMixinProxyHandler(mixin)) as M
|
||||
}
|
||||
|
||||
getAncestors (_class: Ref<Class<Obj>>): Ref<Class<Obj>>[] {
|
||||
const result = this.ancestors.get(_class)
|
||||
if (result === undefined) {
|
||||
throw new Error('ancestors not found: ' + _class)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
getClass (_class: Ref<Class<Obj>>): Class<Obj> {
|
||||
const data = this.classes.get(_class)
|
||||
if (data === undefined) {
|
||||
throw new Error('class not found: ' + _class)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
getDomain (_class: Ref<Class<Obj>>): Domain {
|
||||
const klazz = this.getClass(_class)
|
||||
if (klazz.domain !== undefined) {
|
||||
return klazz.domain
|
||||
}
|
||||
if (klazz.extends !== undefined) {
|
||||
const domain = this.getDomain(klazz.extends)
|
||||
klazz.domain = domain
|
||||
return domain
|
||||
}
|
||||
throw new Error('domain not found: ' + _class)
|
||||
}
|
||||
|
||||
tx (tx: Tx): void {
|
||||
if (tx._class !== core.class.TxCreateDoc) {
|
||||
if (tx._class === core.class.TxMixin) {
|
||||
const mixinTx = tx as TxMixin<Doc, Doc>
|
||||
if (tx.objectClass !== core.class.Class) { return }
|
||||
const obj = this.getClass(tx.objectId as Ref<Class<Obj>>) as any
|
||||
obj[mixinTx.mixin] = mixinTx.attributes
|
||||
}
|
||||
return
|
||||
}
|
||||
const createTx = tx as TxCreateDoc<Doc>
|
||||
if (createTx.objectClass === core.class.Class) {
|
||||
const createTx = tx as TxCreateDoc<Class<Obj>>
|
||||
const _id = createTx.objectId
|
||||
this.classes.set(_id, TxProcessor.createDoc2Doc(createTx))
|
||||
this.addAncestors(_id)
|
||||
this.addDescendant(_id)
|
||||
} else if (createTx.objectClass === core.class.Attribute) {
|
||||
const createTx = tx as TxCreateDoc<AnyAttribute>
|
||||
this.addAttribute(TxProcessor.createDoc2Doc(createTx))
|
||||
}
|
||||
}
|
||||
|
||||
isDerived<T extends Obj>(_class: Ref<Class<T>>, from: Ref<Class<T>>): boolean {
|
||||
let cl: Ref<Class<Obj>> | undefined = _class
|
||||
while (cl !== undefined) {
|
||||
if (cl === from) return true
|
||||
const attrs = this.classes.get(cl)
|
||||
cl = attrs?.extends
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
getDescendants<T extends Obj>(_class: Ref<Class<T>>): Ref<Class<Obj>>[] {
|
||||
const data = this.descendants.get(_class)
|
||||
if (data === undefined) {
|
||||
throw new Error('descendants not found: ' + _class)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private addDescendant<T extends Obj>(_class: Ref<Class<T>>): void {
|
||||
const hierarchy = this.getAncestors(_class)
|
||||
for (const cls of hierarchy) {
|
||||
const list = this.descendants.get(cls)
|
||||
if (list === undefined) {
|
||||
this.descendants.set(cls, [_class])
|
||||
} else {
|
||||
list.push(_class)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addAncestors<T extends Obj>(_class: Ref<Class<T>>): void {
|
||||
let cl: Ref<Class<Obj>> | undefined = _class
|
||||
while (cl !== undefined) {
|
||||
const list = this.ancestors.get(_class)
|
||||
if (list === undefined) {
|
||||
this.ancestors.set(_class, [cl])
|
||||
} else {
|
||||
list.push(cl)
|
||||
}
|
||||
const attrs = this.classes.get(cl)
|
||||
cl = attrs?.extends
|
||||
}
|
||||
}
|
||||
|
||||
private addAttribute (attribute: AnyAttribute): void {
|
||||
const _class = attribute.attributeOf
|
||||
let attributes = this.attributes.get(_class)
|
||||
if (attributes === undefined) {
|
||||
attributes = new Map<string, AnyAttribute>()
|
||||
this.attributes.set(_class, attributes)
|
||||
}
|
||||
attributes.set(attribute.name, attribute)
|
||||
console.log('added attribute:', _class, attribute.name)
|
||||
}
|
||||
|
||||
getAttribute (_class: Ref<Class<Obj>>, name: string): AnyAttribute {
|
||||
const attribute = this.attributes.get(_class)?.get(name)
|
||||
if (attribute === undefined) {
|
||||
const clazz = this.getClass(_class)
|
||||
if (clazz.extends !== undefined) {
|
||||
return this.getAttribute(clazz.extends, name)
|
||||
} else {
|
||||
throw new Error('attribute not found: ' + name)
|
||||
}
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright © 2020, 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.
|
||||
//
|
||||
export * from './classes'
|
||||
export * from './tx'
|
||||
export * from './storage'
|
||||
export * from './utils'
|
||||
export * from './hierarchy'
|
||||
export * from './memdb'
|
||||
export * from './client'
|
||||
export * from './operator'
|
||||
export * from './query'
|
||||
|
||||
export { default, coreId } from './component'
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// 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 { PlatformError, Severity, Status } from '@anticrm/platform'
|
||||
import type { Class, Doc, Ref, Tx, TxCreateDoc, TxMixin } from './classes'
|
||||
import core from './component'
|
||||
import type { Hierarchy } from './hierarchy'
|
||||
import { getOperator } from './operator'
|
||||
import { findProperty, resultSort } from './query'
|
||||
import type { DocumentQuery, FindOptions, FindResult, Storage, WithLookup, LookupData, Refs } from './storage'
|
||||
import type { TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||
import { TxProcessor } from './tx'
|
||||
|
||||
class MemDb extends TxProcessor {
|
||||
protected readonly hierarchy: Hierarchy
|
||||
private readonly objectsByClass = new Map<Ref<Class<Doc>>, Doc[]>()
|
||||
private readonly objectById = new Map<Ref<Doc>, Doc>()
|
||||
|
||||
constructor (hierarchy: Hierarchy) {
|
||||
super()
|
||||
this.hierarchy = hierarchy
|
||||
}
|
||||
|
||||
private getObjectsByClass (_class: Ref<Class<Doc>>): Doc[] {
|
||||
const result = this.objectsByClass.get(_class)
|
||||
if (result === undefined) {
|
||||
const result: Doc[] = []
|
||||
this.objectsByClass.set(_class, result)
|
||||
return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private cleanObjectByClass (_class: Ref<Class<Doc>>, _id: Ref<Doc>): void {
|
||||
let result = this.objectsByClass.get(_class)
|
||||
if (result !== undefined) {
|
||||
result = result.filter((cl) => cl._id !== _id)
|
||||
this.objectsByClass.set(_class, result)
|
||||
}
|
||||
}
|
||||
|
||||
private getByIdQuery<T extends Doc>(query: DocumentQuery<T>, _class: Ref<Class<T>>): T[] {
|
||||
const result: T[] = []
|
||||
if (typeof query._id === 'string') {
|
||||
const obj = this.objectById.get(query._id) as T
|
||||
if (obj !== undefined) result.push(obj)
|
||||
} else if (query._id?.$in !== undefined) {
|
||||
const ids = query._id.$in
|
||||
for (const id of ids) {
|
||||
const obj = this.objectById.get(id) as T
|
||||
if (obj !== undefined) result.push(obj)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
getObject<T extends Doc>(_id: Ref<T>): T {
|
||||
const doc = this.objectById.get(_id)
|
||||
if (doc === undefined) {
|
||||
console.log(_id)
|
||||
throw new PlatformError(new Status(Severity.ERROR, core.status.ObjectNotFound, { _id }))
|
||||
}
|
||||
return doc as T
|
||||
}
|
||||
|
||||
private lookup<T extends Doc>(docs: T[], lookup: Refs<T>): WithLookup<T>[] {
|
||||
const withLookup: WithLookup<T>[] = []
|
||||
for (const doc of docs) {
|
||||
const result: LookupData<T> = {}
|
||||
for (const key in lookup) {
|
||||
const id = (doc as any)[key] as Ref<Doc>
|
||||
(result as any)[key] = this.getObject(id)
|
||||
}
|
||||
withLookup.push(Object.assign({}, doc, { $lookup: result }))
|
||||
}
|
||||
return withLookup
|
||||
}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<FindResult<T>> {
|
||||
let result: Doc[]
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(query, '_id') &&
|
||||
(typeof query._id === 'string' || query._id?.$in !== undefined || query._id === undefined)
|
||||
) {
|
||||
result = this.getByIdQuery(query, _class)
|
||||
} else {
|
||||
result = this.getObjectsByClass(_class)
|
||||
}
|
||||
|
||||
for (const key in query) {
|
||||
if (key === '_id' && ((query._id as any)?.$like === undefined || query._id === undefined)) continue
|
||||
const value = (query as any)[key]
|
||||
result = findProperty(result, key, value)
|
||||
}
|
||||
|
||||
if (options?.sort !== undefined) resultSort(result, options?.sort)
|
||||
|
||||
if (options?.lookup !== undefined) result = this.lookup(result as T[], options.lookup)
|
||||
|
||||
result = result.slice(0, options?.limit)
|
||||
return result as T[]
|
||||
}
|
||||
|
||||
addDoc (doc: Doc): void {
|
||||
this.hierarchy.getAncestors(doc._class).forEach((_class) => {
|
||||
this.getObjectsByClass(_class).push(doc)
|
||||
})
|
||||
this.objectById.set(doc._id, doc)
|
||||
}
|
||||
|
||||
delDoc (_id: Ref<Doc>): void {
|
||||
const doc = this.objectById.get(_id)
|
||||
if (doc === undefined) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, core.status.ObjectNotFound, { _id }))
|
||||
}
|
||||
this.objectById.delete(_id)
|
||||
this.hierarchy.getAncestors(doc._class).forEach((_class) => {
|
||||
this.cleanObjectByClass(_class, _id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold transactions
|
||||
*/
|
||||
export class TxDb extends MemDb implements Storage {
|
||||
async tx (tx: Tx): Promise<void> {
|
||||
this.addDoc(tx)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold model objects and classes
|
||||
*/
|
||||
export class ModelDb extends MemDb implements Storage {
|
||||
protected async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<void> {
|
||||
this.addDoc(TxProcessor.createDoc2Doc(tx))
|
||||
}
|
||||
|
||||
protected async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<void> {
|
||||
const doc = this.getObject(tx.objectId) as any
|
||||
const ops = tx.operations as any
|
||||
for (const key in ops) {
|
||||
if (key.startsWith('$')) {
|
||||
const operator = getOperator(key)
|
||||
operator(doc, ops[key])
|
||||
} else {
|
||||
doc[key] = ops[key]
|
||||
}
|
||||
}
|
||||
doc.modifiedBy = tx.modifiedBy
|
||||
doc.modifiedOn = tx.modifiedOn
|
||||
}
|
||||
|
||||
protected async txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<void> {
|
||||
this.delDoc(tx.objectId)
|
||||
}
|
||||
|
||||
// TODO: process ancessor mixins
|
||||
protected async txMixin (tx: TxMixin<Doc, Doc>): Promise<void> {
|
||||
const obj = this.getObject(tx.objectId) as any
|
||||
obj[tx.mixin] = tx.attributes
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Doc, PropertyType } from './classes'
|
||||
|
||||
type OperatorFunc = (doc: Doc, op: object) => void
|
||||
|
||||
function $push (document: Doc, keyval: Record<string, PropertyType>): void {
|
||||
const doc = document as any
|
||||
for (const key in keyval) {
|
||||
const arr = doc[key]
|
||||
if (arr === undefined) {
|
||||
doc[key] = [keyval[key]]
|
||||
} else {
|
||||
arr.push(keyval[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const operators: Record<string, OperatorFunc> = {
|
||||
$push
|
||||
}
|
||||
|
||||
export function getOperator (name: string): OperatorFunc {
|
||||
const operator = operators[name]
|
||||
if (operator === undefined) throw new Error('unknown operator: ' + name)
|
||||
return operator
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Doc } from './classes'
|
||||
import { checkLikeQuery } from './query'
|
||||
|
||||
type Predicate = (docs: Doc[]) => Doc[]
|
||||
type PredicateFactory = (pred: any, propertyKey: string) => Predicate
|
||||
|
||||
const predicates: Record<string, PredicateFactory> = {
|
||||
$in: (o: any, propertyKey: string): Predicate => {
|
||||
if (!Array.isArray(o)) {
|
||||
throw new Error('$in predicate requires array')
|
||||
}
|
||||
return (docs: Doc[]): Doc[] => {
|
||||
const result: Doc[] = []
|
||||
for (const doc of docs) {
|
||||
if (o.includes((doc as any)[propertyKey])) result.push(doc)
|
||||
}
|
||||
return result
|
||||
}
|
||||
},
|
||||
|
||||
$like: (query: string, propertyKey: string): Predicate => {
|
||||
return (docs: Doc[]): Doc[] => {
|
||||
const result: Doc[] = []
|
||||
for (const doc of docs) {
|
||||
const value = (doc as any)[propertyKey] as string
|
||||
if (checkLikeQuery(value, query)) result.push(doc)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isPredicate (o: Record<string, any>): boolean {
|
||||
if (typeof o !== 'object') { return false }
|
||||
const keys = Object.keys(o)
|
||||
return keys.length > 0 && keys.every((key) => key.startsWith('$'))
|
||||
}
|
||||
|
||||
export function createPredicates (o: Record<string, any>, propertyKey: string): Predicate[] {
|
||||
const keys = Object.keys(o)
|
||||
const result: Predicate[] = []
|
||||
for (const key of keys) {
|
||||
const factory = predicates[key]
|
||||
if (factory === undefined) throw new Error('unknown predicate: ' + keys[0])
|
||||
result.push(factory(o[key], propertyKey))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Doc } from './classes'
|
||||
import { createPredicates, isPredicate } from './predicate'
|
||||
import { SortingQuery } from './storage'
|
||||
|
||||
export const likeSymbol = '%'
|
||||
|
||||
export function checkLikeQuery (value: string, query: string): boolean {
|
||||
const searchString = query.split(likeSymbol).join('.*')
|
||||
const regex = RegExp(`^${searchString}$`)
|
||||
return regex.test(value)
|
||||
}
|
||||
|
||||
export function findProperty (objects: Doc[], propertyKey: string, value: any): Doc[] {
|
||||
if (isPredicate(value)) {
|
||||
const preds = createPredicates(value, propertyKey)
|
||||
for (const pred of preds) {
|
||||
objects = pred(objects)
|
||||
}
|
||||
return objects
|
||||
}
|
||||
const result: Doc[] = []
|
||||
for (const object of objects) {
|
||||
if ((object as any)[propertyKey] === value) {
|
||||
result.push(object)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function resultSort<T extends Doc> (result: T[], sortOptions: SortingQuery<T>): void {
|
||||
const sortFunc = (a: any, b: any): number => {
|
||||
for (const key in sortOptions) {
|
||||
const result = typeof a[key] === 'string' ? a[key].localeCompare(b[key]) : a[key] - b[key]
|
||||
if (result !== 0) return result * (sortOptions[key] as number)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
result.sort(sortFunc)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// 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 { KeysByType } from 'simplytyped'
|
||||
import type { Class, Doc, Ref, Tx } from './classes'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type QuerySelector<T> = {
|
||||
$in?: T[]
|
||||
$like?: string
|
||||
}
|
||||
|
||||
export type ObjQueryType<T> = T | QuerySelector<T>
|
||||
|
||||
export type DocumentQuery<T extends Doc> = {
|
||||
[P in keyof T]?: ObjQueryType<T[P]>
|
||||
}
|
||||
|
||||
export type Refs<T extends Doc> = Partial<Pick<T, KeysByType<T, Ref<Doc>>>>
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type FindOptions<T extends Doc> = {
|
||||
limit?: number
|
||||
sort?: SortingQuery<T>
|
||||
lookup?: Refs<T>
|
||||
}
|
||||
|
||||
export type SortingQuery<T extends Doc> = {
|
||||
[P in keyof T]?: T[P] extends object ? never : SortingOrder
|
||||
}
|
||||
|
||||
export enum SortingOrder {
|
||||
Ascending = 1,
|
||||
Descending = -1
|
||||
}
|
||||
|
||||
type RefsAsDocs<T extends Doc> = {
|
||||
[P in keyof T]: T[P] extends Ref<infer X> ? X : never
|
||||
}
|
||||
|
||||
type RemoveNever<T extends object> = Omit<T, KeysByType<T, never>>
|
||||
|
||||
export type LookupData<T extends Doc> = Partial<RemoveNever<RefsAsDocs<T>>>
|
||||
|
||||
export type WithLookup<T extends Doc> = T & {
|
||||
$lookup?: LookupData<T>
|
||||
}
|
||||
|
||||
export type FindResult<T extends Doc> = WithLookup<T>[]
|
||||
|
||||
export interface Storage {
|
||||
findAll: <T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
) => Promise<FindResult<T>>
|
||||
tx: (tx: Tx) => Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
//
|
||||
// Copyright © 2020, 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 { KeysByType } from 'simplytyped'
|
||||
import type { Class, Data, Doc, Domain, Ref, Account, Space, Arr, Mixin, Tx, TxCreateDoc, TxFactory, TxMixin, ExtendedAttributes } from './classes'
|
||||
import core from './component'
|
||||
import { generateId } from './utils'
|
||||
|
||||
type ArrayAsElement<T extends Doc> = {
|
||||
[P in keyof T]: T[P] extends Arr<infer X> ? X : never
|
||||
}
|
||||
|
||||
type OmitNever<T extends object> = Omit<T, KeysByType<T, never>>
|
||||
|
||||
interface PushOptions<T extends Doc> {
|
||||
$push?: Partial<OmitNever<ArrayAsElement<T>>>
|
||||
}
|
||||
|
||||
export type DocumentUpdate<T extends Doc> = Partial<Data<T>> & PushOptions<T>
|
||||
|
||||
export interface TxUpdateDoc<T extends Doc> extends Tx<T> {
|
||||
operations: DocumentUpdate<T>
|
||||
}
|
||||
|
||||
export interface TxRemoveDoc<T extends Doc> extends Tx<T> {
|
||||
}
|
||||
|
||||
export const DOMAIN_TX = 'tx' as Domain
|
||||
|
||||
interface WithTx {
|
||||
tx: (tx: Tx) => Promise<void>
|
||||
}
|
||||
|
||||
export class TxProcessor implements WithTx {
|
||||
async tx (tx: Tx): Promise<void> {
|
||||
switch (tx._class) {
|
||||
case core.class.TxCreateDoc:
|
||||
return await this.txCreateDoc(tx as TxCreateDoc<Doc>)
|
||||
case core.class.TxUpdateDoc:
|
||||
return await this.txUpdateDoc(tx as TxUpdateDoc<Doc>)
|
||||
case core.class.TxRemoveDoc:
|
||||
return await this.txRemoveDoc(tx as TxRemoveDoc<Doc>)
|
||||
case core.class.TxMixin:
|
||||
return await this.txMixin(tx as TxMixin<Doc, Doc>)
|
||||
}
|
||||
}
|
||||
|
||||
static createDoc2Doc<T extends Doc> (tx: TxCreateDoc<T>): T {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
_id: tx.objectId,
|
||||
_class: tx.objectClass,
|
||||
space: tx.objectSpace,
|
||||
modifiedBy: tx.modifiedBy,
|
||||
modifiedOn: tx.modifiedOn,
|
||||
...tx.attributes
|
||||
} as T
|
||||
}
|
||||
|
||||
protected async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<void> {}
|
||||
protected async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<void> {}
|
||||
protected async txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<void> {}
|
||||
protected async txMixin (tx: TxMixin<Doc, Doc>): Promise<void> {}
|
||||
}
|
||||
|
||||
export interface TxOperations {
|
||||
createDoc: <T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, attributes: Data<T>) => Promise<T>
|
||||
updateDoc: <T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<T>,
|
||||
operations: DocumentUpdate<T>
|
||||
) => Promise<void>
|
||||
removeDoc: <T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, objectId: Ref<T>) => Promise<void>
|
||||
}
|
||||
|
||||
export function withOperations<T extends WithTx> (user: Ref<Account>, storage: T): T & TxOperations {
|
||||
const result = storage as T & TxOperations
|
||||
|
||||
result.createDoc = async <T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
space: Ref<Space>,
|
||||
attributes: Data<T>
|
||||
): Promise<T> => {
|
||||
const tx: TxCreateDoc<T> = {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxCreateDoc,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: user,
|
||||
modifiedOn: Date.now(),
|
||||
objectId: generateId(),
|
||||
objectClass: _class,
|
||||
objectSpace: space,
|
||||
attributes
|
||||
}
|
||||
await storage.tx(tx)
|
||||
return TxProcessor.createDoc2Doc(tx)
|
||||
}
|
||||
|
||||
result.updateDoc = async <T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<T>,
|
||||
operations: DocumentUpdate<T>
|
||||
): Promise<void> => {
|
||||
const tx: TxUpdateDoc<T> = {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxUpdateDoc,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: user,
|
||||
modifiedOn: Date.now(),
|
||||
objectId,
|
||||
objectClass: _class,
|
||||
objectSpace: space,
|
||||
operations
|
||||
}
|
||||
await storage.tx(tx)
|
||||
}
|
||||
|
||||
result.removeDoc = async <T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<T>
|
||||
): Promise<void> => {
|
||||
const tx: TxRemoveDoc<T> = {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxRemoveDoc,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: user,
|
||||
modifiedOn: Date.now(),
|
||||
objectId,
|
||||
objectClass: _class,
|
||||
objectSpace: space
|
||||
}
|
||||
await storage.tx(tx)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export class DefaultTxFactory implements TxFactory {
|
||||
constructor (readonly account: Ref<Account>) {}
|
||||
|
||||
createTxCreateDoc<T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, attributes: Data<T>, objectId?: Ref<T>): TxCreateDoc<T> {
|
||||
return {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxCreateDoc,
|
||||
space: core.space.Tx,
|
||||
objectId: objectId ?? generateId(),
|
||||
objectClass: _class,
|
||||
objectSpace: space,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: this.account,
|
||||
attributes
|
||||
}
|
||||
}
|
||||
|
||||
createTxMixin<D extends Doc, M extends D>(objectId: Ref<D>, objectClass: Ref<Class<D>>, mixin: Ref<Mixin<M>>, attributes: ExtendedAttributes<D, M>): TxMixin<D, M> {
|
||||
return {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxMixin,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: this.account,
|
||||
modifiedOn: Date.now(),
|
||||
objectId,
|
||||
objectClass,
|
||||
objectSpace: core.space.Model,
|
||||
mixin,
|
||||
attributes
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright © 2020, 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 { Doc, Ref } from './classes'
|
||||
|
||||
function toHex (value: number, chars: number): string {
|
||||
const result = value.toString(16)
|
||||
if (result.length < chars) {
|
||||
return '0'.repeat(chars - result.length) + result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
let counter = (Math.random() * (1 << 24)) | 0
|
||||
const random = toHex((Math.random() * (1 << 24)) | 0, 6) + toHex((Math.random() * (1 << 16)) | 0, 4)
|
||||
|
||||
function timestamp (): string {
|
||||
const time = (Date.now() / 1000) | 0
|
||||
return toHex(time, 8)
|
||||
}
|
||||
|
||||
function count (): string {
|
||||
const val = counter++ & 0xffffff
|
||||
return toHex(val, 6)
|
||||
}
|
||||
|
||||
export function generateId<T extends Doc> (): Ref<T> {
|
||||
return (timestamp() + random + count()) as Ref<T>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/heft-jest-plugin":"~0.1.15",
|
||||
"@rushstack/heft":"^0.35.0"
|
||||
"@rushstack/heft":"^0.35.0",
|
||||
"eslint-config-standard-with-typescript":"^20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/api-extractor": "~7.18.4"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": [
|
||||
"standard-with-typescript"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/array-type": "off"
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,6 @@
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["heft-jest"],
|
||||
"lib": [
|
||||
"esnext",
|
||||
"esnext.intl",
|
||||
"dom"
|
||||
]
|
||||
"types": ["heft-jest"]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
extends: 'standard-with-typescript',
|
||||
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
|
||||
parserOptions: {
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "heft build --clean",
|
||||
"build": "heft build",
|
||||
"build:docs": "api-extractor run --local",
|
||||
"test": "jest",
|
||||
"lint": "ts-standard src",
|
||||
@@ -19,7 +19,6 @@
|
||||
"eslint-plugin-import":"2",
|
||||
"eslint-plugin-node":"11",
|
||||
"@typescript-eslint/eslint-plugin":"4",
|
||||
"eslint-config-standard-with-typescript":"^20.0.0",
|
||||
"typescript":"^4.3.5",
|
||||
"eslint":"^7.32.0"
|
||||
},
|
||||
|
||||
@@ -67,7 +67,6 @@ function getLocation (plugin: Plugin): PluginLoader<Resources> {
|
||||
const loading = new Map<Plugin, Promise<Resources>>()
|
||||
|
||||
async function loadPlugin (id: Plugin): Promise<Resources> {
|
||||
console.log('loading plugin ', id)
|
||||
let pluginLoader = loading.get(id)
|
||||
if (pluginLoader === undefined) {
|
||||
const status = new Status(Severity.INFO, platform.status.LoadingPlugin, {
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"lib": ["esnext", "esnext.intl"]
|
||||
}
|
||||
}
|
||||
@@ -446,14 +446,19 @@
|
||||
// // "versionPolicyName": ""
|
||||
// },
|
||||
//
|
||||
{
|
||||
"packageName": "@anticrm/platform-rig",
|
||||
"projectFolder": "packages/platform-rig",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@anticrm/platform",
|
||||
"projectFolder": "packages/platform",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@anticrm/platform-rig",
|
||||
"projectFolder": "packages/platform-rig",
|
||||
"packageName": "@anticrm/core",
|
||||
"projectFolder": "packages/core",
|
||||
"shouldPublish": true
|
||||
}
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user