2021-08-07 05:39:49 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
|
2021-11-18 12:48:05 +00:00
|
|
|
import core, {
|
2021-12-30 09:04:32 +00:00
|
|
|
Account, ArrOf as TypeArrOf, AttachedDoc, Attribute, Class, Classifier, ClassifierKind, Collection as TypeCollection, Data, Doc, Domain, generateId, IndexKind, Interface, Mixin as IMixin, MixinUpdate, Obj, PropertyType, Ref, RefTo, Space, Tx, TxCreateDoc, TxFactory, TxProcessor, Type
|
2021-08-07 05:39:49 +00:00
|
|
|
} from '@anticrm/core'
|
2021-11-18 12:48:05 +00:00
|
|
|
import type { Asset, IntlString } from '@anticrm/platform'
|
2021-08-07 05:39:49 +00:00
|
|
|
import toposort from 'toposort'
|
|
|
|
|
|
|
|
type NoIDs<T extends Tx> = Omit<T, '_id' | 'objectId'>
|
|
|
|
|
2021-08-26 17:59:08 +00:00
|
|
|
const targets = new Map<any, Map<string, IndexKind>>()
|
|
|
|
|
|
|
|
function setIndex (target: any, property: string, index: IndexKind): void {
|
|
|
|
let indexes = targets.get(target)
|
|
|
|
if (indexes === undefined) {
|
|
|
|
indexes = new Map<string, IndexKind>()
|
|
|
|
targets.set(target, indexes)
|
|
|
|
}
|
|
|
|
indexes.set(property, index)
|
|
|
|
}
|
|
|
|
|
|
|
|
function getIndex (target: any, property: string): IndexKind | undefined {
|
|
|
|
return targets.get(target)?.get(property)
|
|
|
|
}
|
|
|
|
|
2021-08-07 05:39:49 +00:00
|
|
|
interface ClassTxes {
|
2021-12-01 20:25:28 +00:00
|
|
|
_id: Ref<Classifier>
|
2021-08-07 05:39:49 +00:00
|
|
|
extends?: Ref<Class<Obj>>
|
2021-12-01 20:25:28 +00:00
|
|
|
implements?: Ref<Interface<Doc>>[]
|
2021-08-07 05:39:49 +00:00
|
|
|
domain?: Domain
|
2021-10-05 09:18:37 +00:00
|
|
|
label: IntlString
|
2021-08-07 05:39:49 +00:00
|
|
|
icon?: Asset
|
|
|
|
txes: Array<NoIDs<Tx>>
|
|
|
|
kind: ClassifierKind
|
2021-10-14 09:44:25 +00:00
|
|
|
shortLabel?: IntlString
|
2021-12-20 09:06:31 +00:00
|
|
|
sortingKey?: string
|
2021-08-07 05:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const transactions = new Map<any, ClassTxes>()
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-12-27 12:01:52 +00:00
|
|
|
const attributes: Map<any, Map<string, Record<string, any>>> = new Map()
|
|
|
|
function setAttr (target: any, prop: string, key: string, value: any): void {
|
|
|
|
const props = attributes.get(target) ?? new Map<string, Record<string, any>>()
|
|
|
|
const attrs = props.get(prop) ?? {}
|
|
|
|
attrs[key] = value
|
|
|
|
|
|
|
|
props.set(prop, attrs)
|
|
|
|
attributes.set(target, props)
|
|
|
|
}
|
|
|
|
|
|
|
|
function clearAttrs (target: any, prop: string): void {
|
|
|
|
const props = attributes.get(target)
|
|
|
|
props?.delete(prop)
|
|
|
|
|
|
|
|
if (props !== undefined && props.size === 0) {
|
|
|
|
attributes.delete(target)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAttrs (target: any, prop: string): Record<string, any> {
|
|
|
|
return attributes.get(target)?.get(prop) ?? {}
|
|
|
|
}
|
|
|
|
|
2021-08-07 05:39:49 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @param type -
|
|
|
|
* @param label -
|
|
|
|
* @param icon -
|
|
|
|
* @returns
|
|
|
|
*/
|
2021-10-05 09:18:37 +00:00
|
|
|
export function Prop (type: Type<PropertyType>, label: IntlString, icon?: Asset) {
|
2021-08-07 05:39:49 +00:00
|
|
|
return function (target: any, propertyKey: string): void {
|
|
|
|
const txes = getTxes(target)
|
|
|
|
const tx: NoIDs<TxCreateDoc<Attribute<PropertyType>>> = {
|
|
|
|
_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,
|
2021-08-26 17:59:08 +00:00
|
|
|
index: getIndex(target, propertyKey),
|
|
|
|
type,
|
2021-08-07 05:39:49 +00:00
|
|
|
label,
|
|
|
|
icon,
|
2021-12-27 12:01:52 +00:00
|
|
|
attributeOf: txes._id, // undefined, need to fix later
|
|
|
|
...getAttrs(target, propertyKey)
|
2021-08-07 05:39:49 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-27 12:01:52 +00:00
|
|
|
|
|
|
|
clearAttrs(target, propertyKey)
|
|
|
|
|
2021-08-07 05:39:49 +00:00
|
|
|
txes.txes.push(tx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-27 12:01:52 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function Hidden () {
|
|
|
|
return function (target: any, propertyKey: string): void {
|
|
|
|
setAttr(target, propertyKey, 'hidden', true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 17:59:08 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function Index (kind: IndexKind) {
|
|
|
|
return function (target: any, propertyKey: string): void {
|
|
|
|
setIndex(target, propertyKey, kind)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-07 05:39:49 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function Model<T extends Obj> (
|
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
_extends: Ref<Class<Obj>>,
|
2021-12-01 20:25:28 +00:00
|
|
|
domain?: Domain,
|
|
|
|
_implements?: Ref<Interface<Doc>>[]
|
2021-08-07 05:39:49 +00:00
|
|
|
) {
|
|
|
|
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
|
2021-12-01 20:25:28 +00:00
|
|
|
txes.implements = _implements
|
2021-08-07 05:39:49 +00:00
|
|
|
txes.domain = domain
|
|
|
|
txes.kind = ClassifierKind.CLASS
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-01 20:25:28 +00:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-07 05:39:49 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function Mixin<T extends Obj> (
|
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
_extends: Ref<Class<Obj>>
|
|
|
|
) {
|
|
|
|
return function classDecorator<C extends new () => 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<T extends Obj> (
|
|
|
|
label: IntlString,
|
2021-10-14 09:44:25 +00:00
|
|
|
icon?: Asset,
|
2021-12-20 09:06:31 +00:00
|
|
|
shortLabel?: IntlString,
|
|
|
|
sortingKey?: string
|
2021-08-07 05:39:49 +00:00
|
|
|
) {
|
|
|
|
return function classDecorator<C extends new () => T> (constructor: C): void {
|
|
|
|
const txes = getTxes(constructor.prototype)
|
|
|
|
txes.label = label
|
|
|
|
txes.icon = icon
|
2021-10-14 09:44:25 +00:00
|
|
|
txes.shortLabel = shortLabel
|
2021-12-20 09:06:31 +00:00
|
|
|
txes.sortingKey = sortingKey
|
2021-08-07 05:39:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function generateIds (objectId: Ref<Doc>, txes: NoIDs<TxCreateDoc<Attribute<PropertyType>>>[]): Tx[] {
|
|
|
|
return txes.map((tx) => {
|
|
|
|
const withId = {
|
|
|
|
_id: generateId<Tx>(),
|
|
|
|
objectId: generateId(),
|
|
|
|
...tx
|
|
|
|
}
|
|
|
|
withId.attributes.attributeOf = objectId as Ref<Class<Obj>>
|
|
|
|
return withId
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const txFactory = new TxFactory(core.account.System)
|
|
|
|
|
|
|
|
function _generateTx (tx: ClassTxes): Tx[] {
|
|
|
|
const objectId = tx._id
|
2021-12-30 09:04:32 +00:00
|
|
|
const _cl = {
|
|
|
|
[ClassifierKind.CLASS]: core.class.Class,
|
|
|
|
[ClassifierKind.INTERFACE]: core.class.Interface,
|
|
|
|
[ClassifierKind.MIXIN]: core.class.Mixin
|
|
|
|
}
|
2021-12-01 20:25:28 +00:00
|
|
|
const createTx = txFactory.createTxCreateDoc<Doc>(
|
2021-12-30 09:04:32 +00:00
|
|
|
_cl[tx.kind],
|
2021-08-07 05:39:49 +00:00
|
|
|
core.space.Model,
|
|
|
|
{
|
2021-12-01 20:25:28 +00:00
|
|
|
...(tx.domain !== undefined ? { domain: tx.domain } : {}),
|
|
|
|
kind: tx.kind,
|
|
|
|
...(tx.kind === ClassifierKind.INTERFACE ? { extends: tx.implements } : { extends: tx.extends, implements: tx.implements }),
|
2021-08-07 05:39:49 +00:00
|
|
|
label: tx.label,
|
2021-10-14 09:44:25 +00:00
|
|
|
icon: tx.icon,
|
2021-12-20 09:06:31 +00:00
|
|
|
shortLabel: tx.shortLabel,
|
|
|
|
sortingKey: tx.sortingKey
|
2021-08-07 05:39:49 +00:00
|
|
|
},
|
|
|
|
objectId
|
|
|
|
)
|
|
|
|
return [createTx, ...generateIds(objectId, tx.txes as NoIDs<TxCreateDoc<Attribute<PropertyType>>>[])]
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export class Builder {
|
|
|
|
private readonly txes: Tx[] = []
|
|
|
|
// private readonly hierarchy = new Hierarchy()
|
|
|
|
|
|
|
|
createModel (...classes: Array<new () => Obj>): void {
|
|
|
|
const txes = classes.map((ctor) => getTxes(ctor.prototype))
|
|
|
|
const byId = new Map<string, ClassTxes>()
|
|
|
|
|
|
|
|
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<string, ClassTxes>
|
|
|
|
): 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<T extends Doc>(
|
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
space: Ref<Space>,
|
|
|
|
attributes: Data<T>,
|
2021-11-18 12:48:05 +00:00
|
|
|
objectId?: Ref<T>,
|
|
|
|
modifiedBy?: Ref<Account>
|
|
|
|
): T {
|
|
|
|
const tx = txFactory.createTxCreateDoc(
|
|
|
|
_class,
|
|
|
|
space,
|
|
|
|
attributes,
|
|
|
|
objectId
|
2021-08-07 05:39:49 +00:00
|
|
|
)
|
2021-11-18 12:48:05 +00:00
|
|
|
if (modifiedBy !== undefined) {
|
|
|
|
tx.modifiedBy = modifiedBy
|
|
|
|
}
|
|
|
|
this.txes.push(tx)
|
|
|
|
return TxProcessor.createDoc2Doc(tx)
|
2021-08-07 05:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mixin<D extends Doc, M extends D> (
|
|
|
|
objectId: Ref<D>,
|
|
|
|
objectClass: Ref<Class<D>>,
|
|
|
|
mixin: Ref<IMixin<M>>,
|
2021-12-30 09:04:32 +00:00
|
|
|
attributes: MixinUpdate<D, M>
|
2021-08-07 05:39:49 +00:00
|
|
|
): void {
|
2021-12-30 09:04:32 +00:00
|
|
|
this.txes.push(txFactory.createTxMixin(objectId, objectClass, core.space.Model, mixin, attributes))
|
2021-08-07 05:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getTxes (): Tx[] {
|
2022-01-24 18:30:13 +00:00
|
|
|
return [...this.txes]
|
2021-08-07 05:39:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// T Y P E S
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function TypeString (): Type<string> {
|
2021-10-05 09:18:37 +00:00
|
|
|
return { _class: core.class.TypeString, label: 'TypeString' as IntlString }
|
2021-08-07 05:39:49 +00:00
|
|
|
}
|
2021-09-15 17:03:34 +00:00
|
|
|
|
2021-10-04 15:46:06 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function TypeBoolean (): Type<string> {
|
2021-10-05 09:18:37 +00:00
|
|
|
return { _class: core.class.TypeBoolean, label: 'TypeBoolean' as IntlString }
|
2021-10-04 15:46:06 +00:00
|
|
|
}
|
|
|
|
|
2021-10-05 14:34:52 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function TypeTimestamp (): Type<string> {
|
|
|
|
return { _class: core.class.TypeTimestamp, label: 'TypeTimestamp' as IntlString }
|
|
|
|
}
|
|
|
|
|
2021-12-02 09:09:37 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function TypeDate (): Type<string> {
|
|
|
|
return { _class: core.class.TypeDate, label: 'TypeDate' as IntlString }
|
|
|
|
}
|
|
|
|
|
2021-11-29 11:32:17 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-12-06 09:16:04 +00:00
|
|
|
export function TypeRef (_class: Ref<Class<Doc>>): RefTo<Doc> {
|
|
|
|
return { _class: core.class.RefTo, to: _class, label: 'TypeRef' as IntlString }
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function Collection<T extends AttachedDoc> (clazz: Ref<Class<T>>): TypeCollection<T> {
|
|
|
|
return { _class: core.class.Collection, of: clazz, label: 'Collection' as IntlString }
|
2021-11-29 11:32:17 +00:00
|
|
|
}
|
|
|
|
|
2021-12-20 09:06:31 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function ArrOf<T extends PropertyType> (type: Type<T>): TypeArrOf<T> {
|
|
|
|
return { _class: core.class.ArrOf, of: type, label: 'Array' as IntlString }
|
|
|
|
}
|
|
|
|
|
2021-09-15 17:03:34 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export function Bag (): Type<Record<string, PropertyType>> {
|
2021-10-05 09:18:37 +00:00
|
|
|
return { _class: core.class.Bag, label: 'Bag' as IntlString }
|
2021-09-15 17:03:34 +00:00
|
|
|
}
|