2023-07-04 01:50:24 +00:00
|
|
|
import { deepEqual } from 'fast-equals'
|
2023-04-07 15:39:53 +00:00
|
|
|
import { DocumentUpdate, Hierarchy, MixinData, MixinUpdate, ModelDb, toFindResult } from '.'
|
|
|
|
import type {
|
|
|
|
Account,
|
|
|
|
AnyAttribute,
|
|
|
|
AttachedData,
|
|
|
|
AttachedDoc,
|
|
|
|
Class,
|
|
|
|
Data,
|
|
|
|
Doc,
|
|
|
|
Mixin,
|
|
|
|
Ref,
|
|
|
|
Space,
|
|
|
|
Timestamp
|
|
|
|
} from './classes'
|
2022-01-13 09:06:50 +00:00
|
|
|
import { Client } from './client'
|
2022-01-14 09:43:58 +00:00
|
|
|
import core from './component'
|
2022-01-13 09:06:50 +00:00
|
|
|
import type { DocumentQuery, FindOptions, FindResult, TxResult, WithLookup } from './storage'
|
2023-07-04 01:50:24 +00:00
|
|
|
import { DocumentClassQuery, Tx, TxCUD, TxFactory, TxProcessor } from './tx'
|
2022-01-13 09:06:50 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*
|
|
|
|
* High Level operations with client, will create low level transactions.
|
|
|
|
*
|
|
|
|
* `notify` is not supported by TxOperations.
|
|
|
|
*/
|
|
|
|
export class TxOperations implements Omit<Client, 'notify'> {
|
|
|
|
readonly txFactory: TxFactory
|
|
|
|
|
2023-08-01 12:40:26 +00:00
|
|
|
constructor (readonly client: Client, readonly user: Ref<Account>, readonly isDerived: boolean = false) {
|
|
|
|
this.txFactory = new TxFactory(user, isDerived)
|
2022-01-13 09:06:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getHierarchy (): Hierarchy {
|
|
|
|
return this.client.getHierarchy()
|
|
|
|
}
|
|
|
|
|
|
|
|
getModel (): ModelDb {
|
|
|
|
return this.client.getModel()
|
|
|
|
}
|
|
|
|
|
|
|
|
async close (): Promise<void> {
|
|
|
|
return await this.client.close()
|
|
|
|
}
|
|
|
|
|
2022-04-29 05:27:17 +00:00
|
|
|
findAll<T extends Doc>(
|
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
query: DocumentQuery<T>,
|
|
|
|
options?: FindOptions<T> | undefined
|
|
|
|
): Promise<FindResult<T>> {
|
2022-01-13 09:06:50 +00:00
|
|
|
return this.client.findAll(_class, query, options)
|
|
|
|
}
|
|
|
|
|
2022-04-29 05:27:17 +00:00
|
|
|
findOne<T extends Doc>(
|
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
query: DocumentQuery<T>,
|
|
|
|
options?: FindOptions<T> | undefined
|
|
|
|
): Promise<WithLookup<T> | undefined> {
|
2022-01-21 09:05:26 +00:00
|
|
|
return this.client.findOne(_class, query, options)
|
2022-01-13 09:06:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tx (tx: Tx): Promise<TxResult> {
|
|
|
|
return this.client.tx(tx)
|
|
|
|
}
|
|
|
|
|
2022-04-29 05:27:17 +00:00
|
|
|
async createDoc<T extends Doc>(
|
2022-01-13 09:06:50 +00:00
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
space: Ref<Space>,
|
|
|
|
attributes: Data<T>,
|
2022-12-16 06:35:51 +00:00
|
|
|
id?: Ref<T>,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
2022-01-13 09:06:50 +00:00
|
|
|
): Promise<Ref<T>> {
|
2023-02-16 16:10:19 +00:00
|
|
|
const hierarchy = this.client.getHierarchy()
|
|
|
|
if (hierarchy.isDerived(_class, core.class.AttachedDoc)) {
|
|
|
|
throw new Error('createDoc cannot be used for objects inherited from AttachedDoc')
|
|
|
|
}
|
2022-12-16 06:35:51 +00:00
|
|
|
const tx = this.txFactory.createTxCreateDoc(_class, space, attributes, id, modifiedOn, modifiedBy)
|
2022-01-13 09:06:50 +00:00
|
|
|
await this.client.tx(tx)
|
|
|
|
return tx.objectId
|
|
|
|
}
|
|
|
|
|
|
|
|
async addCollection<T extends Doc, P extends AttachedDoc>(
|
|
|
|
_class: Ref<Class<P>>,
|
|
|
|
space: Ref<Space>,
|
|
|
|
attachedTo: Ref<T>,
|
|
|
|
attachedToClass: Ref<Class<T>>,
|
|
|
|
collection: string,
|
|
|
|
attributes: AttachedData<P>,
|
2022-12-16 06:35:51 +00:00
|
|
|
id?: Ref<P>,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
2022-01-19 09:04:30 +00:00
|
|
|
): Promise<Ref<P>> {
|
2022-01-13 09:06:50 +00:00
|
|
|
const tx = this.txFactory.createTxCollectionCUD<T, P>(
|
|
|
|
attachedToClass,
|
|
|
|
attachedTo,
|
|
|
|
space,
|
|
|
|
collection,
|
2023-02-02 12:08:09 +00:00
|
|
|
this.txFactory.createTxCreateDoc<P>(_class, space, attributes as unknown as Data<P>, id, modifiedOn, modifiedBy),
|
|
|
|
modifiedOn,
|
|
|
|
modifiedBy
|
2022-01-13 09:06:50 +00:00
|
|
|
)
|
|
|
|
await this.client.tx(tx)
|
2022-01-19 09:04:30 +00:00
|
|
|
return tx.tx.objectId as unknown as Ref<P>
|
2022-01-13 09:06:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async updateCollection<T extends Doc, P extends AttachedDoc>(
|
|
|
|
_class: Ref<Class<P>>,
|
|
|
|
space: Ref<Space>,
|
|
|
|
objectId: Ref<P>,
|
|
|
|
attachedTo: Ref<T>,
|
|
|
|
attachedToClass: Ref<Class<T>>,
|
|
|
|
collection: string,
|
|
|
|
operations: DocumentUpdate<P>,
|
2022-12-16 06:35:51 +00:00
|
|
|
retrieve?: boolean,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
2022-01-13 09:06:50 +00:00
|
|
|
): Promise<Ref<T>> {
|
|
|
|
const tx = this.txFactory.createTxCollectionCUD(
|
|
|
|
attachedToClass,
|
|
|
|
attachedTo,
|
|
|
|
space,
|
|
|
|
collection,
|
2023-02-02 12:08:09 +00:00
|
|
|
this.txFactory.createTxUpdateDoc(_class, space, objectId, operations, retrieve, modifiedOn, modifiedBy),
|
|
|
|
modifiedOn,
|
|
|
|
modifiedBy
|
2022-01-13 09:06:50 +00:00
|
|
|
)
|
|
|
|
await this.client.tx(tx)
|
|
|
|
return tx.objectId
|
|
|
|
}
|
|
|
|
|
|
|
|
async removeCollection<T extends Doc, P extends AttachedDoc>(
|
|
|
|
_class: Ref<Class<P>>,
|
|
|
|
space: Ref<Space>,
|
|
|
|
objectId: Ref<P>,
|
|
|
|
attachedTo: Ref<T>,
|
|
|
|
attachedToClass: Ref<Class<T>>,
|
2022-12-16 06:35:51 +00:00
|
|
|
collection: string,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
2022-01-13 09:06:50 +00:00
|
|
|
): Promise<Ref<T>> {
|
|
|
|
const tx = this.txFactory.createTxCollectionCUD(
|
|
|
|
attachedToClass,
|
|
|
|
attachedTo,
|
|
|
|
space,
|
|
|
|
collection,
|
2023-02-02 12:08:09 +00:00
|
|
|
this.txFactory.createTxRemoveDoc(_class, space, objectId, modifiedOn, modifiedBy),
|
|
|
|
modifiedOn,
|
|
|
|
modifiedBy
|
2022-01-13 09:06:50 +00:00
|
|
|
)
|
|
|
|
await this.client.tx(tx)
|
|
|
|
return tx.objectId
|
|
|
|
}
|
|
|
|
|
2022-04-29 05:27:17 +00:00
|
|
|
updateDoc<T extends Doc>(
|
2022-01-13 09:06:50 +00:00
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
space: Ref<Space>,
|
|
|
|
objectId: Ref<T>,
|
|
|
|
operations: DocumentUpdate<T>,
|
2022-12-16 06:35:51 +00:00
|
|
|
retrieve?: boolean,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
2022-01-13 09:06:50 +00:00
|
|
|
): Promise<TxResult> {
|
2022-12-16 06:35:51 +00:00
|
|
|
const tx = this.txFactory.createTxUpdateDoc(_class, space, objectId, operations, retrieve, modifiedOn, modifiedBy)
|
2022-01-13 09:06:50 +00:00
|
|
|
return this.client.tx(tx)
|
|
|
|
}
|
|
|
|
|
2022-12-16 06:35:51 +00:00
|
|
|
removeDoc<T extends Doc>(
|
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
space: Ref<Space>,
|
|
|
|
objectId: Ref<T>,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
|
|
|
): Promise<TxResult> {
|
|
|
|
const tx = this.txFactory.createTxRemoveDoc(_class, space, objectId, modifiedOn, modifiedBy)
|
2022-01-13 09:06:50 +00:00
|
|
|
return this.client.tx(tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
createMixin<D extends Doc, M extends D>(
|
|
|
|
objectId: Ref<D>,
|
|
|
|
objectClass: Ref<Class<D>>,
|
|
|
|
objectSpace: Ref<Space>,
|
|
|
|
mixin: Ref<Mixin<M>>,
|
2022-12-16 06:35:51 +00:00
|
|
|
attributes: MixinData<D, M>,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
2022-01-13 09:06:50 +00:00
|
|
|
): Promise<TxResult> {
|
2022-12-16 06:35:51 +00:00
|
|
|
const tx = this.txFactory.createTxMixin(
|
|
|
|
objectId,
|
|
|
|
objectClass,
|
|
|
|
objectSpace,
|
|
|
|
mixin,
|
|
|
|
attributes,
|
|
|
|
modifiedOn,
|
|
|
|
modifiedBy
|
|
|
|
)
|
2022-01-13 09:06:50 +00:00
|
|
|
return this.client.tx(tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
updateMixin<D extends Doc, M extends D>(
|
|
|
|
objectId: Ref<D>,
|
|
|
|
objectClass: Ref<Class<D>>,
|
|
|
|
objectSpace: Ref<Space>,
|
|
|
|
mixin: Ref<Mixin<M>>,
|
2022-12-16 06:35:51 +00:00
|
|
|
attributes: MixinUpdate<D, M>,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
2022-01-13 09:06:50 +00:00
|
|
|
): Promise<TxResult> {
|
2022-12-16 06:35:51 +00:00
|
|
|
const tx = this.txFactory.createTxMixin(
|
|
|
|
objectId,
|
|
|
|
objectClass,
|
|
|
|
objectSpace,
|
|
|
|
mixin,
|
|
|
|
attributes,
|
|
|
|
modifiedOn,
|
|
|
|
modifiedBy
|
|
|
|
)
|
2022-01-13 09:06:50 +00:00
|
|
|
return this.client.tx(tx)
|
|
|
|
}
|
|
|
|
|
2023-08-30 16:44:00 +00:00
|
|
|
async update<T extends Doc>(
|
2022-12-16 06:35:51 +00:00
|
|
|
doc: T,
|
|
|
|
update: DocumentUpdate<T>,
|
|
|
|
retrieve?: boolean,
|
|
|
|
modifiedOn?: Timestamp,
|
|
|
|
modifiedBy?: Ref<Account>
|
|
|
|
): Promise<TxResult> {
|
2022-10-28 15:16:09 +00:00
|
|
|
const hierarchy = this.client.getHierarchy()
|
2023-08-04 18:06:21 +00:00
|
|
|
const mixClass = Hierarchy.mixinOrClass(doc)
|
|
|
|
if (hierarchy.isMixin(mixClass)) {
|
2022-10-28 15:16:09 +00:00
|
|
|
const baseClass = hierarchy.getBaseClass(doc._class)
|
2023-08-30 16:44:00 +00:00
|
|
|
|
|
|
|
const byClass = this.splitMixinUpdate(update, mixClass, baseClass)
|
|
|
|
const ops = this.apply(doc._id)
|
|
|
|
for (const it of byClass) {
|
|
|
|
if (hierarchy.isMixin(it[0])) {
|
|
|
|
await ops.updateMixin(doc._id, baseClass, doc.space, it[0], it[1], modifiedOn, modifiedBy)
|
|
|
|
} else {
|
2023-09-27 15:21:06 +00:00
|
|
|
if (hierarchy.isDerived(it[0], core.class.AttachedDoc)) {
|
|
|
|
const adoc = doc as unknown as AttachedDoc
|
|
|
|
return await this.updateCollection(
|
|
|
|
it[0],
|
|
|
|
doc.space,
|
|
|
|
adoc._id,
|
|
|
|
adoc.attachedTo,
|
|
|
|
adoc.attachedToClass,
|
|
|
|
adoc.collection,
|
|
|
|
it[1],
|
|
|
|
retrieve,
|
|
|
|
modifiedOn,
|
|
|
|
modifiedBy
|
|
|
|
)
|
|
|
|
}
|
2023-08-30 16:44:00 +00:00
|
|
|
await ops.updateDoc(it[0], doc.space, doc._id, it[1], retrieve, modifiedOn, modifiedBy)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return await ops.commit()
|
2022-10-28 15:16:09 +00:00
|
|
|
}
|
|
|
|
if (hierarchy.isDerived(doc._class, core.class.AttachedDoc)) {
|
2022-01-13 09:06:50 +00:00
|
|
|
const adoc = doc as unknown as AttachedDoc
|
2023-08-30 16:44:00 +00:00
|
|
|
return await this.updateCollection(
|
2022-04-29 05:27:17 +00:00
|
|
|
doc._class,
|
|
|
|
doc.space,
|
|
|
|
adoc._id,
|
|
|
|
adoc.attachedTo,
|
|
|
|
adoc.attachedToClass,
|
|
|
|
adoc.collection,
|
|
|
|
update,
|
2022-12-16 06:35:51 +00:00
|
|
|
retrieve,
|
|
|
|
modifiedOn,
|
|
|
|
modifiedBy
|
2022-04-29 05:27:17 +00:00
|
|
|
)
|
2022-01-13 09:06:50 +00:00
|
|
|
}
|
2023-08-30 16:44:00 +00:00
|
|
|
return await this.updateDoc(doc._class, doc.space, doc._id, update, retrieve, modifiedOn, modifiedBy)
|
2022-01-13 09:06:50 +00:00
|
|
|
}
|
|
|
|
|
2022-12-16 06:35:51 +00:00
|
|
|
remove<T extends Doc>(doc: T, modifiedOn?: Timestamp, modifiedBy?: Ref<Account>): Promise<TxResult> {
|
2022-01-13 09:06:50 +00:00
|
|
|
if (this.client.getHierarchy().isDerived(doc._class, core.class.AttachedDoc)) {
|
|
|
|
const adoc = doc as unknown as AttachedDoc
|
2022-04-29 05:27:17 +00:00
|
|
|
return this.removeCollection(
|
|
|
|
doc._class,
|
|
|
|
doc.space,
|
|
|
|
adoc._id,
|
|
|
|
adoc.attachedTo,
|
|
|
|
adoc.attachedToClass,
|
2022-12-16 06:35:51 +00:00
|
|
|
adoc.collection,
|
|
|
|
modifiedOn,
|
|
|
|
modifiedBy
|
2022-04-29 05:27:17 +00:00
|
|
|
)
|
2022-01-13 09:06:50 +00:00
|
|
|
}
|
|
|
|
return this.removeDoc(doc._class, doc.space, doc._id)
|
|
|
|
}
|
2022-10-21 07:39:32 +00:00
|
|
|
|
|
|
|
apply (scope: string): ApplyOperations {
|
|
|
|
return new ApplyOperations(this, scope)
|
|
|
|
}
|
2023-07-04 01:50:24 +00:00
|
|
|
|
2023-08-07 14:17:07 +00:00
|
|
|
async diffUpdate (doc: Doc, raw: Doc | Data<Doc>, date: Timestamp, account?: Ref<Account>): Promise<Doc> {
|
2023-07-04 01:50:24 +00:00
|
|
|
// We need to update fields if they are different.
|
|
|
|
const documentUpdate: DocumentUpdate<Doc> = {}
|
|
|
|
for (const [k, v] of Object.entries(raw)) {
|
|
|
|
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const dv = (doc as any)[k]
|
2023-09-08 04:33:23 +00:00
|
|
|
if (!deepEqual(dv, v) && v !== undefined) {
|
2023-07-04 01:50:24 +00:00
|
|
|
;(documentUpdate as any)[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Object.keys(documentUpdate).length > 0) {
|
2023-08-07 14:17:07 +00:00
|
|
|
await this.update(doc, documentUpdate, false, date, account ?? doc.modifiedBy)
|
2023-07-04 01:50:24 +00:00
|
|
|
TxProcessor.applyUpdate(doc, documentUpdate)
|
|
|
|
}
|
|
|
|
return doc
|
|
|
|
}
|
|
|
|
|
|
|
|
async mixinDiffUpdate (
|
|
|
|
doc: Doc,
|
|
|
|
raw: Doc | Data<Doc>,
|
|
|
|
mixin: Ref<Class<Mixin<Doc>>>,
|
|
|
|
modifiedBy: Ref<Account>,
|
|
|
|
modifiedOn: Timestamp
|
|
|
|
): Promise<Doc> {
|
|
|
|
// We need to update fields if they are different.
|
|
|
|
|
|
|
|
if (!this.getHierarchy().hasMixin(doc, mixin)) {
|
|
|
|
await this.createMixin(doc._id, doc._class, doc.space, mixin, raw as MixinData<Doc, Doc>, modifiedOn, modifiedBy)
|
|
|
|
TxProcessor.applyUpdate(this.getHierarchy().as(doc, mixin), raw)
|
|
|
|
return doc
|
|
|
|
}
|
|
|
|
|
|
|
|
const documentUpdate: MixinUpdate<Doc, Doc> = {}
|
|
|
|
for (const [k, v] of Object.entries(raw)) {
|
|
|
|
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const dv = (doc as any)[k]
|
|
|
|
if (!deepEqual(dv, v) && v != null) {
|
|
|
|
;(documentUpdate as any)[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Object.keys(documentUpdate).length > 0) {
|
|
|
|
await this.updateMixin(doc._id, doc._class, doc.space, mixin, documentUpdate, modifiedOn, modifiedBy)
|
|
|
|
TxProcessor.applyUpdate(this.getHierarchy().as(doc, mixin), documentUpdate)
|
|
|
|
}
|
|
|
|
return doc
|
|
|
|
}
|
2023-08-30 16:44:00 +00:00
|
|
|
|
|
|
|
private splitMixinUpdate<T extends Doc>(
|
|
|
|
update: DocumentUpdate<T>,
|
|
|
|
mixClass: Ref<Class<T>>,
|
|
|
|
baseClass: Ref<Class<T>>
|
|
|
|
): Map<Ref<Class<Doc>>, DocumentUpdate<T>> {
|
|
|
|
const hierarchy = this.getHierarchy()
|
|
|
|
const attributes = hierarchy.getAllAttributes(mixClass)
|
|
|
|
|
|
|
|
const updateAttrs = Object.fromEntries(
|
|
|
|
Object.entries(update).filter((it) => !it[0].startsWith('$'))
|
|
|
|
) as DocumentUpdate<T>
|
|
|
|
const updateOps = Object.fromEntries(
|
|
|
|
Object.entries(update).filter((it) => it[0].startsWith('$'))
|
|
|
|
) as DocumentUpdate<T>
|
|
|
|
|
|
|
|
const result: Map<Ref<Class<Doc>>, DocumentUpdate<T>> = this.splitObjectAttributes(
|
|
|
|
updateAttrs,
|
|
|
|
baseClass,
|
|
|
|
attributes
|
|
|
|
)
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(updateOps)) {
|
|
|
|
const updates = this.splitObjectAttributes(value as object, baseClass, attributes)
|
|
|
|
|
|
|
|
for (const [opsClass, opsUpdate] of updates) {
|
|
|
|
const upd: DocumentUpdate<T> = result.get(opsClass) ?? {}
|
|
|
|
result.set(opsClass, { ...upd, [key]: opsUpdate })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
private splitObjectAttributes<T extends object>(
|
|
|
|
obj: T,
|
|
|
|
objClass: Ref<Class<Doc>>,
|
|
|
|
attributes: Map<String, AnyAttribute>
|
|
|
|
): Map<Ref<Class<Doc>>, object> {
|
|
|
|
const hierarchy = this.getHierarchy()
|
|
|
|
|
|
|
|
const result: Map<Ref<Class<Doc>>, any> = new Map()
|
|
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
|
|
const attributeOf = attributes.get(key)?.attributeOf
|
|
|
|
const clazz = attributeOf !== undefined && hierarchy.isMixin(attributeOf) ? attributeOf : objClass
|
|
|
|
result.set(clazz, { ...(result.get(clazz) ?? {}), [key]: value })
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
2022-10-21 07:39:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*
|
|
|
|
* Builder for ApplyOperation, with same syntax as TxOperations.
|
|
|
|
*
|
|
|
|
* Will send real command on commit and will return boolean of operation success.
|
|
|
|
*/
|
|
|
|
export class ApplyOperations extends TxOperations {
|
|
|
|
txes: TxCUD<Doc>[] = []
|
|
|
|
matches: DocumentClassQuery<Doc>[] = []
|
2023-05-30 06:28:36 +00:00
|
|
|
notMatches: DocumentClassQuery<Doc>[] = []
|
2022-10-21 07:39:32 +00:00
|
|
|
constructor (readonly ops: TxOperations, readonly scope: string) {
|
|
|
|
const txClient: Client = {
|
|
|
|
getHierarchy: () => ops.client.getHierarchy(),
|
|
|
|
getModel: () => ops.client.getModel(),
|
|
|
|
close: () => ops.client.close(),
|
|
|
|
findOne: (_class, query, options?) => ops.client.findOne(_class, query, options),
|
|
|
|
findAll: (_class, query, options?) => ops.client.findAll(_class, query, options),
|
|
|
|
tx: async (tx): Promise<TxResult> => {
|
|
|
|
if (ops.getHierarchy().isDerived(tx._class, core.class.TxCUD)) {
|
|
|
|
this.txes.push(tx as TxCUD<Doc>)
|
|
|
|
}
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
super(txClient, ops.user)
|
|
|
|
}
|
|
|
|
|
|
|
|
match<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>): ApplyOperations {
|
|
|
|
this.matches.push({ _class, query })
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2023-05-30 06:28:36 +00:00
|
|
|
notMatch<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>): ApplyOperations {
|
|
|
|
this.notMatches.push({ _class, query })
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-10-21 07:39:32 +00:00
|
|
|
async commit (): Promise<boolean> {
|
2023-04-07 15:39:53 +00:00
|
|
|
if (this.txes.length > 0) {
|
|
|
|
return await ((await this.ops.tx(
|
2023-05-30 06:28:36 +00:00
|
|
|
this.ops.txFactory.createTxApplyIf(core.space.Tx, this.scope, this.matches, this.notMatches, this.txes)
|
2023-04-07 15:39:53 +00:00
|
|
|
)) as Promise<boolean>)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*
|
|
|
|
* Builder for TxOperations.
|
|
|
|
*/
|
|
|
|
export class TxBuilder extends TxOperations {
|
|
|
|
txes: TxCUD<Doc>[] = []
|
|
|
|
matches: DocumentClassQuery<Doc>[] = []
|
|
|
|
constructor (readonly hierarchy: Hierarchy, readonly modelDb: ModelDb, user: Ref<Account>) {
|
|
|
|
const txClient: Client = {
|
|
|
|
getHierarchy: () => this.hierarchy,
|
|
|
|
getModel: () => this.modelDb,
|
|
|
|
close: async () => {},
|
|
|
|
findOne: async (_class, query, options?) => undefined,
|
|
|
|
findAll: async (_class, query, options?) => toFindResult([]),
|
|
|
|
tx: async (tx): Promise<TxResult> => {
|
|
|
|
if (this.hierarchy.isDerived(tx._class, core.class.TxCUD)) {
|
|
|
|
this.txes.push(tx as TxCUD<Doc>)
|
|
|
|
}
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
super(txClient, user)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export async function updateAttribute (
|
|
|
|
client: TxOperations,
|
|
|
|
object: Doc,
|
|
|
|
_class: Ref<Class<Doc>>,
|
|
|
|
attribute: { key: string, attr: AnyAttribute },
|
|
|
|
value: any,
|
|
|
|
modifyBy?: Ref<Account>
|
|
|
|
): Promise<void> {
|
|
|
|
const doc = object
|
|
|
|
const attributeKey = attribute.key
|
|
|
|
if ((doc as any)[attributeKey] === value) return
|
|
|
|
const attr = attribute.attr
|
|
|
|
if (client.getHierarchy().isMixin(attr.attributeOf)) {
|
|
|
|
await client.updateMixin(
|
|
|
|
doc._id,
|
|
|
|
_class,
|
|
|
|
doc.space,
|
|
|
|
attr.attributeOf,
|
|
|
|
{ [attributeKey]: value },
|
|
|
|
Date.now(),
|
|
|
|
modifyBy
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
if (client.getHierarchy().isDerived(attribute.attr.type._class, core.class.ArrOf)) {
|
|
|
|
const oldValue: any[] = (object as any)[attributeKey] ?? []
|
|
|
|
const val: any[] = value
|
|
|
|
const toPull = oldValue.filter((it: any) => !val.includes(it))
|
|
|
|
|
|
|
|
const toPush = val.filter((it) => !oldValue.includes(it))
|
|
|
|
if (toPull.length > 0) {
|
|
|
|
await client.update(object, { $pull: { [attributeKey]: { $in: toPull } } }, false, Date.now(), modifyBy)
|
|
|
|
}
|
|
|
|
if (toPush.length > 0) {
|
|
|
|
await client.update(
|
|
|
|
object,
|
|
|
|
{ $push: { [attributeKey]: { $each: toPush, $position: 0 } } },
|
|
|
|
false,
|
|
|
|
Date.now(),
|
|
|
|
modifyBy
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
await client.update(object, { [attributeKey]: value }, false, Date.now(), modifyBy)
|
|
|
|
}
|
2022-10-21 07:39:32 +00:00
|
|
|
}
|
2022-01-13 09:06:50 +00:00
|
|
|
}
|