diff --git a/packages/napcat-rpc/src/client.ts b/packages/napcat-rpc/src/client.ts index 02ea594a..96a1548d 100644 --- a/packages/napcat-rpc/src/client.ts +++ b/packages/napcat-rpc/src/client.ts @@ -48,7 +48,7 @@ export function createDeepProxy (options: DeepProxyOptions): T { if (!cb) throw new Error(`Nested callback not found: ${id}`); return cb; }, - proxyCreator: (path, proxyRefId) => createProxyAtPath(path, proxyRefId), + proxyCreator: (path, proxyRefId, cachedProps) => createProxyAtPath(path, proxyRefId, cachedProps), })); const result = await callback(...args); return serialize(result, { callbackRegistry }); @@ -57,8 +57,11 @@ export function createDeepProxy (options: DeepProxyOptions): T { /** * 在指定路径创建代理 + * @param path 路径 + * @param refId 远程对象引用 ID + * @param cachedProps 缓存的属性值(避免属性访问需要 RPC) */ - function createProxyAtPath (path: PropertyKey[], refId?: string): unknown { + function createProxyAtPath (path: PropertyKey[], refId?: string, cachedProps?: Record): unknown { const proxyMeta: ProxyMeta = { path: [...path], isProxy: true, @@ -80,7 +83,12 @@ export function createDeepProxy (options: DeepProxyOptions): T { return undefined; } - // 返回新的子路径代理(继承 refId) + // 检查缓存属性(仅顶层代理,即 path 为空时) + if (path.length === 0 && cachedProps && typeof prop === 'string' && prop in cachedProps) { + return cachedProps[prop]; + } + + // 返回新的子路径代理(继承 refId,不继承 cachedProps) return createProxyAtPath([...path, prop], refId); }, @@ -203,7 +211,7 @@ export function createDeepProxy (options: DeepProxyOptions): T { if (!cb) throw new Error(`Callback not found: ${id}`); return cb; }, - proxyCreator: (proxyPath, proxyRefId) => createProxyAtPath(proxyPath, proxyRefId), + proxyCreator: (proxyPath, proxyRefId, cachedProps) => createProxyAtPath(proxyPath, proxyRefId, cachedProps), }); return deserialized; } @@ -214,7 +222,7 @@ export function createDeepProxy (options: DeepProxyOptions): T { if (!cb) throw new Error(`Callback not found: ${id}`); return cb; }, - proxyCreator: (proxyPath, proxyRefId) => createProxyAtPath(proxyPath, proxyRefId), + proxyCreator: (proxyPath, proxyRefId, cachedProps) => createProxyAtPath(proxyPath, proxyRefId, cachedProps), }); })(); } diff --git a/packages/napcat-rpc/src/serializer.ts b/packages/napcat-rpc/src/serializer.ts index 12efed61..4fda321e 100644 --- a/packages/napcat-rpc/src/serializer.ts +++ b/packages/napcat-rpc/src/serializer.ts @@ -61,7 +61,7 @@ export interface DeserializeContext { /** 回调解析器 */ callbackResolver?: (id: string) => Function; /** 代理创建器 */ - proxyCreator?: (path: PropertyKey[], refId?: string) => unknown; + proxyCreator?: (path: PropertyKey[], refId?: string, cachedProps?: Record) => unknown; /** 对象引用解析器 */ refResolver?: (refId: string) => unknown; } @@ -368,7 +368,15 @@ export function deserialize (data: SerializedValue, context: DeserializeContext } // 否则创建代理(客户端场景) if (proxyCreator) { - return proxyCreator([], data.refId); + // 反序列化缓存的属性 + let cachedValues: Record | undefined; + if (data.cachedProps) { + cachedValues = {}; + for (const [key, val] of Object.entries(data.cachedProps)) { + cachedValues[key] = deserialize(val, context); + } + } + return proxyCreator([], data.refId, cachedValues); } } return {}; diff --git a/packages/napcat-rpc/src/server.ts b/packages/napcat-rpc/src/server.ts index 0d2c3fb9..9d9c8bf4 100644 --- a/packages/napcat-rpc/src/server.ts +++ b/packages/napcat-rpc/src/server.ts @@ -155,15 +155,36 @@ export class RpcServer { /** * 存储对象引用并返回序列化的引用 + * 同时序列化可序列化的属性值,避免属性访问需要额外 RPC */ private storeObjectRef (value: unknown): SerializedValue { const refId = generateRefId(); this.objectRefs.set(refId, value); const className = value?.constructor?.name; + + // 序列化非函数属性 + const cachedProps: Record = {}; + if (value && typeof value === 'object') { + for (const key of Object.keys(value)) { + const propValue = (value as Record)[key]; + // 跳过函数(方法需要远程调用) + if (typeof propValue === 'function') { + continue; + } + // 序列化属性值 + try { + cachedProps[key] = serialize(propValue, { callbackRegistry: this.localCallbacks }); + } catch { + // 序列化失败的属性跳过,让客户端通过 RPC 获取 + } + } + } + return { type: SerializedValueType.OBJECT_REF, refId, className: className !== 'Object' ? className : undefined, + cachedProps: Object.keys(cachedProps).length > 0 ? cachedProps : undefined, }; } diff --git a/packages/napcat-rpc/src/types.ts b/packages/napcat-rpc/src/types.ts index 0da39100..d700d846 100644 --- a/packages/napcat-rpc/src/types.ts +++ b/packages/napcat-rpc/src/types.ts @@ -84,6 +84,8 @@ export interface SerializedValue { elements?: SerializedValue[]; /** 远程对象引用 ID(用于保持代理能力) */ refId?: string; + /** 缓存的属性值(OBJECT_REF 时使用,避免属性访问需要 RPC) */ + cachedProps?: Record; } /** diff --git a/packages/napcat-test/rpc.test.ts b/packages/napcat-test/rpc.test.ts index ff86bb9b..6f9c0d7a 100644 --- a/packages/napcat-test/rpc.test.ts +++ b/packages/napcat-test/rpc.test.ts @@ -786,9 +786,9 @@ describe('napcat-rpc RPC', () => { }); const user = await api.fetchUser(123); - // 对象返回代理 - expect(await user.id).toBe(123); - expect(await user.name).toBe('User123'); + // 对象返回代理,属性已缓存,可直接访问 + expect(user.id).toBe(123); + expect(user.name).toBe('User123'); }); }); @@ -866,16 +866,15 @@ describe('napcat-rpc RPC', () => { }); const items = await client.getItems(); - // 数组返回代理 - const item0 = await items[0]; - const item1 = await items[1]; - expect(await item0!.id).toBe(1); - expect(await item0!.name).toBe('Item1'); - expect(await item1!.id).toBe(2); + // 数组直接序列化,元素可直接访问 + expect(items[0]!.id).toBe(1); + expect(items[0]!.name).toBe('Item1'); + expect(items[1]!.id).toBe(2); const item = await client.getItemById(2); - expect(await item!.id).toBe(2); - expect(await item!.name).toBe('Item2'); + // 普通对象(无方法)直接序列化 + expect(item!.id).toBe(2); + expect(item!.name).toBe('Item2'); }); it('should handle Map and Set in nested structures', async () => { @@ -1095,9 +1094,9 @@ describe('napcat-rpc RPC', () => { expect(greeting).toBe('Hi, I am Alice'); const info = await user.getInfo(); - // 对象返回代理 - expect(await info.name).toBe('Alice'); - expect(await info.age).toBe(25); + // getInfo 返回普通对象,直接序列化,属性可直接访问 + expect(info.name).toBe('Alice'); + expect(info.age).toBe(25); // 更新远程对象状态 const newAge = await user.updateAge(30); @@ -1105,7 +1104,7 @@ describe('napcat-rpc RPC', () => { // 验证状态被更新 const updatedInfo = await user.getInfo(); - expect(await updatedInfo.age).toBe(30); + expect(updatedInfo.age).toBe(30); }); it('should support chained method calls on returned class instance', async () => { @@ -1218,7 +1217,7 @@ describe('napcat-rpc RPC', () => { }); it('should proxy simple objects and allow property access', async () => { - // 现在所有对象都返回代理,访问属性需要通过 RPC + // 简单对象(无方法)直接序列化,属性可直接访问 const { client } = createRpcPair({ getSimpleData () { return { id: 1, name: 'test', active: true }; @@ -1226,10 +1225,10 @@ describe('napcat-rpc RPC', () => { }); const data = await client.getSimpleData(); - // 对象现在也是代理,访问属性返回代理,await 后获取值 - expect(await data.id).toBe(1); - expect(await data.name).toBe('test'); - expect(await data.active).toBe(true); + // 普通对象直接序列化,属性可直接访问 + expect(data.id).toBe(1); + expect(data.name).toBe('test'); + expect(data.active).toBe(true); }); it('should handle async methods in returned class', async () => { @@ -1288,9 +1287,10 @@ describe('napcat-rpc RPC', () => { const factory = await client.getFactory(); - // 调用返回代理上的方法 + // 调用返回代理上的方法,返回带方法的对象被代理 const obj = await factory.create('widget1'); - expect(await obj.name).toBe('widget1'); + // 属性已缓存,可直接访问 + expect(obj.name).toBe('widget1'); // 在返回代理上调用构造函数 const Widget = await factory.Widget;