add core package

Signed-off-by: Andrey Platov <andrey@hardcoreeng.com>
This commit is contained in:
Andrey Platov
2021-08-03 18:55:52 +02:00
parent 044652e8d0
commit f0cf5e8832
30 changed files with 1642 additions and 16 deletions
+43 -3
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
project: './tsconfig.json'
}
}
+18
View File
@@ -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"
}
+32
View File
@@ -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)
})
})
+53
View File
@@ -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')
})
})
+194
View File
@@ -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')
// })
// })
})
+76
View File
@@ -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
}
+130
View File
@@ -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>
}
+95
View File
@@ -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
}
+47
View File
@@ -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 }>
}
})
+175
View File
@@ -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
}
}
+25
View File
@@ -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'
+180
View File
@@ -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
}
}
+41
View File
@@ -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
}
+64
View File
@@ -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
}
+39
View File
@@ -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)
}
+70
View File
@@ -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>
}
+184
View File
@@ -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
}
}
}
+41
View File
@@ -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>
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
}
}
+2 -1
View File
@@ -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 -1
View File
@@ -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'
}
+1 -2
View File
@@ -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"
},
-1
View File
@@ -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, {
+1
View File
@@ -4,5 +4,6 @@
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "esnext.intl"]
}
}
+7 -2
View File
@@ -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
}
//