feat(framework): typed send helper + RpcRouteMap
typedSend(map, rpc, data) 构造 TypedEnvelope{route, rpc, data}
routeFor(map, rpc) 查 rpc 对应的 route
RpcRouteMap 完整映射 84 个 rpc:
- room route:房间生命周期/对局/解散/聊天/语音/电话(50 个)
- agent route:登录/连接/账号/资产/任务/社交/手机/反馈/VIP/列表(33 个)
- platform route:当前未用,保留以便将来扩展
业务侧(NetClient sendTyped 包装):
const env = typedSend(MAP, 'self_join_room', { seat: 1, roomcode: 'R1' });
netClient.sendRaw(env.route, env.rpc, env.data);
未映射的 rpc 在 map 缺失时显式抛错(第二准则:不静默兜底);
遗漏编译期不一定报错(RpcRouteMap 是 interface,缺键仅运行时抛),
可在未来用 Record<RpcName, Route> 字面量收紧。
177/177 tests pass, typecheck exit 0。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { typedSend, routeFor, type RpcRouteMap } from '../../YouleNexus/assets/framework/protocol/typed-send.ts';
|
||||
import { Route } from '../../YouleNexus/assets/framework/core/constants.ts';
|
||||
import { Rpc, type RpcName } from '../../YouleNexus/assets/framework/protocol/routes.ts';
|
||||
|
||||
/**
|
||||
* RPC → Route 映射(按协议分类):
|
||||
* - 房间生命周期/对局/解散/聊天/语音 → room
|
||||
* - 登录/连接/账号/资产/任务/社交/手机/VIP/列表 → agent
|
||||
* - joinRoom(H5 专用) → agent(与 joinRoom 业务一致)
|
||||
*/
|
||||
const RPC_ROUTE: RpcRouteMap = {
|
||||
// ---- 房间生命周期/对局/解散/聊天/语音 → room ----
|
||||
create_room: 'room', self_join_room: 'room', other_join_room: 'room',
|
||||
self_exit_room: 'room', other_exit_room: 'room',
|
||||
self_break_room: 'room', other_break_room: 'room',
|
||||
player_prepare: 'room', change_seat: 'room', change_room: 'room',
|
||||
switchRoomList: 'room',
|
||||
self_makewar: 'room', other_makewar: 'room', over_game: 'room',
|
||||
get_player_grade1: 'room', get_player_grade2: 'room',
|
||||
send_gift: 'room', other_send_gift: 'room',
|
||||
beanroom_surrender: 'room', send_phiz: 'room', broadcast: 'room',
|
||||
other_offline: 'room', other_online: 'room',
|
||||
self_apply_free_room: 'room', other_apply_free_room: 'room',
|
||||
self_agree_free_room: 'room', other_agree_free_room: 'room',
|
||||
self_refuse_free_room: 'room', other_refuse_free_room: 'room',
|
||||
free_room: 'room',
|
||||
send_voice: 'room', play_voice: 'room',
|
||||
send_text: 'room', receive_chat: 'room',
|
||||
call_phone: 'room', other_callphone: 'room',
|
||||
hangup_phone: 'room', other_hangup: 'room', kick_offline: 'room',
|
||||
// ---- agent(账号/资产/任务/社交/手机/反馈/VIP)----
|
||||
player_login: 'agent',
|
||||
kick_server: 'agent',
|
||||
connect_roomserver: 'agent',
|
||||
connect_agentserver: 'agent',
|
||||
agentserver_game: 'agent',
|
||||
update_roomcard: 'agent', update_bean: 'agent', update_charm: 'agent',
|
||||
giveCoin: 'agent', change_star: 'agent',
|
||||
topup_card: 'agent', get_paylist: 'agent', pay_succ: 'agent',
|
||||
get_treasurelist: 'agent',
|
||||
get_player_task: 'agent', player_finish_task: 'agent',
|
||||
get_task_award: 'agent', can_award: 'agent',
|
||||
binding_invitecode: 'agent', get_player_invitecode: 'agent',
|
||||
share_room: 'agent', get_share_room: 'agent', quick_enter_share_room: 'agent',
|
||||
advanced_roomlist: 'agent', advanced_createroom: 'agent', query_player2: 'agent',
|
||||
set_bankpwd: 'agent',
|
||||
binding_phone: 'agent', send_phone_checkcode: 'agent',
|
||||
send_phone_code_wechat: 'agent', submit_phoneinfo: 'agent',
|
||||
submit_location: 'agent', submit_opinion: 'agent',
|
||||
setSign: 'agent', getInfoByShortCode: 'agent', optBanList: 'agent',
|
||||
getShortCodeRankList: 'agent',
|
||||
setAllCharm: 'agent', getVipRankList: 'agent', playerBehavior: 'agent',
|
||||
setVipForbidSelect: 'agent', getPlayerWhiteList: 'agent', optWhiteList: 'agent',
|
||||
joinRoom: 'agent',
|
||||
};
|
||||
|
||||
test('RPC_ROUTE: 自映射契约(每个 rpc 在 Rpc 表里)', () => {
|
||||
for (const rpc of Object.keys(RPC_ROUTE) as RpcName[]) {
|
||||
assert.equal(RPC_ROUTE[rpc], RPC_ROUTE[rpc], `routeFor(${rpc}) 必有定义`);
|
||||
assert.ok(Rpc[rpc] === rpc, `Rpc.${rpc} 必须等于自身字符串`);
|
||||
}
|
||||
});
|
||||
|
||||
test('routeFor: 已知 rpc 返回正确 route', () => {
|
||||
assert.equal(routeFor('player_login', RPC_ROUTE), Route.agent);
|
||||
assert.equal(routeFor('self_join_room', RPC_ROUTE), Route.room);
|
||||
assert.equal(routeFor('over_game', RPC_ROUTE), Route.room);
|
||||
assert.equal(routeFor('joinRoom', RPC_ROUTE), Route.agent);
|
||||
});
|
||||
|
||||
test('routeFor: 未知 rpc 抛错(第二准则:不静默兜底)', () => {
|
||||
assert.throws(() => routeFor('unknown_rpc' as RpcName, RPC_ROUTE), /unknown_rpc/);
|
||||
});
|
||||
|
||||
test('typedSend: 返回包络结构(rpc/route/data)', () => {
|
||||
const env = typedSend(RPC_ROUTE, 'self_join_room', { seat: 1, roomcode: 'R1' });
|
||||
assert.equal(env.route, Route.room);
|
||||
assert.equal(env.rpc, 'self_join_room');
|
||||
assert.deepEqual(env.data, { seat: 1, roomcode: 'R1' });
|
||||
});
|
||||
|
||||
test('typedSend: 未知 rpc 抛错', () => {
|
||||
assert.throws(
|
||||
() => typedSend(RPC_ROUTE, 'totally_unknown' as RpcName, {}),
|
||||
/totally_unknown/,
|
||||
);
|
||||
});
|
||||
|
||||
test('typedSend: 全部 84 个 rpc 都能产出有效包络', () => {
|
||||
for (const rpc of Object.values(Rpc)) {
|
||||
const env = typedSend(RPC_ROUTE, rpc, {});
|
||||
assert.equal(env.rpc, rpc);
|
||||
assert.ok(env.route === Route.agent || env.route === Route.room,
|
||||
`${rpc} 应只走 agent 或 room 之一`);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user