mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 02:51:54 +03:00
c23af625f6
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
165 lines
4.9 KiB
TypeScript
165 lines
4.9 KiB
TypeScript
import core, {
|
|
type Attribute,
|
|
type Data,
|
|
type Doc,
|
|
type DocumentUpdate,
|
|
Hierarchy,
|
|
ModelDb,
|
|
type Ref,
|
|
type Tx,
|
|
type Type
|
|
} from '@hcengineering/core'
|
|
import { deepEqual } from 'fast-equals'
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
export async function buildModel (existingTxes: Tx[]): Promise<{ hierarchy: Hierarchy, model: ModelDb, dropTx: Tx[] }> {
|
|
existingTxes = existingTxes.filter((tx) => tx.modifiedBy === core.account.System)
|
|
const dropTx: Tx[] = []
|
|
const hierarchy = new Hierarchy()
|
|
const model = new ModelDb(hierarchy)
|
|
// Construct existing model
|
|
existingTxes.forEach(hierarchy.tx.bind(hierarchy))
|
|
for (const tx of existingTxes) {
|
|
await applyTx(model, tx, dropTx)
|
|
}
|
|
return { hierarchy, model, dropTx }
|
|
}
|
|
|
|
async function applyTx (model: ModelDb, tx: Tx, dropTx: Tx[]): Promise<void> {
|
|
try {
|
|
await model.tx(tx)
|
|
} catch (err: any) {
|
|
dropTx.push(tx)
|
|
console.info('Found issue during processing of tx. Transaction', tx, 'is dropped...')
|
|
}
|
|
}
|
|
|
|
function toUndef (value: any): any {
|
|
return value === null ? undefined : value
|
|
}
|
|
|
|
function diffAttributes (doc: Data<Doc>, newDoc: Data<Doc>): DocumentUpdate<Doc> {
|
|
const result: DocumentUpdate<any> = {}
|
|
const allDocuments = new Map(Object.entries(doc))
|
|
const newDocuments = new Map(Object.entries(newDoc))
|
|
|
|
for (const [key, value] of allDocuments) {
|
|
const newValue = toUndef(newDocuments.get(key))
|
|
if (!deepEqual(newValue, toUndef(value))) {
|
|
// update is required, since values are different
|
|
result[key] = newValue
|
|
}
|
|
}
|
|
for (const [key, value] of newDocuments) {
|
|
const oldValue = toUndef(allDocuments.get(key))
|
|
if (oldValue === undefined && value !== undefined) {
|
|
// Update with new value.
|
|
result[key] = value
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Generate a set of transactions to upgrade from one model to another.
|
|
* @public
|
|
*/
|
|
export async function generateModelDiff (existingTxes: Tx[], txes: Tx[]): Promise<{ diffTx: Op[], dropTx: Tx[] }> {
|
|
const { model, dropTx } = await buildModel(existingTxes)
|
|
const { model: newModel } = await buildModel(txes)
|
|
|
|
const diffTx = generateDocumentDiff(
|
|
await model.findAll(core.class.Doc, {}),
|
|
await newModel.findAll(core.class.Doc, {})
|
|
)
|
|
return { diffTx, dropTx }
|
|
}
|
|
|
|
export type Op = Record<string, any>
|
|
/**
|
|
* @public
|
|
*/
|
|
export function generateDocumentDiff (oldDocs: Doc[], newDocs: Doc[]): Op[] {
|
|
const diffTx: Op[] = []
|
|
|
|
const allDocuments = new Map(oldDocs.map((d) => [getId(d), d]))
|
|
const newDocuments = new Map(newDocs.map((d) => [getId(d), d]))
|
|
|
|
// Find same documents.
|
|
allDocuments.forEach(handleUpdateRemove(newDocuments, diffTx))
|
|
newDocuments.forEach(handleAdd(allDocuments, diffTx))
|
|
return diffTx
|
|
}
|
|
|
|
function getId (d: Doc): Ref<Doc> {
|
|
// We need to update Attribute IDS
|
|
if (d._class === core.class.Attribute) {
|
|
const attr = d as Attribute<Type<any>>
|
|
return (attr.attributeOf + '.' + attr.name) as Ref<Doc>
|
|
} else if (d._class === ('view:class:Viewlet' as Ref<Doc>)) {
|
|
const cr = d as any
|
|
return ((cr.attachTo as string) + '.' + (cr.open as string)) as Ref<Doc>
|
|
} else if (d._class === ('workbench:class:Application' as Ref<Doc>)) {
|
|
const cr = d as any
|
|
return ('workbench.app.' + (cr.label as string)) as Ref<Doc>
|
|
} else if (d._class === ('server-core:class:Trigger' as Ref<Doc>)) {
|
|
const cr = d as any
|
|
return cr.trigger as string as Ref<Doc>
|
|
}
|
|
return d._id
|
|
}
|
|
|
|
function handleAdd (allDocuments: Map<Ref<Doc>, Doc>, newTxes: Op[]): (value: Doc, key: Ref<Doc>) => void {
|
|
return (doc, key) => {
|
|
if (!allDocuments.has(key)) {
|
|
// Add is required
|
|
const { _id, _class, modifiedBy, modifiedOn, space, ...data } = doc
|
|
const tx: Op = {
|
|
_class: 'create-doc',
|
|
objectId: _id,
|
|
objectClass: doc._class,
|
|
attributes: data
|
|
}
|
|
newTxes.push(tx)
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleUpdateRemove (newDocuments: Map<Ref<Doc>, Doc>, newTxes: Op[]): (value: Doc, key: Ref<Doc>) => void {
|
|
return (doc, key) => {
|
|
const newDoc = newDocuments.get(key)
|
|
if (newDoc !== undefined) {
|
|
// update is required.
|
|
const { _id, _class, modifiedBy, modifiedOn, space, ...data } = newDoc
|
|
const { _id: _0, _class: _1, modifiedBy: _2, modifiedOn: _3, space: _4, ...oldData } = doc
|
|
const operations = diffAttributes(oldData, data)
|
|
if (Object.keys(operations).length > 0) {
|
|
const tx: Op = {
|
|
_class: 'update-doc',
|
|
objectId: _id,
|
|
objectClass: _class,
|
|
operations
|
|
}
|
|
newTxes.push(tx)
|
|
}
|
|
} else {
|
|
// Delete is required
|
|
const { _id: oldId, _class: _1, modifiedBy: _2, modifiedOn: _3, space: _4, ...oldData } = doc
|
|
const tx: Op = {
|
|
_class: 'remove-doc',
|
|
objectId: oldId,
|
|
objectClass: doc._class,
|
|
data: oldData
|
|
}
|
|
newTxes.push(tx)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function printDiff (diffTx: Op[]): void {
|
|
// Collect Classes.
|
|
console.log('Diff Transactions', JSON.stringify(diffTx, undefined, 2))
|
|
}
|