diff --git a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository-with-cache.test.ts b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository-with-cache.test.ts new file mode 100644 index 000000000..83cbf9d75 --- /dev/null +++ b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository-with-cache.test.ts @@ -0,0 +1,531 @@ +import { Database } from '@nocobase/database'; +import { Cache } from '@nocobase/cache'; +import { mockServer, MockServer } from '@nocobase/test'; +import UiSchemaRepository, { GetJsonSchemaOptions, GetPropertiesOptions } from '../repository'; +import PluginUiSchema from '../server'; + +describe('ui_schema repository with cache', () => { + let app: MockServer; + let db: Database; + let cache: Cache; + let repository: UiSchemaRepository; + let schema; + + afterEach(async () => { + await app.destroy(); + }); + + beforeEach(async () => { + app = mockServer({ + registerActions: true, + }); + + db = app.db; + cache = app.cache; + + await db.sequelize.getQueryInterface().dropAllTables(); + + app.plugin(PluginUiSchema); + + await app.load(); + await db.sync({ + force: false, + alter: { + drop: false, + }, + }); + repository = db.getCollection('uiSchemas').repository as UiSchemaRepository; + repository.setCache(cache); + + schema = { + type: 'object', + title: 'title', + name: 'root', + 'x-uid': 'rootUid', + properties: { + a1: { + type: 'string', + title: 'A1', + 'x-component': 'Input', + properties: { + c1: { + 'x-uid': 'c1Uid', + type: 'string', + title: 'C1', + }, + c2: { + 'x-uid': 'c2Uid', + type: 'string', + title: 'C2', + }, + }, + }, + b1: { + 'x-async': true, // 添加了一个异步节点 + type: 'string', + title: 'B1', + properties: { + c1: { + type: 'string', + title: 'C1', + }, + d1: { + 'x-async': true, + type: 'string', + title: 'D1', + }, + }, + }, + }, + }; + }); + + it('repository init with cache', async () => { + expect(repository.cache).toBeDefined(); + }); + + it('should cache when getJsonSchema with readFromCache', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(xUid)).toBeUndefined(); + await repository.insert(schema); + const result = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + + // xUid's schema is cached + expect(await cache.get(`s_${xUid}`)).toMatchObject(result); + + // delete from database + await repository.destroy({ + filterByTk: xUid, + }); + const databaseSchema = await repository.findById(xUid); + expect(databaseSchema).toBeNull(); + + // also getJsonSchema from cache + const secondResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(secondResult).toMatchObject(result); + }); + + it('should not cache when getJsonSchema with readFromCache false', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(xUid)).toBeUndefined(); + await repository.insert(schema); + const result = await repository.getJsonSchema(xUid, { readFromCache: false } as GetJsonSchemaOptions); + expect(result).toBeDefined(); + // xUid's schema is not cached + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + }); + + it('should cache when getProperties with readFromCache', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(xUid)).toBeUndefined(); + await repository.insert(schema); + const result = await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions); + + // xUid's properties is cached + expect(await cache.get(`p_${xUid}`)).toMatchObject(result); + + // delete from database + await repository.destroy({ + filterByTk: xUid, + }); + const databaseSchema = await repository.findById(xUid); + expect(databaseSchema).toBeNull(); + + // also getProperties from cache + const secondResult = await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions); + expect(secondResult).toMatchObject(result); + }); + + it('should not cache when getProperties with readFromCache false', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(xUid)).toBeUndefined(); + await repository.insert(schema); + const result = await repository.getProperties(xUid, { readFromCache: false } as GetPropertiesOptions); + + expect(result).toBeDefined(); + // xUid's properties is not cached + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + }); + + it('should clear cache when remove', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(xUid)).toBeUndefined(); + + await repository.insert(schema); + + const sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + const pResult = await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions); + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + await repository.remove(xUid); + + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + }); + + it('should clear cache when remove children schema', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(xUid)).toBeUndefined(); + + await repository.insert(schema); + + let sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + expect(sResult.properties.a1.properties.c1).toBeDefined(); + + let pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + expect(pResult.properties.a1.properties.c1).toBeDefined(); + + await repository.remove(schema.properties.a1.properties.c1['x-uid']); + + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + expect(sResult.properties.a1.properties.c1).toBeUndefined(); + + pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + expect(pResult.properties.a1.properties.c1).toBeUndefined(); + }); + + it('should clear cache when patch', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + await repository.insert(schema); + + let sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + let pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.title).toEqual(schema.title); + expect(pResult.properties.a1.title).toEqual(schema.properties.a1.title); + + const newSchema = { + 'x-uid': xUid, + title: 'test-title', + properties: { + a1: { + type: 'string', + title: 'new a1 title', + 'x-component': 'Input', + }, + }, + }; + await repository.patch(newSchema); + + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + // patched result + sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.title).toEqual(newSchema.title); + expect(pResult.properties.a1.title).toEqual(newSchema.properties.a1.title); + }); + + it('should clear cache when patch children schema', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + await repository.insert(schema); + + let sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + let pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.properties.a1.properties.c1.title).toEqual(schema.properties.a1.properties.c1.title); + expect(pResult.properties.a1.properties.c1.title).toEqual(schema.properties.a1.properties.c1.title); + + const newSchema = { + 'x-uid': 'c1Uid', + type: 'string', + title: 'C1-test', + }; + await repository.patch(newSchema); + + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + // patched result + sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.properties.a1.properties.c1.title).toEqual(newSchema.title); + expect(pResult.properties.a1.properties.c1.title).toEqual(newSchema.title); + }); + + it('should clear cache when clearAncestor', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + await repository.insert(schema); + + let sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + let pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.title).toEqual(schema.title); + expect(pResult.properties.a1.title).toEqual(schema.properties.a1.title); + + await repository.clearAncestor(sResult.properties.a1['x-uid']); + + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + }); + + it('should clear cache when insertAdjacent', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + await repository.insert(schema); + + let sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + let pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.title).toEqual(schema.title); + expect(pResult.properties.a1.title).toEqual(schema.properties.a1.title); + await repository.insertAdjacent('beforeBegin', sResult.properties.a1['x-uid'], { + name: 'a0', + type: 'string', + title: 'a0 title', + 'x-component': 'Input', + }); + + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.properties?.a0).toBeDefined(); + expect(pResult.properties?.a0).toBeDefined(); + }); + + it('should clear cache when insertAdjacent children schema', async () => { + const xUid = schema['x-uid']; + // xUid not in cache + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + await repository.insert(schema); + + let sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + let pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.title).toEqual(schema.title); + expect(pResult.properties.a1.title).toEqual(schema.properties.a1.title); + const c0Schema = { + name: 'c0', + type: 'string', + title: 'c0 title', + 'x-component': 'Input', + }; + await repository.insertAdjacent('afterBegin', sResult.properties.a1['x-uid'], c0Schema); + + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.properties.a1.properties.c0.title).toEqual(c0Schema.title); + expect(pResult.properties.a1.properties.c0.title).toEqual(c0Schema.title); + }); + + it('should clear cache when move children schema 1', async () => { + const schema = { + name: 'A', + 'x-uid': 'A', + title: 'title A', + properties: { + B: { + name: 'B', + 'x-uid': 'B', + title: 'title B', + properties: { + C: { + name: 'C', + 'x-uid': 'C', + title: 'title C', + }, + }, + }, + D: { + name: 'D', + 'x-uid': 'D', + title: 'title D', + }, + }, + }; + const xUid = 'A'; + // xUid not in cache + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + await repository.insert(schema); + + let sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + let pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.properties.B.properties.C).toBeDefined(); + expect(sResult.properties.D).toBeDefined(); + expect(pResult.properties.B.properties.C).toBeDefined(); + expect(pResult.properties.D).toBeDefined(); + + await repository.insertAdjacent('afterBegin', schema.properties.D['x-uid'], schema.properties.B.properties.C); + + expect(await cache.get(`s_${xUid}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid}`)).toBeUndefined(); + + sResult = await repository.getJsonSchema(xUid, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid}`)).toMatchObject(sResult); + + pResult = (await repository.getProperties(xUid, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid}`)).toMatchObject(pResult); + + expect(sResult.properties.B.properties?.C).toBeUndefined(); + expect(sResult.properties.D.properties?.C).toBeDefined(); + expect(sResult.properties.D.properties?.C?.title).toEqual(schema.properties.B.properties.C.title); + + expect(pResult.properties.B.properties?.C).toBeUndefined(); + expect(pResult.properties.D.properties?.C).toBeDefined(); + }); + + it('should clear cache when move children schema 2', async () => { + const schema1 = { + name: 'A', + 'x-uid': 'A', + title: 'title A', + properties: { + B: { + name: 'B', + 'x-uid': 'B', + title: 'title B', + properties: { + C: { + name: 'C', + 'x-uid': 'C', + title: 'title C', + }, + }, + }, + }, + }; + const schema2 = { + name: 'D', + 'x-uid': 'D', + title: 'title D', + properties: { + E: { + name: 'E', + 'x-uid': 'E', + title: 'title E', + }, + }, + }; + const xUid_A = 'A'; + const xUid_D = 'D'; + // xUid not in cache + expect(await cache.get(`s_${xUid_A}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid_A}`)).toBeUndefined(); + expect(await cache.get(`s_${xUid_D}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid_D}`)).toBeUndefined(); + + await repository.insert(schema1); + await repository.insert(schema2); + + let sResult1 = await repository.getJsonSchema(xUid_A, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid_A}`)).toMatchObject(sResult1); + + let pResult1 = (await repository.getProperties(xUid_A, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid_A}`)).toMatchObject(pResult1); + + let sResult2 = await repository.getJsonSchema(xUid_D, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid_D}`)).toMatchObject(sResult2); + + let pResult2 = (await repository.getProperties(xUid_D, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid_D}`)).toMatchObject(pResult2); + + expect(sResult1.properties.B.properties.C).toBeDefined(); + expect(pResult1.properties.B.properties.C).toBeDefined(); + + expect(sResult2.properties.E).toBeDefined(); + expect(sResult2.properties.E).toBeDefined(); + expect(sResult2.properties.E?.properties?.C).toBeUndefined(); + expect(pResult2.properties.E?.properties?.C).toBeUndefined(); + + await repository.insertAdjacent('afterBegin', schema2.properties.E['x-uid'], schema1.properties.B.properties.C); + + expect(await cache.get(`s_${xUid_A}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid_A}`)).toBeUndefined(); + expect(await cache.get(`s_${xUid_D}`)).toBeUndefined(); + expect(await cache.get(`p_${xUid_D}`)).toBeUndefined(); + + sResult1 = await repository.getJsonSchema(xUid_A, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid_A}`)).toMatchObject(sResult1); + + pResult1 = (await repository.getProperties(xUid_A, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid_A}`)).toMatchObject(pResult1); + + sResult2 = await repository.getJsonSchema(xUid_D, { readFromCache: true } as GetJsonSchemaOptions); + expect(await cache.get(`s_${xUid_D}`)).toMatchObject(sResult2); + + pResult2 = (await repository.getProperties(xUid_D, { readFromCache: true } as GetPropertiesOptions)) as any; + expect(await cache.get(`p_${xUid_D}`)).toMatchObject(pResult2); + + expect(sResult1.properties.B?.properties?.C).toBeUndefined(); + expect(pResult1.properties.B?.properties?.C).toBeUndefined(); + + expect(sResult2.properties.E.properties.C).toBeDefined(); + expect(pResult2.properties.E.properties.C).toBeDefined(); + expect(sResult2.properties.E.properties?.C?.title).toEqual(schema1.properties.B.properties.C.title); + }); +}); diff --git a/packages/plugins/ui-schema-storage/src/actions/ui-schema-action.ts b/packages/plugins/ui-schema-storage/src/actions/ui-schema-action.ts index 8cc649dc6..42bdac337 100644 --- a/packages/plugins/ui-schema-storage/src/actions/ui-schema-action.ts +++ b/packages/plugins/ui-schema-storage/src/actions/ui-schema-action.ts @@ -1,10 +1,12 @@ import { Context } from '@nocobase/actions'; import { ActionParams } from '@nocobase/resourcer'; import lodash from 'lodash'; -import UiSchemaRepository from '../repository'; +import UiSchemaRepository, { GetJsonSchemaOptions, GetPropertiesOptions } from '../repository'; const getRepositoryFromCtx = (ctx: Context) => { - return ctx.db.getCollection('uiSchemas').repository as UiSchemaRepository; + const repo = ctx.db.getCollection('uiSchemas').repository as UiSchemaRepository; + repo.setCache(ctx.cache); + return repo; }; const callRepositoryMethod = (method, paramsKey: 'resourceIndex' | 'values', optionsBuilder?) => { @@ -33,12 +35,21 @@ function parseInsertAdjacentValues(values) { export const uiSchemaActions = { getJsonSchema: callRepositoryMethod('getJsonSchema', 'resourceIndex', (params: ActionParams) => { + const includeAsyncNode = params?.includeAsyncNode; return { - includeAsyncNode: params?.includeAsyncNode, - }; + readFromCache: !includeAsyncNode, + includeAsyncNode, + } as GetJsonSchemaOptions; }), - getProperties: callRepositoryMethod('getProperties', 'resourceIndex'), + getProperties: callRepositoryMethod( + 'getProperties', + 'resourceIndex', + () => + ({ + readFromCache: true, + } as GetPropertiesOptions), + ), insert: callRepositoryMethod('insert', 'values'), insertNewSchema: callRepositoryMethod('insertNewSchema', 'values'), remove: callRepositoryMethod('remove', 'resourceIndex'), @@ -78,7 +89,7 @@ export const uiSchemaActions = { }, transaction, }); - await db.getRepository('uiSchemas').clearAncestor(filterByTk, { transaction }); + await getRepositoryFromCtx(ctx).clearAncestor(filterByTk, { transaction }); ctx.body = { result: 'ok', }; diff --git a/packages/plugins/ui-schema-storage/src/repository.ts b/packages/plugins/ui-schema-storage/src/repository.ts index 0128497f4..4ab8e1d5f 100644 --- a/packages/plugins/ui-schema-storage/src/repository.ts +++ b/packages/plugins/ui-schema-storage/src/repository.ts @@ -1,11 +1,18 @@ import { Repository, Transactionable } from '@nocobase/database'; +import { Cache } from '@nocobase/cache'; import { uid } from '@nocobase/utils'; import lodash from 'lodash'; import { Transaction } from 'sequelize'; import { ChildOptions, SchemaNode, TargetPosition } from './dao/ui_schema_node_dao'; -interface GetJsonSchemaOptions { +export interface GetJsonSchemaOptions { includeAsyncNode?: boolean; + readFromCache?: boolean; + transaction?: Transaction; +} + +export interface GetPropertiesOptions { + readFromCache?: boolean; transaction?: Transaction; } @@ -64,6 +71,36 @@ function transaction(transactionAbleArgPosition?: number) { } export class UiSchemaRepository extends Repository { + cache: Cache; + + // if you need to handle cache in repo method, so you must set cache first + setCache(cache: Cache) { + this.cache = cache; + } + + /** + * clear cache with xUid which in uiSchemaTreePath's Path + * @param {string} xUid + * @param {Transaction} transaction + * @returns {Promise} + */ + async clearXUidPathCache(xUid: string, transaction: Transaction) { + if (!this.cache || !xUid) { + return; + } + // find all xUid node's parent nodes + const uiSchemaNodes = await this.database.getRepository('uiSchemaTreePath').find({ + filter: { + descendant: xUid, + }, + transaction: transaction, + }); + for (const uiSchemaNode of uiSchemaNodes) { + await this.cache.del(`p_${uiSchemaNode['ancestor']}`); + await this.cache.del(`s_${uiSchemaNode['ancestor']}`); + } + } + tableNameAdapter(tableName) { if (this.database.sequelize.getDialect() === 'postgres') { return `"${tableName}"`; @@ -144,7 +181,16 @@ export class UiSchemaRepository extends Repository { return carry; } - async getProperties(uid: string, options: Transactionable = {}) { + async getProperties(uid: string, options: GetPropertiesOptions = {}) { + if (options?.readFromCache && this.cache) { + return this.cache.wrap(`p_${uid}`, () => { + return this.doGetProperties(uid, options); + }); + } + return this.doGetProperties(uid, options); + } + + private async doGetProperties(uid: string, options: GetPropertiesOptions = {}) { const { transaction } = options; const db = this.database; @@ -174,7 +220,7 @@ export class UiSchemaRepository extends Repository { return lodash.pick(schema, ['type', 'properties']); } - async getJsonSchema(uid: string, options?: GetJsonSchemaOptions): Promise { + private async doGetJsonSchema(uid: string, options?: GetJsonSchemaOptions) { const db = this.database; const treeTable = this.uiSchemaTreePathTableName; @@ -201,8 +247,16 @@ export class UiSchemaRepository extends Repository { return {}; } - const schema = this.nodesToSchema(nodes[0], uid); - return schema; + return this.nodesToSchema(nodes[0], uid); + } + + async getJsonSchema(uid: string, options?: GetJsonSchemaOptions): Promise { + if (options?.readFromCache && this.cache) { + return this.cache.wrap(`s_${uid}`, () => { + return this.doGetJsonSchema(uid, options); + }); + } + return this.doGetJsonSchema(uid, options); } private ignoreSchemaProperties(schemaProperties) { @@ -257,6 +311,7 @@ export class UiSchemaRepository extends Repository { @transaction() async clearAncestor(uid: string, options?: Transactionable) { + await this.clearXUidPathCache(uid, options?.transaction); const db = this.database; const treeTable = this.uiSchemaTreePathTableName; @@ -283,6 +338,7 @@ export class UiSchemaRepository extends Repository { const { transaction } = options; const rootUid = newSchema['x-uid']; + await this.clearXUidPathCache(rootUid, transaction); const oldTree = await this.getJsonSchema(rootUid); const traverSchemaTree = async (schema, path = []) => { @@ -311,7 +367,7 @@ export class UiSchemaRepository extends Repository { } } - async updateNode(uid: string, schema: any, transaction?: Transaction) { + protected async updateNode(uid: string, schema: any, transaction?: Transaction) { const nodeModel = await this.findOne({ filter: { 'x-uid': uid, @@ -354,7 +410,7 @@ export class UiSchemaRepository extends Repository { return childrenCount === 0; } - async findParentUid(uid, transaction?) { + protected async findParentUid(uid, transaction?) { const parent = await this.database.getRepository('uiSchemaTreePath').findOne({ filter: { descendant: uid, @@ -408,6 +464,8 @@ export class UiSchemaRepository extends Repository { async removeEmptyParents(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) { const { transaction, uid, breakRemoveOn } = options; + await this.clearXUidPathCache(uid, transaction); + const removeParent = async (nodeUid: string) => { const parent = await this.isSingleChild(nodeUid, transaction); @@ -440,6 +498,8 @@ export class UiSchemaRepository extends Repository { async recursivelyRemoveIfNoChildren(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) { const { uid, transaction, breakRemoveOn } = options; + await this.clearXUidPathCache(uid, transaction); + const removeLeafNode = async (nodeUid: string) => { const isLeafNode = await this.isLeafNode(nodeUid, transaction); @@ -467,6 +527,7 @@ export class UiSchemaRepository extends Repository { async remove(uid: string, options?: Transactionable & removeParentOptions) { let { transaction } = options; + await this.clearXUidPathCache(uid, transaction); if (options?.removeParentsIfNoChildren) { await this.removeEmptyParents({ transaction, uid, breakRemoveOn: options.breakRemoveOn }); return; @@ -501,7 +562,12 @@ export class UiSchemaRepository extends Repository { } @transaction() - async insertBeside(targetUid: string, schema: any, side: 'before' | 'after', options?: InsertAdjacentOptions) { + protected async insertBeside( + targetUid: string, + schema: any, + side: 'before' | 'after', + options?: InsertAdjacentOptions, + ) { const { transaction } = options; const targetParent = await this.findParentUid(targetUid, transaction); @@ -537,7 +603,12 @@ export class UiSchemaRepository extends Repository { } @transaction() - async insertInner(targetUid: string, schema: any, position: 'first' | 'last', options?: InsertAdjacentOptions) { + protected async insertInner( + targetUid: string, + schema: any, + position: 'first' | 'last', + options?: InsertAdjacentOptions, + ) { const { transaction } = options; const nodes = UiSchemaRepository.schemaToSingleNodes(schema); @@ -585,6 +656,9 @@ export class UiSchemaRepository extends Repository { ) { const { transaction } = options; + // if schema is existed then clear origin path schema cache + await this.clearXUidPathCache(schema['x-uid'], transaction); + if (options.wrap) { // insert wrap schema using insertNewSchema const wrapSchemaNodes = await this.insertNewSchema(options.wrap, { @@ -615,7 +689,10 @@ export class UiSchemaRepository extends Repository { } } - return await this[`insert${lodash.upperFirst(position)}`](target, schema, options); + const result = await this[`insert${lodash.upperFirst(position)}`](target, schema, options); + // clear target schema path cache + await this.clearXUidPathCache(result['x-uid'], transaction); + return result; } @transaction() @@ -639,7 +716,7 @@ export class UiSchemaRepository extends Repository { } @transaction() - async insertNodes(nodes: SchemaNode[], options?: Transactionable) { + protected async insertNodes(nodes: SchemaNode[], options?: Transactionable) { const { transaction } = options; const insertedNodes = []; @@ -660,9 +737,11 @@ export class UiSchemaRepository extends Repository { async insert(schema: any, options?: Transactionable) { const nodes = UiSchemaRepository.schemaToSingleNodes(schema); const insertedNodes = await this.insertNodes(nodes, options); - return this.getJsonSchema(insertedNodes[0].get('x-uid'), { + const result = await this.getJsonSchema(insertedNodes[0].get('x-uid'), { transaction: options?.transaction, }); + await this.clearXUidPathCache(result['x-uid'], options?.transaction); + return result; } @transaction() @@ -730,9 +809,11 @@ export class UiSchemaRepository extends Repository { return nodes; } - return this.getJsonSchema(nodes[0]['x-uid'], { + const result = await this.getJsonSchema(nodes[0]['x-uid'], { transaction, }); + await this.clearXUidPathCache(result['x-uid'], transaction); + return result; } private async insertSchemaRecord(name, uid, schema, transaction) { @@ -1026,7 +1107,7 @@ WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and TreeTable.sort }, ); } - + await this.clearXUidPathCache(uid, transaction); return savedNode; } }