From e25d9ed618ec0632cd261fd90536099ae3fe3b5a Mon Sep 17 00:00:00 2001 From: Andrey Platov Date: Thu, 5 Aug 2021 11:15:09 +0200 Subject: [PATCH] initial `$pushMixin` implementation Signed-off-by: Andrey Platov --- packages/core/src/__tests__/memdb.test.ts | 43 +++++++++++++++++------ packages/core/src/__tests__/minmodel.ts | 16 ++++++++- packages/core/src/operator.ts | 19 +++++++++- packages/core/src/tx.ts | 22 +++++++++++- packages/platform/src/ident.ts | 4 +-- packages/platform/src/platform.ts | 7 +++- 6 files changed, 95 insertions(+), 16 deletions(-) diff --git a/packages/core/src/__tests__/memdb.test.ts b/packages/core/src/__tests__/memdb.test.ts index 56def38107..133c9ebba9 100644 --- a/packages/core/src/__tests__/memdb.test.ts +++ b/packages/core/src/__tests__/memdb.test.ts @@ -19,10 +19,18 @@ import { Hierarchy } from '../hierarchy' import { ModelDb, TxDb } from '../memdb' import { SortingOrder } from '../storage' import { TxOperations } from '../tx' -import { genMinModel } from './minmodel' +import { genMinModel, test, TestMixin } from './minmodel' const txes = genMinModel() +async function createModel (): Promise<{ model: ModelDb, hierarchy: Hierarchy }> { + const hierarchy = new Hierarchy() + for (const tx of txes) await hierarchy.tx(tx) + const model = new ModelDb(hierarchy) + for (const tx of txes) await model.tx(tx) + return { model, hierarchy } +} + describe('memdb', () => { it('should save all tx', async () => { const hierarchy = new Hierarchy() @@ -34,21 +42,36 @@ describe('memdb', () => { }) it('should query model', async () => { - const hierarchy = new Hierarchy() - for (const tx of txes) await hierarchy.tx(tx) - const model = new ModelDb(hierarchy) - for (const tx of txes) await model.tx(tx) + const { model } = await createModel() const result = await model.findAll(core.class.Class, {}) - expect(result.length).toBe(9) + expect(result.length).toBe(10) const result2 = await model.findAll(core.class.Class, { _id: undefined }) expect(result2.length).toBe(0) }) + it('should create mixin', async () => { + const { model } = await createModel() + const ops = new TxOperations(model, core.account.System) + + await ops.createMixin(core.class.Obj, core.class.Class, test.mixin.TestMixin, { arr: ['hello'] }) + const objClass = (await model.findAll(core.class.Class, { _id: core.class.Obj }))[0] as any + expect(objClass['test:mixin:TestMixin'].arr).toEqual(expect.arrayContaining(['hello'])) + + await ops.updateDoc(test.mixin.TestMixin, core.space.Model, core.class.Obj as unknown as Ref, { + $pushMixin: { + $mixin: test.mixin.TestMixin, + values: { + arr: 'there' + } + } + }) + + const objClass2 = (await model.findAll(core.class.Class, { _id: core.class.Obj }))[0] as any + expect(objClass2['test:mixin:TestMixin'].arr).toEqual(expect.arrayContaining(['hello', 'there'])) + }) + it('should allow delete', async () => { - const hierarchy = new Hierarchy() - for (const tx of txes) await hierarchy.tx(tx) - const model = new ModelDb(hierarchy) - for (const tx of txes) await model.tx(tx) + const { model } = await createModel() const result = await model.findAll(core.class.Space, {}) expect(result.length).toBe(2) diff --git a/packages/core/src/__tests__/minmodel.ts b/packages/core/src/__tests__/minmodel.ts index 3e4e5c62c5..f95540c70c 100644 --- a/packages/core/src/__tests__/minmodel.ts +++ b/packages/core/src/__tests__/minmodel.ts @@ -13,7 +13,9 @@ // limitations under the License. // -import type { Class, Data, Doc, Obj, Ref } from '../classes' +import type { Plugin } from '@anticrm/platform' +import { plugin } from '@anticrm/platform' +import type { Class, Data, Doc, Obj, Ref, Mixin, Arr } from '../classes' import { ClassifierKind, DOMAIN_MODEL } from '../classes' import type { Tx, TxCreateDoc } from '../tx' import core from '../component' @@ -29,6 +31,16 @@ export function createDoc (_class: Ref>, attributes: Dat return txFactory.createTxCreateDoc(_class, core.space.Model, attributes) } +export interface TestMixin extends Doc { + arr: Arr +} + +export const test = plugin('test' as Plugin, { + mixin: { + TestMixin: '' as Ref> + } +}) + /** * Generate minimal model for testing purposes. * @returns R @@ -47,6 +59,8 @@ export function genMinModel (): Tx[] { txes.push(createClass(core.class.TxUpdateDoc, { extends: core.class.Tx, kind: ClassifierKind.CLASS })) txes.push(createClass(core.class.TxRemoveDoc, { extends: core.class.Tx, kind: ClassifierKind.CLASS })) + txes.push(createClass(test.mixin.TestMixin, { extends: core.class.Doc, kind: ClassifierKind.MIXIN })) + txes.push( createDoc(core.class.Space, { name: 'Sp1', diff --git a/packages/core/src/operator.ts b/packages/core/src/operator.ts index deda9a9746..ab1fb52d88 100644 --- a/packages/core/src/operator.ts +++ b/packages/core/src/operator.ts @@ -33,8 +33,25 @@ function $push (document: Doc, keyval: Record): void { } } +function $pushMixin (document: Doc, options: any): void { + const doc = document as any + const mixinId = options.$mixin + if (mixinId === undefined) { throw new Error('$mixin must be specified for $push_mixin operation') } + const mixin = doc[mixinId] + const keyval = options.values + for (const key in keyval) { + const arr = mixin[key] + if (arr === undefined) { + mixin[key] = [keyval[key]] + } else { + arr.push(keyval[key]) + } + } +} + const operators: Record = { - $push + $push, + $pushMixin } /** diff --git a/packages/core/src/tx.ts b/packages/core/src/tx.ts index a6c71ccd12..12557ed0d5 100644 --- a/packages/core/src/tx.ts +++ b/packages/core/src/tx.ts @@ -70,7 +70,17 @@ export interface PushOptions { /** * @public */ -export type DocumentUpdate = Partial> & PushOptions +export interface PushMixinOptions { + $pushMixin?: { + $mixin: Ref> + values: Partial>> + } +} + +/** + * @public + */ +export type DocumentUpdate = Partial> & PushOptions & PushMixinOptions /** * @public @@ -178,6 +188,16 @@ export class TxOperations implements Storage { const tx = this.txFactory.createTxRemoveDoc(_class, space, objectId) return this.storage.tx(tx) } + + createMixin( + objectId: Ref, + objectClass: Ref>, + mixin: Ref>, + attributes: ExtendedAttributes + ): Promise { + const tx = this.txFactory.createTxMixin(objectId, objectClass, mixin, attributes) + return this.storage.tx(tx) + } } /** diff --git a/packages/platform/src/ident.ts b/packages/platform/src/ident.ts index 292b3ff253..db8e4745ea 100644 --- a/packages/platform/src/ident.ts +++ b/packages/platform/src/ident.ts @@ -16,7 +16,7 @@ import type { Id, Plugin } from './platform' import { PlatformError, Status, Severity } from './status' -import platform from './platform' +import platform, { _ID_SEPARATOR } from './platform' /** * @internal @@ -31,7 +31,7 @@ export interface _IdInfo { * @internal */ export function _parseId (id: Id): _IdInfo { - const path = id.split('.') + const path = id.split(_ID_SEPARATOR) if (path.length !== 3) { throw new PlatformError( new Status(Severity.ERROR, platform.status.InvalidId, { id }) diff --git a/packages/platform/src/platform.ts b/packages/platform/src/platform.ts index f56b74701f..df78df03f1 100644 --- a/packages/platform/src/platform.ts +++ b/packages/platform/src/platform.ts @@ -63,6 +63,11 @@ export type StatusCode = {}> = IntlString */ export type Namespace = Record> +/** + * @internal + */ +export const _ID_SEPARATOR = ':' + function identify ( result: Record, prefix: string, @@ -73,7 +78,7 @@ function identify ( if (typeof result[key] === 'string') { throw new Error(`'identify' overwrites '${key}'.`) } - const ident = prefix + '.' + key + const ident = prefix + _ID_SEPARATOR + key result[key] = typeof value === 'string' ? ident