feat: ui schema cache (#877)

* feat(core/cache): support cache

* perf(plugins/ui-schema-storage): cache schema

* refactor(plugins/ui-schema-storage): handle schema cache in repository level, not in action

* fix(plugins/ui-schema-storage): jsonSchema use s_ + x-uid and properties use p_ + x-uid cache

prevent jsonSchema and properties cache override each other

* test(plugins/ui-schema-storage): add ui_schema repository with cache test

* build(create-nocobase-app): remove create-nocobase cli's cache-store-package option

* test(plugins/ui-schema-storage): add ui_schema repository with cache test with readFromCache false

* fix(plugins/ui-schema-storage): repository insertAdjacent and patch method clear cache fix

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
lyf-coder 2022-10-24 09:13:21 +08:00 committed by chenos
parent 1bdc29b706
commit 40554d8151
3 changed files with 643 additions and 20 deletions

View File

@ -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);
});
});

View File

@ -1,10 +1,12 @@
import { Context } from '@nocobase/actions'; import { Context } from '@nocobase/actions';
import { ActionParams } from '@nocobase/resourcer'; import { ActionParams } from '@nocobase/resourcer';
import lodash from 'lodash'; import lodash from 'lodash';
import UiSchemaRepository from '../repository'; import UiSchemaRepository, { GetJsonSchemaOptions, GetPropertiesOptions } from '../repository';
const getRepositoryFromCtx = (ctx: Context) => { 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?) => { const callRepositoryMethod = (method, paramsKey: 'resourceIndex' | 'values', optionsBuilder?) => {
@ -33,12 +35,21 @@ function parseInsertAdjacentValues(values) {
export const uiSchemaActions = { export const uiSchemaActions = {
getJsonSchema: callRepositoryMethod('getJsonSchema', 'resourceIndex', (params: ActionParams) => { getJsonSchema: callRepositoryMethod('getJsonSchema', 'resourceIndex', (params: ActionParams) => {
const includeAsyncNode = params?.includeAsyncNode;
return { 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'), insert: callRepositoryMethod('insert', 'values'),
insertNewSchema: callRepositoryMethod('insertNewSchema', 'values'), insertNewSchema: callRepositoryMethod('insertNewSchema', 'values'),
remove: callRepositoryMethod('remove', 'resourceIndex'), remove: callRepositoryMethod('remove', 'resourceIndex'),
@ -78,7 +89,7 @@ export const uiSchemaActions = {
}, },
transaction, transaction,
}); });
await db.getRepository<UiSchemaRepository>('uiSchemas').clearAncestor(filterByTk, { transaction }); await getRepositoryFromCtx(ctx).clearAncestor(filterByTk, { transaction });
ctx.body = { ctx.body = {
result: 'ok', result: 'ok',
}; };

View File

@ -1,11 +1,18 @@
import { Repository, Transactionable } from '@nocobase/database'; import { Repository, Transactionable } from '@nocobase/database';
import { Cache } from '@nocobase/cache';
import { uid } from '@nocobase/utils'; import { uid } from '@nocobase/utils';
import lodash from 'lodash'; import lodash from 'lodash';
import { Transaction } from 'sequelize'; import { Transaction } from 'sequelize';
import { ChildOptions, SchemaNode, TargetPosition } from './dao/ui_schema_node_dao'; import { ChildOptions, SchemaNode, TargetPosition } from './dao/ui_schema_node_dao';
interface GetJsonSchemaOptions { export interface GetJsonSchemaOptions {
includeAsyncNode?: boolean; includeAsyncNode?: boolean;
readFromCache?: boolean;
transaction?: Transaction;
}
export interface GetPropertiesOptions {
readFromCache?: boolean;
transaction?: Transaction; transaction?: Transaction;
} }
@ -64,6 +71,36 @@ function transaction(transactionAbleArgPosition?: number) {
} }
export class UiSchemaRepository extends Repository { 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<void>}
*/
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) { tableNameAdapter(tableName) {
if (this.database.sequelize.getDialect() === 'postgres') { if (this.database.sequelize.getDialect() === 'postgres') {
return `"${tableName}"`; return `"${tableName}"`;
@ -144,7 +181,16 @@ export class UiSchemaRepository extends Repository {
return carry; 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 { transaction } = options;
const db = this.database; const db = this.database;
@ -174,7 +220,7 @@ export class UiSchemaRepository extends Repository {
return lodash.pick(schema, ['type', 'properties']); return lodash.pick(schema, ['type', 'properties']);
} }
async getJsonSchema(uid: string, options?: GetJsonSchemaOptions): Promise<any> { private async doGetJsonSchema(uid: string, options?: GetJsonSchemaOptions) {
const db = this.database; const db = this.database;
const treeTable = this.uiSchemaTreePathTableName; const treeTable = this.uiSchemaTreePathTableName;
@ -201,8 +247,16 @@ export class UiSchemaRepository extends Repository {
return {}; return {};
} }
const schema = this.nodesToSchema(nodes[0], uid); return this.nodesToSchema(nodes[0], uid);
return schema; }
async getJsonSchema(uid: string, options?: GetJsonSchemaOptions): Promise<any> {
if (options?.readFromCache && this.cache) {
return this.cache.wrap(`s_${uid}`, () => {
return this.doGetJsonSchema(uid, options);
});
}
return this.doGetJsonSchema(uid, options);
} }
private ignoreSchemaProperties(schemaProperties) { private ignoreSchemaProperties(schemaProperties) {
@ -257,6 +311,7 @@ export class UiSchemaRepository extends Repository {
@transaction() @transaction()
async clearAncestor(uid: string, options?: Transactionable) { async clearAncestor(uid: string, options?: Transactionable) {
await this.clearXUidPathCache(uid, options?.transaction);
const db = this.database; const db = this.database;
const treeTable = this.uiSchemaTreePathTableName; const treeTable = this.uiSchemaTreePathTableName;
@ -283,6 +338,7 @@ export class UiSchemaRepository extends Repository {
const { transaction } = options; const { transaction } = options;
const rootUid = newSchema['x-uid']; const rootUid = newSchema['x-uid'];
await this.clearXUidPathCache(rootUid, transaction);
const oldTree = await this.getJsonSchema(rootUid); const oldTree = await this.getJsonSchema(rootUid);
const traverSchemaTree = async (schema, path = []) => { 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({ const nodeModel = await this.findOne({
filter: { filter: {
'x-uid': uid, 'x-uid': uid,
@ -354,7 +410,7 @@ export class UiSchemaRepository extends Repository {
return childrenCount === 0; return childrenCount === 0;
} }
async findParentUid(uid, transaction?) { protected async findParentUid(uid, transaction?) {
const parent = await this.database.getRepository('uiSchemaTreePath').findOne({ const parent = await this.database.getRepository('uiSchemaTreePath').findOne({
filter: { filter: {
descendant: uid, descendant: uid,
@ -408,6 +464,8 @@ export class UiSchemaRepository extends Repository {
async removeEmptyParents(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) { async removeEmptyParents(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) {
const { transaction, uid, breakRemoveOn } = options; const { transaction, uid, breakRemoveOn } = options;
await this.clearXUidPathCache(uid, transaction);
const removeParent = async (nodeUid: string) => { const removeParent = async (nodeUid: string) => {
const parent = await this.isSingleChild(nodeUid, transaction); const parent = await this.isSingleChild(nodeUid, transaction);
@ -440,6 +498,8 @@ export class UiSchemaRepository extends Repository {
async recursivelyRemoveIfNoChildren(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) { async recursivelyRemoveIfNoChildren(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) {
const { uid, transaction, breakRemoveOn } = options; const { uid, transaction, breakRemoveOn } = options;
await this.clearXUidPathCache(uid, transaction);
const removeLeafNode = async (nodeUid: string) => { const removeLeafNode = async (nodeUid: string) => {
const isLeafNode = await this.isLeafNode(nodeUid, transaction); const isLeafNode = await this.isLeafNode(nodeUid, transaction);
@ -467,6 +527,7 @@ export class UiSchemaRepository extends Repository {
async remove(uid: string, options?: Transactionable & removeParentOptions) { async remove(uid: string, options?: Transactionable & removeParentOptions) {
let { transaction } = options; let { transaction } = options;
await this.clearXUidPathCache(uid, transaction);
if (options?.removeParentsIfNoChildren) { if (options?.removeParentsIfNoChildren) {
await this.removeEmptyParents({ transaction, uid, breakRemoveOn: options.breakRemoveOn }); await this.removeEmptyParents({ transaction, uid, breakRemoveOn: options.breakRemoveOn });
return; return;
@ -501,7 +562,12 @@ export class UiSchemaRepository extends Repository {
} }
@transaction() @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 { transaction } = options;
const targetParent = await this.findParentUid(targetUid, transaction); const targetParent = await this.findParentUid(targetUid, transaction);
@ -537,7 +603,12 @@ export class UiSchemaRepository extends Repository {
} }
@transaction() @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 { transaction } = options;
const nodes = UiSchemaRepository.schemaToSingleNodes(schema); const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
@ -585,6 +656,9 @@ export class UiSchemaRepository extends Repository {
) { ) {
const { transaction } = options; const { transaction } = options;
// if schema is existed then clear origin path schema cache
await this.clearXUidPathCache(schema['x-uid'], transaction);
if (options.wrap) { if (options.wrap) {
// insert wrap schema using insertNewSchema // insert wrap schema using insertNewSchema
const wrapSchemaNodes = await this.insertNewSchema(options.wrap, { 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() @transaction()
@ -639,7 +716,7 @@ export class UiSchemaRepository extends Repository {
} }
@transaction() @transaction()
async insertNodes(nodes: SchemaNode[], options?: Transactionable) { protected async insertNodes(nodes: SchemaNode[], options?: Transactionable) {
const { transaction } = options; const { transaction } = options;
const insertedNodes = []; const insertedNodes = [];
@ -660,9 +737,11 @@ export class UiSchemaRepository extends Repository {
async insert(schema: any, options?: Transactionable) { async insert(schema: any, options?: Transactionable) {
const nodes = UiSchemaRepository.schemaToSingleNodes(schema); const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
const insertedNodes = await this.insertNodes(nodes, options); 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, transaction: options?.transaction,
}); });
await this.clearXUidPathCache(result['x-uid'], options?.transaction);
return result;
} }
@transaction() @transaction()
@ -730,9 +809,11 @@ export class UiSchemaRepository extends Repository {
return nodes; return nodes;
} }
return this.getJsonSchema(nodes[0]['x-uid'], { const result = await this.getJsonSchema(nodes[0]['x-uid'], {
transaction, transaction,
}); });
await this.clearXUidPathCache(result['x-uid'], transaction);
return result;
} }
private async insertSchemaRecord(name, uid, schema, transaction) { 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; return savedNode;
} }
} }