// // 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, Doc, Type, PropertyType, Attribute, Tx, Class, Obj, Data, TxCreateDoc, Domain, Mixin as IMixin, Space, ExtendedAttributes } from '@anticrm/core' import core, { ClassifierKind, IndexKind, generateId, TxFactory } from '@anticrm/core' import type { IntlString, Asset } from '@anticrm/platform' import toposort from 'toposort' type NoIDs = Omit const targets = new Map>() function setIndex (target: any, property: string, index: IndexKind): void { let indexes = targets.get(target) if (indexes === undefined) { indexes = new Map() targets.set(target, indexes) } indexes.set(property, index) } function getIndex (target: any, property: string): IndexKind | undefined { return targets.get(target)?.get(property) } interface ClassTxes { _id: Ref> extends?: Ref> domain?: Domain label: IntlString icon?: Asset txes: Array> kind: ClassifierKind shortLabel?: IntlString } const transactions = new Map() function getTxes (target: any): ClassTxes { const txes = transactions.get(target) if (txes === undefined) { const txes = { txes: [] } as unknown as ClassTxes transactions.set(target, txes) return txes } return txes } /** * @public * @param type - * @param label - * @param icon - * @returns */ export function Prop (type: Type, label: IntlString, icon?: Asset) { return function (target: any, propertyKey: string): void { const txes = getTxes(target) const tx: NoIDs>> = { _class: core.class.TxCreateDoc, space: core.space.Tx, modifiedBy: core.account.System, modifiedOn: Date.now(), objectSpace: core.space.Model, objectClass: core.class.Attribute, attributes: { name: propertyKey, index: getIndex(target, propertyKey), type, label, icon, attributeOf: txes._id // undefined, need to fix later } } txes.txes.push(tx) } } /** * @public */ export function Index (kind: IndexKind) { return function (target: any, propertyKey: string): void { setIndex(target, propertyKey, kind) } } /** * @public */ export function Model ( _class: Ref>, _extends: Ref>, domain?: Domain ) { return function classDecorator T> (constructor: C): void { const txes = getTxes(constructor.prototype) txes._id = _class txes.extends = _class !== core.class.Obj ? _extends : undefined txes.domain = domain txes.kind = ClassifierKind.CLASS } } /** * @public */ export function Mixin ( _class: Ref>, _extends: Ref> ) { return function classDecorator T> (constructor: C): void { const txes = getTxes(constructor.prototype) txes._id = _class txes.extends = _extends txes.kind = ClassifierKind.MIXIN } } /** * @public * @param label - * @param icon - * @returns */ export function UX ( label: IntlString, icon?: Asset, shortLabel?: IntlString ) { return function classDecorator T> (constructor: C): void { const txes = getTxes(constructor.prototype) txes.label = label txes.icon = icon txes.shortLabel = shortLabel } } function generateIds (objectId: Ref, txes: NoIDs>>[]): Tx[] { return txes.map((tx) => { const withId = { _id: generateId(), objectId: generateId(), ...tx } withId.attributes.attributeOf = objectId as Ref> return withId }) } const txFactory = new TxFactory(core.account.System) function _generateTx (tx: ClassTxes): Tx[] { const objectId = tx._id const createTx = txFactory.createTxCreateDoc( core.class.Class, core.space.Model, { domain: tx.domain, kind: ClassifierKind.CLASS, extends: tx.extends, label: tx.label, icon: tx.icon, shortLabel: tx.shortLabel }, objectId ) return [createTx, ...generateIds(objectId, tx.txes as NoIDs>>[])] } /** * @public */ export class Builder { private readonly txes: Tx[] = [] // private readonly hierarchy = new Hierarchy() createModel (...classes: Array Obj>): void { const txes = classes.map((ctor) => getTxes(ctor.prototype)) const byId = new Map() txes.forEach((tx) => { byId.set(tx._id, tx) }) const generated = this.generateTransactions(txes, byId) for (const tx of generated) { this.txes.push(tx) // this.hierarchy.tx(tx) } } private generateTransactions ( txes: ClassTxes[], byId: Map ): Tx[] { const graph = this.createGraph(txes) const sorted = toposort(graph) .reverse() .map((edge) => byId.get(edge)) return sorted.flatMap((tx) => (tx != null ? _generateTx(tx) : [])) } private createGraph (txes: ClassTxes[]): [string, string | undefined][] { return txes.map( (tx) => [tx._id, tx.extends] as [string, string | undefined] ) } // do we need this? createDoc( _class: Ref>, space: Ref, attributes: Data, objectId?: Ref ): void { this.txes.push( txFactory.createTxCreateDoc( _class, space, attributes, objectId ) ) } mixin ( objectId: Ref, objectClass: Ref>, mixin: Ref>, attributes: ExtendedAttributes ): void { this.txes.push(txFactory.createTxMixin(objectId, objectClass, mixin, attributes)) } getTxes (): Tx[] { return this.txes } } // T Y P E S /** * @public */ export function TypeString (): Type { return { _class: core.class.TypeString, label: 'TypeString' as IntlString } } /** * @public */ export function TypeBoolean (): Type { return { _class: core.class.TypeBoolean, label: 'TypeBoolean' as IntlString } } /** * @public */ export function TypeTimestamp (): Type { return { _class: core.class.TypeTimestamp, label: 'TypeTimestamp' as IntlString } } /** * @public */ export function Bag (): Type> { return { _class: core.class.Bag, label: 'Bag' as IntlString } }