Interfaces Support (#473)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2021-12-02 03:25:28 +07:00 committed by GitHub
parent fc7bb7823d
commit c60388224b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 264 additions and 87 deletions

View File

@ -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>

View File

@ -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 {

View File

@ -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)

View File

@ -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, {

View File

@ -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
}

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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

View File

@ -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')