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:
parent
1bdc29b706
commit
40554d8151
@ -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);
|
||||
});
|
||||
});
|
@ -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<UiSchemaRepository>('uiSchemas').clearAncestor(filterByTk, { transaction });
|
||||
await getRepositoryFromCtx(ctx).clearAncestor(filterByTk, { transaction });
|
||||
ctx.body = {
|
||||
result: 'ok',
|
||||
};
|
||||
|
@ -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<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) {
|
||||
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<any> {
|
||||
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<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) {
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user