mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-13 19:58:09 +00:00
Interfaces Support (#473)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
fc7bb7823d
commit
c60388224b
@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import type { Account, Arr, Ref, Space, Domain, State } from '@anticrm/core'
|
||||
import type { Account, Arr, Domain, Ref, Space, State } from '@anticrm/core'
|
||||
import { DOMAIN_MODEL } from '@anticrm/core'
|
||||
import { Model, Prop, TypeState, TypeString } from '@anticrm/model'
|
||||
import { Implements, Model, Prop, TypeState, TypeString } from '@anticrm/model'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import core from './component'
|
||||
import { TDoc } from './core'
|
||||
|
||||
@ -47,7 +47,7 @@ export class TState extends TDoc implements State {
|
||||
color!: string
|
||||
}
|
||||
|
||||
@Model(core.class.DocWithState, core.class.AttachedDoc)
|
||||
@Implements(core.interface.DocWithState)
|
||||
export class TDocWithState extends TDoc {
|
||||
@Prop(TypeState(), 'State' as IntlString)
|
||||
state!: Ref<State>
|
||||
|
@ -13,20 +13,18 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Builder, Model, UX, Prop, TypeString, TypeBoolean } from '@anticrm/model'
|
||||
import type { Ref, FindOptions, Doc, Domain, Class } from '@anticrm/core'
|
||||
import core, { TSpace, TSpaceWithStates, TDocWithState } from '@anticrm/model-core'
|
||||
import type { Vacancy, Candidates, Candidate, Applicant } from '@anticrm/recruit'
|
||||
import activity from '@anticrm/activity'
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
|
||||
import view from '@anticrm/model-view'
|
||||
import contact, { TPerson } from '@anticrm/model-contact'
|
||||
import recruit from './plugin'
|
||||
import type { Doc, Domain, FindOptions, Ref } from '@anticrm/core'
|
||||
import { Builder, Model, Prop, TypeBoolean, TypeString, UX } from '@anticrm/model'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact, { TPerson } from '@anticrm/model-contact'
|
||||
import core, { TAttachedDoc, TDocWithState, TSpace, TSpaceWithStates } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import type { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
|
||||
import recruit from './plugin'
|
||||
|
||||
export const DOMAIN_RECRUIT = 'recruit' as Domain
|
||||
|
||||
@ -63,14 +61,12 @@ export class TCandidate extends TPerson implements Candidate {
|
||||
source?: string
|
||||
}
|
||||
|
||||
@Model(recruit.class.Applicant, core.class.DocWithState, DOMAIN_RECRUIT)
|
||||
@Model(recruit.class.Applicant, core.class.AttachedDoc, DOMAIN_RECRUIT, [core.interface.DocWithState])
|
||||
@UX('Application' as IntlString, recruit.icon.RecruitApplication, 'APP' as IntlString)
|
||||
export class TApplicant extends TDocWithState implements Applicant {
|
||||
export class TApplicant extends TAttachedDoc implements Applicant {
|
||||
// We need to declare, to provide property with label
|
||||
@Prop(TypeString(), 'Candidate' as IntlString)
|
||||
attachedTo!: Ref<Candidate>
|
||||
|
||||
attachedToClass!: Ref<Class<Candidate>>
|
||||
collection!: string
|
||||
declare attachedTo: Ref<Candidate>
|
||||
|
||||
@Prop(TypeString(), 'Attachments' as IntlString)
|
||||
attachments?: number
|
||||
@ -80,6 +76,10 @@ export class TApplicant extends TDocWithState implements Applicant {
|
||||
|
||||
@Prop(TypeString(), 'Assigned recruiter' as IntlString)
|
||||
employee!: Ref<Employee>
|
||||
|
||||
// We need this two to make typescript happy.
|
||||
declare state: TDocWithState['state']
|
||||
declare number: TDocWithState['number']
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
|
@ -17,30 +17,45 @@ import type { Class, Doc, Obj, Ref } from '../classes'
|
||||
import type { TxCreateDoc } from '../tx'
|
||||
import core from '../component'
|
||||
import { Hierarchy } from '../hierarchy'
|
||||
import { genMinModel } from './minmodel'
|
||||
import { genMinModel, test } from './minmodel'
|
||||
|
||||
const txes = genMinModel()
|
||||
|
||||
function prepare (): Hierarchy {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
return hierarchy
|
||||
}
|
||||
|
||||
describe('hierarchy', () => {
|
||||
it('should build hierarchy', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const hierarchy = prepare()
|
||||
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 hierarchy = prepare()
|
||||
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('isImplements', async () => {
|
||||
const hierarchy = prepare()
|
||||
let isImplements = hierarchy.isImplements(test.class.Task, core.interface.DocWithState)
|
||||
expect(isImplements).toBeTruthy()
|
||||
|
||||
isImplements = hierarchy.isImplements(test.class.TaskCheckItem, core.interface.DocWithState)
|
||||
expect(isImplements).toBeTruthy()
|
||||
|
||||
const notImplements = hierarchy.isImplements(core.class.Space, core.interface.DocWithState)
|
||||
expect(notImplements).not.toBeTruthy()
|
||||
})
|
||||
|
||||
it('getClass', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const hierarchy = prepare()
|
||||
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>>
|
||||
@ -48,8 +63,7 @@ describe('hierarchy', () => {
|
||||
})
|
||||
|
||||
it('getDomain', async () => {
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) hierarchy.tx(tx)
|
||||
const hierarchy = prepare()
|
||||
const txDomain = hierarchy.getDomain(core.class.TxCreateDoc)
|
||||
expect(txDomain).toBe('tx')
|
||||
const modelDomain = hierarchy.getDomain(core.class.Class)
|
||||
|
@ -15,7 +15,8 @@
|
||||
|
||||
import type { IntlString, Plugin } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import type { Arr, Class, Data, Doc, Mixin, Obj, Ref } from '../classes'
|
||||
import { DocWithState } from '..'
|
||||
import type { Arr, Class, Data, Doc, Interface, Mixin, Obj, Ref } from '../classes'
|
||||
import { AttachedDoc, ClassifierKind, DOMAIN_MODEL } from '../classes'
|
||||
import core from '../component'
|
||||
import type { TxCreateDoc, TxCUD } from '../tx'
|
||||
@ -27,6 +28,10 @@ function createClass (_class: Ref<Class<Obj>>, attributes: Data<Class<Obj>>): Tx
|
||||
return txFactory.createTxCreateDoc(core.class.Class, core.space.Model, attributes, _class)
|
||||
}
|
||||
|
||||
function createInterface (_interface: Ref<Interface<Doc>>, attributes: Data<Interface<Doc>>): TxCreateDoc<Doc> {
|
||||
return txFactory.createTxCreateDoc(core.class.Interface, core.space.Model, attributes, _interface)
|
||||
}
|
||||
|
||||
export function createDoc<T extends Doc> (_class: Ref<Class<T>>, attributes: Data<T>): TxCreateDoc<Doc> {
|
||||
return txFactory.createTxCreateDoc(_class, core.space.Model, attributes)
|
||||
}
|
||||
@ -39,12 +44,26 @@ export interface AttachedComment extends AttachedDoc {
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface Task extends Doc, DocWithState {
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface TaskCheckItem extends AttachedDoc, DocWithState {
|
||||
name: string
|
||||
complete: boolean
|
||||
}
|
||||
|
||||
export const test = plugin('test' as Plugin, {
|
||||
mixin: {
|
||||
TestMixin: '' as Ref<Mixin<TestMixin>>
|
||||
},
|
||||
class: {
|
||||
Task: '' as Ref<Class<Task>>,
|
||||
TaskCheckItem: '' as Ref<Class<TaskCheckItem>>,
|
||||
TestComment: '' as Ref<Class<AttachedComment>>
|
||||
},
|
||||
interface: {
|
||||
DummyDocWithState: '' as Ref<Interface<DocWithState>>
|
||||
}
|
||||
})
|
||||
|
||||
@ -59,9 +78,12 @@ export function genMinModel (): TxCUD<Doc>[] {
|
||||
txes.push(createClass(core.class.Doc, { label: 'Doc' as IntlString, extends: core.class.Obj, kind: ClassifierKind.CLASS }))
|
||||
txes.push(createClass(core.class.AttachedDoc, { label: 'AttachedDoc' as IntlString, extends: core.class.Doc, kind: ClassifierKind.MIXIN }))
|
||||
txes.push(createClass(core.class.Class, { label: 'Class' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_MODEL }))
|
||||
txes.push(createClass(core.class.Interface, { label: 'Interface' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS }))
|
||||
txes.push(createClass(core.class.Space, { label: 'Space' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_MODEL }))
|
||||
txes.push(createClass(core.class.Account, { label: 'Account' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_MODEL }))
|
||||
|
||||
txes.push(createInterface(core.interface.DocWithState, { label: 'DocWithState' as IntlString, extends: [], kind: ClassifierKind.INTERFACE }))
|
||||
|
||||
txes.push(createClass(core.class.Tx, { label: 'Tx' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_TX }))
|
||||
txes.push(createClass(core.class.TxCUD, { label: 'TxCUD' as IntlString, extends: core.class.Tx, kind: ClassifierKind.CLASS, domain: DOMAIN_TX }))
|
||||
txes.push(createClass(core.class.TxCreateDoc, { label: 'TxCreateDoc' as IntlString, extends: core.class.TxCUD, kind: ClassifierKind.CLASS }))
|
||||
@ -71,7 +93,10 @@ export function genMinModel (): TxCUD<Doc>[] {
|
||||
|
||||
txes.push(createClass(test.mixin.TestMixin, { label: 'TestMixin' as IntlString, extends: core.class.Doc, kind: ClassifierKind.MIXIN }))
|
||||
|
||||
txes.push(createInterface(test.interface.DummyDocWithState, { label: 'DummyDocWithState' as IntlString, extends: [core.interface.DocWithState], kind: ClassifierKind.INTERFACE }))
|
||||
txes.push(createClass(test.class.TestComment, { label: 'TestComment' as IntlString, extends: core.class.AttachedDoc, kind: ClassifierKind.CLASS }))
|
||||
txes.push(createClass(test.class.Task, { label: 'Task' as IntlString, extends: core.class.Doc, implements: [test.interface.DummyDocWithState], kind: ClassifierKind.CLASS }))
|
||||
txes.push(createClass(test.class.TaskCheckItem, { label: 'Task' as IntlString, extends: core.class.AttachedDoc, implements: [core.interface.DocWithState], kind: ClassifierKind.CLASS }))
|
||||
|
||||
txes.push(
|
||||
createDoc(core.class.Space, {
|
||||
|
@ -103,6 +103,7 @@ export type AnyAttribute = Attribute<Type<any>>
|
||||
*/
|
||||
export enum ClassifierKind {
|
||||
CLASS,
|
||||
INTERFACE,
|
||||
MIXIN
|
||||
}
|
||||
|
||||
@ -118,12 +119,21 @@ export interface Classifier extends Doc, UXObject {
|
||||
*/
|
||||
export type Domain = string & { __domain: true }
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export interface Interface<T extends Doc> extends Classifier {
|
||||
extends?: Ref<Interface<Doc>>[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export interface Class<T extends Obj> extends Classifier {
|
||||
extends?: Ref<Class<Obj>>
|
||||
implements?: Ref<Interface<Doc>>[]
|
||||
domain?: Domain
|
||||
shortLabel?: IntlString
|
||||
}
|
||||
|
@ -14,8 +14,8 @@
|
||||
//
|
||||
import type { Plugin, StatusCode } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import type { Account, Class, Doc, Obj, Ref, Space, AnyAttribute, State, Type, PropertyType, SpaceWithStates, Timestamp, DocWithState, AttachedDoc } from './classes'
|
||||
import type { Tx, TxCollectionCUD, TxBulkWrite, TxCreateDoc, TxCUD, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||
import type { Account, AnyAttribute, AttachedDoc, Class, Doc, DocWithState, Interface, Obj, PropertyType, Ref, Space, SpaceWithStates, State, Timestamp, Type } from './classes'
|
||||
import type { Tx, TxBulkWrite, TxCollectionCUD, TxCreateDoc, TxCUD, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -28,6 +28,7 @@ export default plugin(coreId, {
|
||||
Doc: '' as Ref<Class<Doc>>,
|
||||
AttachedDoc: '' as Ref<Class<AttachedDoc>>,
|
||||
Class: '' as Ref<Class<Class<Obj>>>,
|
||||
Interface: '' as Ref<Class<Interface<Doc>>>,
|
||||
Attribute: '' as Ref<Class<AnyAttribute>>,
|
||||
Tx: '' as Ref<Class<Tx>>,
|
||||
TxBulkWrite: '' as Ref<Class<TxBulkWrite>>,
|
||||
@ -40,7 +41,6 @@ export default plugin(coreId, {
|
||||
TxPutBag: '' as Ref<Class<TxPutBag<PropertyType>>>,
|
||||
Space: '' as Ref<Class<Space>>,
|
||||
SpaceWithStates: '' as Ref<Class<SpaceWithStates>>,
|
||||
DocWithState: '' as Ref<Class<DocWithState>>,
|
||||
Account: '' as Ref<Class<Account>>,
|
||||
State: '' as Ref<Class<State>>,
|
||||
TypeString: '' as Ref<Class<Type<string>>>,
|
||||
@ -48,6 +48,9 @@ export default plugin(coreId, {
|
||||
TypeTimestamp: '' as Ref<Class<Type<Timestamp>>>,
|
||||
Bag: '' as Ref<Class<Type<Record<string, PropertyType>>>>
|
||||
},
|
||||
interface: {
|
||||
DocWithState: '' as Ref<Interface<DocWithState>>
|
||||
},
|
||||
space: {
|
||||
Tx: '' as Ref<Space>,
|
||||
Model: '' as Ref<Space>
|
||||
|
@ -13,20 +13,20 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Ref, Class, Obj, Domain, Mixin, Doc, AnyAttribute } from './classes'
|
||||
import type { AnyAttribute, Class, Classifier, Doc, Domain, Interface, Mixin, Obj, Ref } from './classes'
|
||||
import { ClassifierKind } from './classes'
|
||||
import { Tx, TxCreateDoc, TxMixin, TxProcessor } from './tx'
|
||||
|
||||
import core from './component'
|
||||
import type { Tx, TxCreateDoc, TxMixin } from './tx'
|
||||
import { TxProcessor } from './tx'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
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 classifiers = new Map<Ref<Classifier>, Classifier>()
|
||||
private readonly attributes = new Map<Ref<Classifier>, Map<string, AnyAttribute>>()
|
||||
private readonly descendants = new Map<Ref<Classifier>, Ref<Classifier>[]>()
|
||||
private readonly ancestors = new Map<Ref<Classifier>, Ref<Classifier>[]>()
|
||||
private readonly proxies = new Map<Ref<Mixin<Doc>>, ProxyHandler<Doc>>()
|
||||
|
||||
private createMixinProxyHandler (mixin: Ref<Mixin<Doc>>): ProxyHandler<Doc> {
|
||||
@ -36,7 +36,9 @@ export class Hierarchy {
|
||||
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] }
|
||||
if (value === undefined) {
|
||||
return ancestorProxy !== null ? ancestorProxy.get?.(target, property, receiver) : target[property]
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
@ -56,7 +58,7 @@ export class Hierarchy {
|
||||
return new Proxy(doc, this.getMixinProxyHandler(mixin)) as M
|
||||
}
|
||||
|
||||
getAncestors (_class: Ref<Class<Obj>>): Ref<Class<Obj>>[] {
|
||||
getAncestors (_class: Ref<Classifier>): Ref<Classifier>[] {
|
||||
const result = this.ancestors.get(_class)
|
||||
if (result === undefined) {
|
||||
throw new Error('ancestors not found: ' + _class)
|
||||
@ -65,24 +67,39 @@ export class Hierarchy {
|
||||
}
|
||||
|
||||
getClass (_class: Ref<Class<Obj>>): Class<Obj> {
|
||||
const data = this.classes.get(_class)
|
||||
if (data === undefined) {
|
||||
const data = this.classifiers.get(_class)
|
||||
if (data === undefined || this.isInterface(data)) {
|
||||
throw new Error('class not found: ' + _class)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
getInterface (_interface: Ref<Interface<Doc>>): Interface<Doc> {
|
||||
const data = this.classifiers.get(_interface)
|
||||
if (data === undefined || !this.isInterface(data)) {
|
||||
throw new Error('interface not found: ' + _interface)
|
||||
}
|
||||
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
|
||||
klazz.domain = this.findDomain(klazz)
|
||||
return klazz.domain
|
||||
}
|
||||
|
||||
private findDomain (klazz: Class<Doc>): Domain {
|
||||
let _klazz = klazz
|
||||
while (_klazz.extends !== undefined) {
|
||||
_klazz = this.getClass(_klazz.extends)
|
||||
if (_klazz.domain !== undefined) {
|
||||
return _klazz.domain
|
||||
}
|
||||
}
|
||||
throw new Error('domain not found: ' + _class)
|
||||
throw new Error(`domain not found: ${klazz._id} `)
|
||||
}
|
||||
|
||||
tx (tx: Tx): void {
|
||||
@ -96,10 +113,9 @@ export class Hierarchy {
|
||||
}
|
||||
|
||||
private txCreateDoc (tx: TxCreateDoc<Doc>): void {
|
||||
if (tx.objectClass === core.class.Class) {
|
||||
const createTx = tx as TxCreateDoc<Class<Obj>>
|
||||
const _id = createTx.objectId
|
||||
this.classes.set(_id, TxProcessor.createDoc2Doc(createTx))
|
||||
if (tx.objectClass === core.class.Class || tx.objectClass === core.class.Interface) {
|
||||
const _id = tx.objectId as Ref<Classifier>
|
||||
this.classifiers.set(_id, TxProcessor.createDoc2Doc(tx as TxCreateDoc<Classifier>))
|
||||
this.addAncestors(_id)
|
||||
this.addDescendant(_id)
|
||||
} else if (tx.objectClass === core.class.Attribute) {
|
||||
@ -115,12 +131,48 @@ export class Hierarchy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed _class is derived from `from` class.
|
||||
* It will iterave over parents.
|
||||
*/
|
||||
isDerived<T extends Obj>(_class: Ref<Class<T>>, from: Ref<Class<T>>): boolean {
|
||||
let cl: Ref<Class<Obj>> | undefined = _class
|
||||
let cl: Ref<Class<T>> | undefined = _class
|
||||
while (cl !== undefined) {
|
||||
if (cl === from) return true
|
||||
const attrs = this.classes.get(cl)
|
||||
cl = attrs?.extends
|
||||
cl = this.getClass(cl).extends
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed _class implements passed interfaces `from`.
|
||||
* It will check for class parents and they interfaces.
|
||||
*/
|
||||
isImplements<T extends Doc>(_class: Ref<Class<T>>, from: Ref<Interface<T>>): boolean {
|
||||
let cl: Ref<Class<T>> | undefined = _class
|
||||
while (cl !== undefined) {
|
||||
const klazz = this.getClass(cl)
|
||||
if (this.isExtends(klazz.implements ?? [], from)) {
|
||||
return true
|
||||
}
|
||||
cl = klazz.extends
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if interface is extends passed interface.
|
||||
*/
|
||||
private isExtends<T extends Doc>(extendsOrImplements: Ref<Interface<Doc>>[], from: Ref<Interface<T>>): boolean {
|
||||
const result: Ref<Interface<Doc>>[] = []
|
||||
const toVisit = extendsOrImplements
|
||||
while (toVisit.length > 0) {
|
||||
const ref = toVisit.shift() as Ref<Interface<Doc>>
|
||||
if (ref === from) {
|
||||
return true
|
||||
}
|
||||
addIf(result, ref)
|
||||
toVisit.push(...this.ancestorsOf(ref))
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -133,7 +185,7 @@ export class Hierarchy {
|
||||
return data
|
||||
}
|
||||
|
||||
private addDescendant<T extends Obj>(_class: Ref<Class<T>>): void {
|
||||
private addDescendant (_class: Ref<Classifier>): void {
|
||||
const hierarchy = this.getAncestors(_class)
|
||||
for (const cls of hierarchy) {
|
||||
const list = this.descendants.get(cls)
|
||||
@ -145,20 +197,50 @@ export class Hierarchy {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
private addAncestors (_class: Ref<Classifier>): void {
|
||||
const cl: Ref<Classifier>[] = [_class]
|
||||
const visited = new Set<Ref<Classifier>>()
|
||||
while (cl.length > 0) {
|
||||
const classifier = cl.shift() as Ref<Classifier>
|
||||
if (addNew(visited, classifier)) {
|
||||
const list = this.ancestors.get(_class)
|
||||
if (list === undefined) {
|
||||
this.ancestors.set(_class, [classifier])
|
||||
} else {
|
||||
addIf(list, classifier)
|
||||
}
|
||||
cl.push(...this.ancestorsOf(classifier))
|
||||
}
|
||||
const attrs = this.classes.get(cl)
|
||||
cl = attrs?.extends
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return extends and implemnets as combined list of references
|
||||
*/
|
||||
private ancestorsOf (classifier: Ref<Classifier>): Ref<Classifier>[] {
|
||||
const attrs = this.classifiers.get(classifier)
|
||||
const result: Ref<Classifier>[] = []
|
||||
if (this.isClass(attrs)) {
|
||||
const cls = attrs as Class<Doc>
|
||||
if (cls.extends !== undefined) {
|
||||
result.push(cls.extends)
|
||||
}
|
||||
result.push(...(cls.implements ?? []))
|
||||
}
|
||||
if (this.isInterface(attrs)) {
|
||||
result.push(...((attrs as Interface<Doc>).extends ?? []))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private isClass (attrs?: Classifier): boolean {
|
||||
return attrs?.kind === ClassifierKind.CLASS
|
||||
}
|
||||
|
||||
private isInterface (attrs?: Classifier): boolean {
|
||||
return attrs?.kind === ClassifierKind.INTERFACE
|
||||
}
|
||||
|
||||
private addAttribute (attribute: AnyAttribute): void {
|
||||
const _class = attribute.attributeOf
|
||||
let attributes = this.attributes.get(_class)
|
||||
@ -169,7 +251,7 @@ export class Hierarchy {
|
||||
attributes.set(attribute.name, attribute)
|
||||
}
|
||||
|
||||
getAllAttributes (clazz: Ref<Class<Obj>>): Map<string, AnyAttribute> {
|
||||
getAllAttributes (clazz: Ref<Classifier>): Map<string, AnyAttribute> {
|
||||
const result = new Map<string, AnyAttribute>()
|
||||
const ancestors = this.getAncestors(clazz)
|
||||
|
||||
@ -185,16 +267,41 @@ export class Hierarchy {
|
||||
return result
|
||||
}
|
||||
|
||||
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)
|
||||
getAttribute (classifier: Ref<Classifier>, name: string): AnyAttribute {
|
||||
const attr = this.findAttribute(classifier, name)
|
||||
if (attr === undefined) {
|
||||
throw new Error('attribute not found: ' + name)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
private findAttribute (classifier: Ref<Classifier>, name: string): AnyAttribute | undefined {
|
||||
const list = [classifier]
|
||||
const visited = new Set<Ref<Classifier>>()
|
||||
while (list.length > 0) {
|
||||
const cl = list.shift() as Ref<Classifier>
|
||||
if (addNew(visited, cl)) {
|
||||
const attribute = this.attributes.get(cl)?.get(name)
|
||||
if (attribute !== undefined) {
|
||||
return attribute
|
||||
}
|
||||
// Check ancestorsOf
|
||||
list.push(...this.ancestorsOf(cl))
|
||||
}
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
}
|
||||
|
||||
function addNew<T> (val: Set<T>, value: T): boolean {
|
||||
if (val.has(value)) {
|
||||
return false
|
||||
}
|
||||
val.add(value)
|
||||
return true
|
||||
}
|
||||
|
||||
function addIf<T> (array: T[], value: T): void {
|
||||
if (!array.includes(value)) {
|
||||
array.push(value)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
import core, {
|
||||
Account,
|
||||
Attribute, Class, ClassifierKind, Data, Doc, Domain, ExtendedAttributes, generateId, IndexKind, Mixin as IMixin, Obj, PropertyType, Ref, Space, Tx, TxCreateDoc, TxFactory, TxProcessor, Type
|
||||
Attribute, Class, Classifier, ClassifierKind, Data, Doc, Domain, ExtendedAttributes, generateId, IndexKind, Interface, Mixin as IMixin, Obj, PropertyType, Ref, Space, Tx, TxCreateDoc, TxFactory, TxProcessor, Type
|
||||
} from '@anticrm/core'
|
||||
import type { Asset, IntlString } from '@anticrm/platform'
|
||||
import toposort from 'toposort'
|
||||
@ -38,8 +38,9 @@ function getIndex (target: any, property: string): IndexKind | undefined {
|
||||
}
|
||||
|
||||
interface ClassTxes {
|
||||
_id: Ref<Class<Obj>>
|
||||
_id: Ref<Classifier>
|
||||
extends?: Ref<Class<Obj>>
|
||||
implements?: Ref<Interface<Doc>>[]
|
||||
domain?: Domain
|
||||
label: IntlString
|
||||
icon?: Asset
|
||||
@ -105,17 +106,34 @@ export function Index (kind: IndexKind) {
|
||||
export function Model<T extends Obj> (
|
||||
_class: Ref<Class<T>>,
|
||||
_extends: Ref<Class<Obj>>,
|
||||
domain?: Domain
|
||||
domain?: Domain,
|
||||
_implements?: Ref<Interface<Doc>>[]
|
||||
) {
|
||||
return function classDecorator<C extends new () => T> (constructor: C): void {
|
||||
const txes = getTxes(constructor.prototype)
|
||||
txes._id = _class
|
||||
txes.extends = _class !== core.class.Obj ? _extends : undefined
|
||||
txes.implements = _implements
|
||||
txes.domain = domain
|
||||
txes.kind = ClassifierKind.CLASS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function Implements<T extends Doc> (
|
||||
_interface: Ref<Interface<T>>,
|
||||
_extends?: Ref<Interface<Doc>>[]
|
||||
) {
|
||||
return function classDecorator<C extends new () => T> (constructor: C): void {
|
||||
const txes = getTxes(constructor.prototype)
|
||||
txes._id = _interface
|
||||
txes.implements = _extends
|
||||
txes.kind = ClassifierKind.INTERFACE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -166,13 +184,13 @@ const txFactory = new TxFactory(core.account.System)
|
||||
|
||||
function _generateTx (tx: ClassTxes): Tx[] {
|
||||
const objectId = tx._id
|
||||
const createTx = txFactory.createTxCreateDoc(
|
||||
const createTx = txFactory.createTxCreateDoc<Doc>(
|
||||
core.class.Class,
|
||||
core.space.Model,
|
||||
{
|
||||
domain: tx.domain,
|
||||
kind: ClassifierKind.CLASS,
|
||||
extends: tx.extends,
|
||||
...(tx.domain !== undefined ? { domain: tx.domain } : {}),
|
||||
kind: tx.kind,
|
||||
...(tx.kind === ClassifierKind.INTERFACE ? { extends: tx.implements } : { extends: tx.extends, implements: tx.implements }),
|
||||
label: tx.label,
|
||||
icon: tx.icon,
|
||||
shortLabel: tx.shortLabel
|
||||
|
@ -30,7 +30,7 @@ export async function OnDocWithState (tx: Tx, txFactory: TxFactory, findAll: Fin
|
||||
|
||||
if (hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
|
||||
const createTx = tx as TxCreateDoc<DocWithState>
|
||||
if (hierarchy.isDerived(createTx.objectClass, core.class.DocWithState)) {
|
||||
if (hierarchy.isImplements(createTx.objectClass, core.interface.DocWithState)) {
|
||||
const state = (await (findAll as FindAll<State>)(core.class.State, { space: createTx.objectSpace }))[0] // TODO: make FindAll generic
|
||||
if (state === undefined) {
|
||||
throw new Error('OnDocWithState: state not found')
|
||||
@ -46,7 +46,7 @@ export async function OnDocWithState (tx: Tx, txFactory: TxFactory, findAll: Fin
|
||||
}
|
||||
} else if (tx._class === core.class.TxRemoveDoc) {
|
||||
const removeTx = tx as TxRemoveDoc<DocWithState>
|
||||
if (hierarchy.isDerived(removeTx.objectClass, core.class.DocWithState)) {
|
||||
if (hierarchy.isImplements(removeTx.objectClass, core.interface.DocWithState)) {
|
||||
const kanban = (await (findAll as FindAll<Kanban>)(view.class.Kanban, { attachedTo: removeTx.objectSpace }))[0]
|
||||
if (kanban === undefined) {
|
||||
throw new Error('OnDocWithState: kanban not found')
|
||||
|
Loading…
Reference in New Issue
Block a user