// // 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 { KeysByType } from 'simplytyped' import type { Account, Arr, AttachedDoc, Class, Data, Doc, Domain, Mixin, PropertyType, Ref, Space } from './classes' import core from './component' import { _getOperator } from './operator' import { _toDoc } from './proxy' import type { TxResult } from './storage' import { generateId } from './utils' /** * @public */ export interface Tx extends Doc { objectSpace: Ref // space where transaction will operate } /** * @public */ export interface TxCUD extends Tx { objectId: Ref objectClass: Ref> } /** * @public */ export interface TxCreateDoc extends TxCUD { attributes: Data } /** * * Will perform create/update/delete of attached documents. * * @public */ export interface TxCollectionCUD extends TxCUD { collection: string tx: TxCUD

} /** * @public */ export interface TxPutBag extends TxCUD { bag: string key: string value: T } /** * @public */ export interface TxBulkWrite extends Tx { txes: TxCUD[] } /** * @public */ export type MixinData = Omit & PushOptions> & IncOptions> /** * @public */ export type MixinUpdate = Partial> & PushOptions> & IncOptions> /** * Define Create/Update for mixin attributes. * @public */ export interface TxMixin extends TxCUD { mixin: Ref> attributes: MixinUpdate } /** * @public */ export type ArrayAsElement = { [P in keyof T]: T[P] extends Arr ? X : never } /** * @public */ export interface Position { $each: X[] $position: number } /** * @public */ export interface MoveDescriptor { $value: X $position: number } /** * @public */ export type ArrayAsElementPosition = { [P in keyof T]-?: T[P] extends Arr ? X | Position : never } /** * @public */ export type ArrayMoveDescriptor = { [P in keyof T]: T[P] extends Arr ? MoveDescriptor : never } /** * @public */ export type NumberProperties = { [P in keyof T]: T[P] extends number | undefined ? T[P] : never } /** * @public */ export type OmitNever = Omit> /** * @public */ export interface PushOptions { $push?: Partial>> $pull?: Partial>> $move?: Partial>> } /** * @public */ export interface PushMixinOptions { $pushMixin?: { $mixin: Ref> values: Partial>> } } /** * @public */ export interface IncOptions { $inc?: Partial>> } /** * @public */ export interface SpaceUpdate { space?: Ref } /** * @public */ export type DocumentUpdate = Partial> & PushOptions & PushMixinOptions & IncOptions & SpaceUpdate /** * @public */ export interface TxUpdateDoc extends TxCUD { operations: DocumentUpdate retrieve?: boolean } /** * @public */ export interface TxRemoveDoc extends TxCUD { } /** * @public */ export const DOMAIN_TX = 'tx' as Domain /** * @public */ export interface WithTx { tx: (tx: Tx) => Promise } /** * @public */ export abstract class TxProcessor implements WithTx { async tx (tx: Tx): Promise { switch (tx._class) { case core.class.TxCreateDoc: return await this.txCreateDoc(tx as TxCreateDoc) case core.class.TxCollectionCUD: return await this.txCollectionCUD(tx as TxCollectionCUD) case core.class.TxUpdateDoc: return await this.txUpdateDoc(tx as TxUpdateDoc) case core.class.TxRemoveDoc: return await this.txRemoveDoc(tx as TxRemoveDoc) case core.class.TxMixin: return await this.txMixin(tx as TxMixin) case core.class.TxPutBag: return await this.txPutBag(tx as TxPutBag) case core.class.TxBulkWrite: return await this.txBulkWrite(tx as TxBulkWrite) } throw new Error('TxProcessor: unhandled transaction class: ' + tx._class) } static createDoc2Doc(tx: TxCreateDoc): T { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return { _id: tx.objectId, _class: tx.objectClass, space: tx.objectSpace, modifiedBy: tx.modifiedBy, modifiedOn: tx.modifiedOn, ...tx.attributes } as T } static updateDoc2Doc(rawDoc: T, tx: TxUpdateDoc): T { const doc = _toDoc(rawDoc) const ops = tx.operations as any for (const key in ops) { if (key.startsWith('$')) { const operator = _getOperator(key) operator(doc, ops[key]) } else { (doc as any)[key] = ops[key] } } doc.modifiedBy = tx.modifiedBy doc.modifiedOn = tx.modifiedOn return rawDoc } static updateMixin4Doc(rawDoc: D, mixinClass: Ref>, operations: MixinUpdate): D { const ops = operations as any const doc = _toDoc(rawDoc) const mixin = (doc as any)[mixinClass] ?? {} for (const key in ops) { if (key.startsWith('$')) { const operator = _getOperator(key) operator(mixin, ops[key]) } else { mixin[key] = ops[key] } } (doc as any)[mixinClass] = mixin return rawDoc } protected abstract txCreateDoc (tx: TxCreateDoc): Promise protected abstract txPutBag (tx: TxPutBag): Promise protected abstract txUpdateDoc (tx: TxUpdateDoc): Promise protected abstract txRemoveDoc (tx: TxRemoveDoc): Promise protected abstract txMixin (tx: TxMixin): Promise protected txCollectionCUD (tx: TxCollectionCUD): Promise { // We need update only create transactions to contain attached, attachedToClass. if (tx.tx._class === core.class.TxCreateDoc) { const createTx = tx.tx as TxCreateDoc const d: TxCreateDoc = { ...createTx, attributes: { ...createTx.attributes, attachedTo: tx.objectId, attachedToClass: tx.objectClass, collection: tx.collection } } return this.txCreateDoc(d) } return this.tx(tx.tx) } protected async txBulkWrite (bulkTx: TxBulkWrite): Promise { for (const tx of bulkTx.txes) { await this.tx(tx) } return {} } } /** * @public */ export class TxFactory { constructor (readonly account: Ref) {} createTxCreateDoc(_class: Ref>, space: Ref, attributes: Data, objectId?: Ref): TxCreateDoc { return { _id: generateId(), _class: core.class.TxCreateDoc, space: core.space.Tx, objectId: objectId ?? generateId(), objectClass: _class, objectSpace: space, modifiedOn: Date.now(), modifiedBy: this.account, attributes } } createTxCollectionCUD( _class: Ref>, objectId: Ref, space: Ref, collection: string, tx: TxCUD

): TxCollectionCUD { return { _id: generateId(), _class: core.class.TxCollectionCUD, space: core.space.Tx, objectId, objectClass: _class, objectSpace: space, modifiedOn: Date.now(), modifiedBy: this.account, collection, tx } } createTxPutBag

( _class: Ref>, space: Ref, objectId: Ref, bag: string, key: string, value: P ): TxPutBag

{ return { _id: generateId(), _class: core.class.TxPutBag, space: core.space.Tx, modifiedBy: this.account, modifiedOn: Date.now(), objectId, objectClass: _class, objectSpace: space, bag, key, value } } createTxUpdateDoc ( _class: Ref>, space: Ref, objectId: Ref, operations: DocumentUpdate, retrieve?: boolean ): TxUpdateDoc { return { _id: generateId(), _class: core.class.TxUpdateDoc, space: core.space.Tx, modifiedBy: this.account, modifiedOn: Date.now(), objectId, objectClass: _class, objectSpace: space, operations, retrieve } } createTxRemoveDoc ( _class: Ref>, space: Ref, objectId: Ref ): TxRemoveDoc { return { _id: generateId(), _class: core.class.TxRemoveDoc, space: core.space.Tx, modifiedBy: this.account, modifiedOn: Date.now(), objectId, objectClass: _class, objectSpace: space } } createTxMixin(objectId: Ref, objectClass: Ref>, objectSpace: Ref, mixin: Ref>, attributes: MixinUpdate): TxMixin { return { _id: generateId(), _class: core.class.TxMixin, space: core.space.Tx, modifiedBy: this.account, modifiedOn: Date.now(), objectId, objectClass, objectSpace, mixin, attributes: attributes } } createTxBulkWrite (space: Ref, txes: TxCUD[]): TxBulkWrite { return { _id: generateId(), _class: core.class.TxBulkWrite, space: core.space.Tx, modifiedBy: this.account, modifiedOn: Date.now(), objectSpace: space, txes } } }