diff --git a/models/core/src/security.ts b/models/core/src/security.ts index 3d1d1acf27..a2588a9775 100644 --- a/models/core/src/security.ts +++ b/models/core/src/security.ts @@ -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 diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index 6e43ac1f6b..880c18f7dd 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -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 - - attachedToClass!: Ref> - collection!: string + declare attachedTo: Ref @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 + + // We need this two to make typescript happy. + declare state: TDocWithState['state'] + declare number: TDocWithState['number'] } export function createModel (builder: Builder): void { diff --git a/packages/core/src/__tests__/hierarchy.test.ts b/packages/core/src/__tests__/hierarchy.test.ts index ce26dbad65..d4f6605b63 100644 --- a/packages/core/src/__tests__/hierarchy.test.ts +++ b/packages/core/src/__tests__/hierarchy.test.ts @@ -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).attributes) const notExistClass = 'class:test.MyClass' as Ref> @@ -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) diff --git a/packages/core/src/__tests__/minmodel.ts b/packages/core/src/__tests__/minmodel.ts index 3ddee25128..89ed7d8f2f 100644 --- a/packages/core/src/__tests__/minmodel.ts +++ b/packages/core/src/__tests__/minmodel.ts @@ -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>, attributes: Data>): Tx return txFactory.createTxCreateDoc(core.class.Class, core.space.Model, attributes, _class) } +function createInterface (_interface: Ref>, attributes: Data>): TxCreateDoc { + return txFactory.createTxCreateDoc(core.class.Interface, core.space.Model, attributes, _interface) +} + export function createDoc (_class: Ref>, attributes: Data): TxCreateDoc { 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> }, class: { + Task: '' as Ref>, + TaskCheckItem: '' as Ref>, TestComment: '' as Ref> + }, + interface: { + DummyDocWithState: '' as Ref> } }) @@ -59,9 +78,12 @@ export function genMinModel (): TxCUD[] { 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[] { 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, { diff --git a/packages/core/src/classes.ts b/packages/core/src/classes.ts index c58da7d427..046bdf33b6 100644 --- a/packages/core/src/classes.ts +++ b/packages/core/src/classes.ts @@ -103,6 +103,7 @@ export type AnyAttribute = Attribute> */ 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 extends Classifier { + extends?: Ref>[] +} + /** * @public */ // eslint-disable-next-line @typescript-eslint/no-unused-vars export interface Class extends Classifier { extends?: Ref> + implements?: Ref>[] domain?: Domain shortLabel?: IntlString } diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index c700f164ec..1b8cff025c 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -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>, AttachedDoc: '' as Ref>, Class: '' as Ref>>, + Interface: '' as Ref>>, Attribute: '' as Ref>, Tx: '' as Ref>, TxBulkWrite: '' as Ref>, @@ -40,7 +41,6 @@ export default plugin(coreId, { TxPutBag: '' as Ref>>, Space: '' as Ref>, SpaceWithStates: '' as Ref>, - DocWithState: '' as Ref>, Account: '' as Ref>, State: '' as Ref>, TypeString: '' as Ref>>, @@ -48,6 +48,9 @@ export default plugin(coreId, { TypeTimestamp: '' as Ref>>, Bag: '' as Ref>>> }, + interface: { + DocWithState: '' as Ref> + }, space: { Tx: '' as Ref, Model: '' as Ref diff --git a/packages/core/src/hierarchy.ts b/packages/core/src/hierarchy.ts index 321b617322..7820cf218a 100644 --- a/packages/core/src/hierarchy.ts +++ b/packages/core/src/hierarchy.ts @@ -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>, Class>() - private readonly attributes = new Map>, Map>() - private readonly descendants = new Map>, Ref>[]>() - private readonly ancestors = new Map>, Ref>[]>() + private readonly classifiers = new Map, Classifier>() + private readonly attributes = new Map, Map>() + private readonly descendants = new Map, Ref[]>() + private readonly ancestors = new Map, Ref[]>() private readonly proxies = new Map>, ProxyHandler>() private createMixinProxyHandler (mixin: Ref>): ProxyHandler { @@ -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>): Ref>[] { + getAncestors (_class: Ref): Ref[] { 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 { - 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 { + const data = this.classifiers.get(_interface) + if (data === undefined || !this.isInterface(data)) { + throw new Error('interface not found: ' + _interface) + } + return data + } + getDomain (_class: Ref>): 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): 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): void { - if (tx.objectClass === core.class.Class) { - const createTx = tx as TxCreateDoc> - 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 + this.classifiers.set(_id, TxProcessor.createDoc2Doc(tx as TxCreateDoc)) 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(_class: Ref>, from: Ref>): boolean { - let cl: Ref> | undefined = _class + let cl: Ref> | 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(_class: Ref>, from: Ref>): boolean { + let cl: Ref> | 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(extendsOrImplements: Ref>[], from: Ref>): boolean { + const result: Ref>[] = [] + const toVisit = extendsOrImplements + while (toVisit.length > 0) { + const ref = toVisit.shift() as Ref> + 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(_class: Ref>): void { + private addDescendant (_class: Ref): 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(_class: Ref>): void { - let cl: Ref> | 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): void { + const cl: Ref[] = [_class] + const visited = new Set>() + while (cl.length > 0) { + const classifier = cl.shift() as Ref + 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): Ref[] { + const attrs = this.classifiers.get(classifier) + const result: Ref[] = [] + if (this.isClass(attrs)) { + const cls = attrs as Class + if (cls.extends !== undefined) { + result.push(cls.extends) + } + result.push(...(cls.implements ?? [])) + } + if (this.isInterface(attrs)) { + result.push(...((attrs as Interface).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>): Map { + getAllAttributes (clazz: Ref): Map { const result = new Map() const ancestors = this.getAncestors(clazz) @@ -185,16 +267,41 @@ export class Hierarchy { return result } - getAttribute (_class: Ref>, 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, 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, name: string): AnyAttribute | undefined { + const list = [classifier] + const visited = new Set>() + while (list.length > 0) { + const cl = list.shift() as Ref + 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 (val: Set, value: T): boolean { + if (val.has(value)) { + return false + } + val.add(value) + return true +} + +function addIf (array: T[], value: T): void { + if (!array.includes(value)) { + array.push(value) } } diff --git a/packages/model/src/dsl.ts b/packages/model/src/dsl.ts index ab4797afb1..757ef455e7 100644 --- a/packages/model/src/dsl.ts +++ b/packages/model/src/dsl.ts @@ -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> + _id: Ref extends?: Ref> + implements?: Ref>[] domain?: Domain label: IntlString icon?: Asset @@ -105,17 +106,34 @@ export function Index (kind: IndexKind) { export function Model ( _class: Ref>, _extends: Ref>, - domain?: Domain + domain?: Domain, + _implements?: Ref>[] ) { return function classDecorator 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 ( + _interface: Ref>, + _extends?: Ref>[] +) { + return function classDecorator 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( 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 diff --git a/server/view-resources/src/index.ts b/server/view-resources/src/index.ts index be7b009c95..62eff8c697 100644 --- a/server/view-resources/src/index.ts +++ b/server/view-resources/src/index.ts @@ -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 - if (hierarchy.isDerived(createTx.objectClass, core.class.DocWithState)) { + if (hierarchy.isImplements(createTx.objectClass, core.interface.DocWithState)) { const state = (await (findAll as FindAll)(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 - if (hierarchy.isDerived(removeTx.objectClass, core.class.DocWithState)) { + if (hierarchy.isImplements(removeTx.objectClass, core.interface.DocWithState)) { const kanban = (await (findAll as FindAll)(view.class.Kanban, { attachedTo: removeTx.objectSpace }))[0] if (kanban === undefined) { throw new Error('OnDocWithState: kanban not found')