[add] first templates
This commit is contained in:
commit
d797a2a41a
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.DS_STORE
|
||||||
|
*.meta
|
11
.mocharc.js
Normal file
11
.mocharc.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module.exports = {
|
||||||
|
require: [
|
||||||
|
'ts-node/register',
|
||||||
|
],
|
||||||
|
timeout: 999999,
|
||||||
|
exit: true,
|
||||||
|
spec: [
|
||||||
|
'./test/**/*.test.ts'
|
||||||
|
],
|
||||||
|
'preserve-symlinks': true
|
||||||
|
}
|
30
.vscode/launch.json
vendored
Normal file
30
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "mocha current file",
|
||||||
|
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
||||||
|
"args": [
|
||||||
|
"${file}"
|
||||||
|
],
|
||||||
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "ts-node current file",
|
||||||
|
"protocol": "inspector",
|
||||||
|
"args": [
|
||||||
|
"${relativeFile}"
|
||||||
|
],
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"-r",
|
||||||
|
"ts-node/register"
|
||||||
|
],
|
||||||
|
"internalConsoleOptions": "openOnSessionStart"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||||
|
}
|
30
Dockerfile
Normal file
30
Dockerfile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
FROM node
|
||||||
|
|
||||||
|
# 使用淘宝 NPM 镜像(国内机器构建推荐启用)
|
||||||
|
# RUN npm config set registry https://registry.npm.taobao.org/
|
||||||
|
|
||||||
|
# npm install
|
||||||
|
ADD package*.json /src/
|
||||||
|
WORKDIR /src
|
||||||
|
RUN npm i
|
||||||
|
|
||||||
|
# build
|
||||||
|
ADD . /src
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# clean
|
||||||
|
RUN npm prune --production
|
||||||
|
|
||||||
|
# move
|
||||||
|
RUN rm -rf /app \
|
||||||
|
&& mv dist /app \
|
||||||
|
&& mv node_modules /app/ \
|
||||||
|
&& rm -rf /src
|
||||||
|
|
||||||
|
# ENV
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
CMD node index.js
|
49
README.md
Normal file
49
README.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# TSRPC Server
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
### Local dev server
|
||||||
|
|
||||||
|
Dev server would restart automatically when code changed.
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run unit Test
|
||||||
|
Execute `npm run dev` first, then execute:
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Scripts
|
||||||
|
|
||||||
|
### Generate API document
|
||||||
|
|
||||||
|
Generate API document in swagger/openapi and markdown format.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run doc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate ServiceProto
|
||||||
|
```
|
||||||
|
npm run proto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate API templates
|
||||||
|
```
|
||||||
|
npm run api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manually sync shared code
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run sync
|
||||||
|
```
|
3672
package-lock.json
generated
Normal file
3672
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
package.json
Normal file
27
package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "backend-.",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"proto": "tsrpc proto",
|
||||||
|
"sync": "tsrpc link",
|
||||||
|
"api": "tsrpc api",
|
||||||
|
"doc": "tsrpc doc",
|
||||||
|
"dev": "tsrpc dev",
|
||||||
|
"test": "mocha test/**/*.test.ts",
|
||||||
|
"build": "tsrpc build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/mocha": "^8.2.3",
|
||||||
|
"@types/node": "^15.14.9",
|
||||||
|
"mocha": "^9.1.3",
|
||||||
|
"onchange": "^7.1.0",
|
||||||
|
"ts-node": "^10.4.0",
|
||||||
|
"tsrpc-cli": "^2.2.2",
|
||||||
|
"typescript": "^4.5.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tsrpc": "^3.1.3"
|
||||||
|
}
|
||||||
|
}
|
12
src/api/ApiJoin.ts
Normal file
12
src/api/ApiJoin.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { ApiCallWs } from "tsrpc";
|
||||||
|
import { roomInstance } from "..";
|
||||||
|
import { ReqJoin, ResJoin } from "../shared/protocols/PtlJoin";
|
||||||
|
|
||||||
|
export async function ApiJoin(call: ApiCallWs<ReqJoin, ResJoin>) {
|
||||||
|
let playerId = roomInstance.join(call.req, call.conn);
|
||||||
|
|
||||||
|
call.succ({
|
||||||
|
playerId: playerId,
|
||||||
|
gameState: roomInstance.gameSystem.state
|
||||||
|
})
|
||||||
|
}
|
39
src/index.ts
Normal file
39
src/index.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import 'k8w-extend-native';
|
||||||
|
import * as path from "path";
|
||||||
|
import { WsConnection, WsServer } from "tsrpc";
|
||||||
|
import { Room } from './models/Room';
|
||||||
|
import { serviceProto, ServiceType } from './shared/protocols/serviceProto';
|
||||||
|
|
||||||
|
// 创建 TSRPC WebSocket Server
|
||||||
|
export const server = new WsServer(serviceProto, {
|
||||||
|
port: 3000,
|
||||||
|
json: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 断开连接后退出房间
|
||||||
|
server.flows.postDisconnectFlow.push(v => {
|
||||||
|
let conn = v.conn as WsConnection<ServiceType>;
|
||||||
|
if (conn.playerId) {
|
||||||
|
roomInstance.leave(conn.playerId, conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const roomInstance = new Room(server);
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
async function init() {
|
||||||
|
// 挂载 API 接口
|
||||||
|
await server.autoImplementApi(path.resolve(__dirname, 'api'));
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Prepare something... (e.g. connect the db)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 启动入口点
|
||||||
|
async function main() {
|
||||||
|
await init();
|
||||||
|
await server.start();
|
||||||
|
}
|
||||||
|
main();
|
101
src/models/Room.ts
Normal file
101
src/models/Room.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { WsConnection, WsServer } from "tsrpc";
|
||||||
|
import { gameConfig } from "../shared/game/gameConfig";
|
||||||
|
import { GameSystem, GameSystemInput, PlayerJoin } from "../shared/game/GameSystem";
|
||||||
|
import { ReqJoin } from "../shared/protocols/PtlJoin";
|
||||||
|
import { ServiceType } from "../shared/protocols/serviceProto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端 - 房间 - 逻辑系统
|
||||||
|
*/
|
||||||
|
export class Room {
|
||||||
|
|
||||||
|
// 帧同步频率,次数/秒
|
||||||
|
syncRate = gameConfig.syncRate;
|
||||||
|
nextPlayerId = 1;
|
||||||
|
|
||||||
|
gameSystem = new GameSystem();
|
||||||
|
|
||||||
|
server: WsServer<ServiceType>;
|
||||||
|
conns: WsConnection<ServiceType>[] = [];
|
||||||
|
pendingInputs: GameSystemInput[] = [];
|
||||||
|
playerLastSn: { [playerId: number]: number | undefined } = {};
|
||||||
|
lastSyncTime?: number;
|
||||||
|
|
||||||
|
constructor(server: WsServer<ServiceType>) {
|
||||||
|
this.server = server;
|
||||||
|
setInterval(() => { this.sync() }, 1000 / this.syncRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加入房间 */
|
||||||
|
join(req: ReqJoin, conn: WsConnection<ServiceType>) {
|
||||||
|
let input: PlayerJoin = {
|
||||||
|
type: 'PlayerJoin',
|
||||||
|
playerId: this.nextPlayerId++,
|
||||||
|
// 初始位置随机
|
||||||
|
pos: {
|
||||||
|
x: Math.random() * 10 - 5,
|
||||||
|
y: Math.random() * 10 - 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.applyInput(input);
|
||||||
|
|
||||||
|
this.conns.push(conn);
|
||||||
|
conn.playerId = input.playerId;
|
||||||
|
conn.listenMsg('client/ClientInput', call => {
|
||||||
|
this.playerLastSn[input.playerId] = call.msg.sn;
|
||||||
|
call.msg.inputs.forEach(v => {
|
||||||
|
this.applyInput({
|
||||||
|
...v,
|
||||||
|
playerId: input.playerId
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return input.playerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyInput(input: GameSystemInput) {
|
||||||
|
this.pendingInputs.push(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
sync() {
|
||||||
|
let inputs = this.pendingInputs;
|
||||||
|
this.pendingInputs = [];
|
||||||
|
|
||||||
|
// Apply inputs
|
||||||
|
inputs.forEach(v => {
|
||||||
|
this.gameSystem.applyInput(v)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply TimePast
|
||||||
|
let now = process.uptime() * 1000;
|
||||||
|
this.applyInput({
|
||||||
|
type: 'TimePast',
|
||||||
|
dt: now - (this.lastSyncTime ?? now)
|
||||||
|
});
|
||||||
|
this.lastSyncTime = now;
|
||||||
|
|
||||||
|
// 发送同步帧
|
||||||
|
this.conns.forEach(v => {
|
||||||
|
v.sendMsg('server/Frame', {
|
||||||
|
inputs: inputs,
|
||||||
|
lastSn: this.playerLastSn[v.playerId!]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 离开房间 */
|
||||||
|
leave(playerId: number, conn: WsConnection<ServiceType>) {
|
||||||
|
this.conns.removeOne(v => v.playerId === playerId);
|
||||||
|
this.applyInput({
|
||||||
|
type: 'PlayerLeave',
|
||||||
|
playerId: playerId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'tsrpc' {
|
||||||
|
export interface WsConnection {
|
||||||
|
playerId?: number;
|
||||||
|
}
|
||||||
|
}
|
143
src/shared/game/GameSystem.ts
Normal file
143
src/shared/game/GameSystem.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { gameConfig } from "./gameConfig";
|
||||||
|
import { ArrowState } from "./state/ArrowState";
|
||||||
|
import { PlayerState } from "./state/PlayerState";
|
||||||
|
|
||||||
|
// 状态定义
|
||||||
|
export interface GameSystemState {
|
||||||
|
// 当前的时间(游戏时间)
|
||||||
|
now: number,
|
||||||
|
// 玩家
|
||||||
|
players: PlayerState[],
|
||||||
|
// 飞行中的箭矢
|
||||||
|
arrows: ArrowState[],
|
||||||
|
// 箭矢的 ID 生成
|
||||||
|
nextArrowId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前后端复用的状态计算模块
|
||||||
|
*/
|
||||||
|
export class GameSystem {
|
||||||
|
|
||||||
|
// 当前状态
|
||||||
|
private _state: GameSystemState = {
|
||||||
|
now: 0,
|
||||||
|
players: [],
|
||||||
|
arrows: [],
|
||||||
|
nextArrowId: 1
|
||||||
|
}
|
||||||
|
get state(): Readonly<GameSystemState> {
|
||||||
|
return this._state
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重设状态
|
||||||
|
reset(state: GameSystemState) {
|
||||||
|
this._state = Object.merge({}, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用输入,计算状态变更
|
||||||
|
applyInput(input: GameSystemInput) {
|
||||||
|
if (input.type === 'PlayerMove') {
|
||||||
|
let player = this._state.players.find(v => v.id === input.playerId);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.dizzyEndTime && player.dizzyEndTime > this._state.now) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.pos.x += input.speed.x * input.dt;
|
||||||
|
player.pos.y += input.speed.y * input.dt;
|
||||||
|
}
|
||||||
|
else if (input.type === 'PlayerAttack') {
|
||||||
|
let player = this._state.players.find(v => v.id === input.playerId);
|
||||||
|
if (player) {
|
||||||
|
let newArrow: ArrowState = {
|
||||||
|
id: this._state.nextArrowId++,
|
||||||
|
fromPlayerId: input.playerId,
|
||||||
|
targetPos: { ...input.targetPos },
|
||||||
|
targetTime: input.targetTime
|
||||||
|
};
|
||||||
|
this._state.arrows.push(newArrow);
|
||||||
|
this.onNewArrow.forEach(v => v(newArrow));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (input.type === 'PlayerJoin') {
|
||||||
|
this.state.players.push({
|
||||||
|
id: input.playerId,
|
||||||
|
pos: { ...input.pos }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (input.type === 'PlayerLeave') {
|
||||||
|
this.state.players.remove(v => v.id === input.playerId);
|
||||||
|
}
|
||||||
|
else if (input.type === 'TimePast') {
|
||||||
|
this._state.now += input.dt;
|
||||||
|
|
||||||
|
// 落地的 Arrow
|
||||||
|
for (let i = this._state.arrows.length - 1; i > -1; --i) {
|
||||||
|
let arrow = this._state.arrows[i];
|
||||||
|
if (arrow.targetTime <= this._state.now) {
|
||||||
|
// 伤害判定
|
||||||
|
let damagedPlayers = this._state.players.filter(v => {
|
||||||
|
return (v.pos.x - arrow.targetPos.x) * (v.pos.x - arrow.targetPos.x) + (v.pos.y - arrow.targetPos.y) * (v.pos.y - arrow.targetPos.y) <= gameConfig.arrowAttackRadius * gameConfig.arrowAttackRadius
|
||||||
|
});
|
||||||
|
damagedPlayers.forEach(p => {
|
||||||
|
// 设置击晕状态
|
||||||
|
p.dizzyEndTime = this._state.now + gameConfig.arrowDizzyTime;
|
||||||
|
|
||||||
|
// Event
|
||||||
|
})
|
||||||
|
|
||||||
|
this._state.arrows.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 事件
|
||||||
|
* 某些转瞬即逝的事件,可能不会直观的体现在前后两帧状态的变化中,但表面层又需要知晓。
|
||||||
|
* 例如一颗狙击枪的子弹,在少于一帧的时间内创建和销毁,前后两帧的状态中都不包含这颗子弹;但表现层却需要绘制出子弹的弹道。
|
||||||
|
* 此时,可以通过事件的方式通知表现层。
|
||||||
|
*/
|
||||||
|
// 发射箭矢
|
||||||
|
onNewArrow: ((arrow: ArrowState) => void)[] = [];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerMove {
|
||||||
|
type: 'PlayerMove',
|
||||||
|
playerId: number,
|
||||||
|
speed: { x: number, y: number },
|
||||||
|
// 移动的时间 (秒)
|
||||||
|
dt: number,
|
||||||
|
}
|
||||||
|
export interface PlayerAttack {
|
||||||
|
type: 'PlayerAttack',
|
||||||
|
playerId: number,
|
||||||
|
// 落点坐标
|
||||||
|
targetPos: { x: number, y: number },
|
||||||
|
// 落点时间(游戏时间)
|
||||||
|
targetTime: number
|
||||||
|
}
|
||||||
|
export interface PlayerJoin {
|
||||||
|
type: 'PlayerJoin',
|
||||||
|
playerId: number,
|
||||||
|
pos: { x: number, y: number }
|
||||||
|
}
|
||||||
|
export interface PlayerLeave {
|
||||||
|
type: 'PlayerLeave',
|
||||||
|
playerId: number
|
||||||
|
}
|
||||||
|
// 时间流逝
|
||||||
|
export interface TimePast {
|
||||||
|
type: 'TimePast',
|
||||||
|
dt: number
|
||||||
|
}
|
||||||
|
// 输入定义
|
||||||
|
export type GameSystemInput = PlayerMove
|
||||||
|
| PlayerAttack
|
||||||
|
| PlayerJoin
|
||||||
|
| PlayerLeave
|
||||||
|
| TimePast;
|
14
src/shared/game/gameConfig.ts
Normal file
14
src/shared/game/gameConfig.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const gameConfig = {
|
||||||
|
syncRate: 10,
|
||||||
|
|
||||||
|
moveSpeed: 10,
|
||||||
|
|
||||||
|
// 箭矢飞行时间(毫秒)
|
||||||
|
arrowFlyTime: 500,
|
||||||
|
// 箭矢投掷距离
|
||||||
|
arrowDistance: 8,
|
||||||
|
// 箭矢落地命中判定半径
|
||||||
|
arrowAttackRadius: 2,
|
||||||
|
// 被箭矢几种后的晕眩时间(毫秒)
|
||||||
|
arrowDizzyTime: 1000
|
||||||
|
}
|
9
src/shared/game/state/ArrowState.ts
Normal file
9
src/shared/game/state/ArrowState.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export type ArrowState = {
|
||||||
|
id: number,
|
||||||
|
// 谁发出的箭
|
||||||
|
fromPlayerId: number,
|
||||||
|
// 落地时间(游戏时间)
|
||||||
|
targetTime: number,
|
||||||
|
// 落点位置(游戏位置)
|
||||||
|
targetPos: { x: number, y: number }
|
||||||
|
}
|
7
src/shared/game/state/PlayerState.ts
Normal file
7
src/shared/game/state/PlayerState.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface PlayerState {
|
||||||
|
id: number,
|
||||||
|
// 位置
|
||||||
|
pos: { x: number, y: number },
|
||||||
|
// 晕眩结束时间
|
||||||
|
dizzyEndTime?: number,
|
||||||
|
}
|
15
src/shared/protocols/PtlJoin.ts
Normal file
15
src/shared/protocols/PtlJoin.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { GameSystemState } from "../game/GameSystem";
|
||||||
|
|
||||||
|
/** 加入房间 */
|
||||||
|
export interface ReqJoin {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResJoin {
|
||||||
|
/** 加入房间后,自己的 ID */
|
||||||
|
playerId: number,
|
||||||
|
/** 状态同步:一次性同步当前状态 */
|
||||||
|
gameState: GameSystemState
|
||||||
|
}
|
||||||
|
|
||||||
|
// export const conf = {}
|
9
src/shared/protocols/client/MsgClientInput.ts
Normal file
9
src/shared/protocols/client/MsgClientInput.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { PlayerAttack, PlayerMove } from "../../game/GameSystem";
|
||||||
|
|
||||||
|
/** 发送自己的输入 */
|
||||||
|
export interface MsgClientInput {
|
||||||
|
sn: number,
|
||||||
|
inputs: ClientInput[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClientInput = Omit<PlayerMove, 'playerId'> | Omit<PlayerAttack, 'playerId'>;
|
11
src/shared/protocols/server/MsgFrame.ts
Normal file
11
src/shared/protocols/server/MsgFrame.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { GameSystemInput } from "../../game/GameSystem";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端定期广播的同步帧
|
||||||
|
* 包含了这一段期间所有输入
|
||||||
|
*/
|
||||||
|
export interface MsgFrame {
|
||||||
|
inputs: GameSystemInput[],
|
||||||
|
/** 当前用户提交的,经服务端确认的最后一条输入的 SN */
|
||||||
|
lastSn?: number
|
||||||
|
}
|
496
src/shared/protocols/serviceProto.ts
Normal file
496
src/shared/protocols/serviceProto.ts
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
import { ServiceProto } from 'tsrpc-proto';
|
||||||
|
import { MsgClientInput } from './client/MsgClientInput';
|
||||||
|
import { ReqJoin, ResJoin } from './PtlJoin';
|
||||||
|
import { MsgFrame } from './server/MsgFrame';
|
||||||
|
|
||||||
|
export interface ServiceType {
|
||||||
|
api: {
|
||||||
|
"Join": {
|
||||||
|
req: ReqJoin,
|
||||||
|
res: ResJoin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
msg: {
|
||||||
|
"client/ClientInput": MsgClientInput,
|
||||||
|
"server/Frame": MsgFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const serviceProto: ServiceProto<ServiceType> = {
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "client/ClientInput",
|
||||||
|
"type": "msg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Join",
|
||||||
|
"type": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "server/Frame",
|
||||||
|
"type": "msg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": {
|
||||||
|
"client/MsgClientInput/MsgClientInput": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "sn",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "inputs",
|
||||||
|
"type": {
|
||||||
|
"type": "Array",
|
||||||
|
"elementType": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "client/MsgClientInput/ClientInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"client/MsgClientInput/ClientInput": {
|
||||||
|
"type": "Union",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"type": {
|
||||||
|
"target": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/PlayerMove"
|
||||||
|
},
|
||||||
|
"keys": [
|
||||||
|
"playerId"
|
||||||
|
],
|
||||||
|
"type": "Omit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": {
|
||||||
|
"target": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/PlayerAttack"
|
||||||
|
},
|
||||||
|
"keys": [
|
||||||
|
"playerId"
|
||||||
|
],
|
||||||
|
"type": "Omit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/GameSystem/PlayerMove": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "type",
|
||||||
|
"type": {
|
||||||
|
"type": "Literal",
|
||||||
|
"literal": "PlayerMove"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "playerId",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "speed",
|
||||||
|
"type": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "x",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "y",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "dt",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/GameSystem/PlayerAttack": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "type",
|
||||||
|
"type": {
|
||||||
|
"type": "Literal",
|
||||||
|
"literal": "PlayerAttack"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "playerId",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "targetPos",
|
||||||
|
"type": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "x",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "y",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "targetTime",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PtlJoin/ReqJoin": {
|
||||||
|
"type": "Interface"
|
||||||
|
},
|
||||||
|
"PtlJoin/ResJoin": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "playerId",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "gameState",
|
||||||
|
"type": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/GameSystemState"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/GameSystem/GameSystemState": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "now",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "players",
|
||||||
|
"type": {
|
||||||
|
"type": "Array",
|
||||||
|
"elementType": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/state/PlayerState/PlayerState"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "arrows",
|
||||||
|
"type": {
|
||||||
|
"type": "Array",
|
||||||
|
"elementType": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/state/ArrowState/ArrowState"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "nextArrowId",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/state/PlayerState/PlayerState": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "pos",
|
||||||
|
"type": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "x",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "y",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "dizzyEndTime",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
},
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/state/ArrowState/ArrowState": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "fromPlayerId",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "targetTime",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "targetPos",
|
||||||
|
"type": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "x",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "y",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"server/MsgFrame/MsgFrame": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "inputs",
|
||||||
|
"type": {
|
||||||
|
"type": "Array",
|
||||||
|
"elementType": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/GameSystemInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "lastSn",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
},
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/GameSystem/GameSystemInput": {
|
||||||
|
"type": "Union",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"type": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/PlayerMove"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/PlayerAttack"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/PlayerJoin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"type": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/PlayerLeave"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"type": {
|
||||||
|
"type": "Reference",
|
||||||
|
"target": "../game/GameSystem/TimePast"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/GameSystem/PlayerJoin": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "type",
|
||||||
|
"type": {
|
||||||
|
"type": "Literal",
|
||||||
|
"literal": "PlayerJoin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "playerId",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "pos",
|
||||||
|
"type": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "x",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "y",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/GameSystem/PlayerLeave": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "type",
|
||||||
|
"type": {
|
||||||
|
"type": "Literal",
|
||||||
|
"literal": "PlayerLeave"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "playerId",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"../game/GameSystem/TimePast": {
|
||||||
|
"type": "Interface",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "type",
|
||||||
|
"type": {
|
||||||
|
"type": "Literal",
|
||||||
|
"literal": "TimePast"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "dt",
|
||||||
|
"type": {
|
||||||
|
"type": "Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
39
test/api/ApiSend.test.ts
Normal file
39
test/api/ApiSend.test.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { TsrpcError, WsClient } from "tsrpc";
|
||||||
|
import { serviceProto, ServiceType } from "../../src/shared/protocols/serviceProto";
|
||||||
|
|
||||||
|
// 1. EXECUTE `npm run dev` TO START A LOCAL DEV SERVER
|
||||||
|
// 2. EXECUTE `npm test` TO START UNIT TEST
|
||||||
|
|
||||||
|
describe("ApiSend", function (): void {
|
||||||
|
let client: WsClient<ServiceType> = new WsClient(serviceProto, {
|
||||||
|
server: "ws://127.0.0.1:3000",
|
||||||
|
logger: console
|
||||||
|
});
|
||||||
|
|
||||||
|
before(async function (): Promise<void> {
|
||||||
|
let res: any = await client.connect();
|
||||||
|
assert.strictEqual(res.isSucc, true, "Failed to connect to server, have you executed `npm run dev` already?");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Success", async function (): Promise<void> {
|
||||||
|
let ret: any = await client.callApi("Send", {
|
||||||
|
content: "Test"
|
||||||
|
});
|
||||||
|
assert.ok(ret.isSucc)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Check content is empty", async function (): Promise<void> {
|
||||||
|
let ret: any = await client.callApi("Send", {
|
||||||
|
content: ""
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(ret, {
|
||||||
|
isSucc: false,
|
||||||
|
err: new TsrpcError("Content is empty")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await client.disconnect();
|
||||||
|
});
|
||||||
|
});
|
15
test/tsconfig.json
Normal file
15
test/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": [
|
||||||
|
"es2018"
|
||||||
|
],
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2018",
|
||||||
|
"outDir": "dist",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
}
|
||||||
|
}
|
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": [
|
||||||
|
"es2018"
|
||||||
|
],
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2018",
|
||||||
|
"outDir": "dist",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
116
tslint.json
Normal file
116
tslint.json
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"defaultSeverity": "warning",
|
||||||
|
"rules": {
|
||||||
|
"ban": [
|
||||||
|
true,
|
||||||
|
[
|
||||||
|
"_",
|
||||||
|
"extend"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"_",
|
||||||
|
"isNull"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"_",
|
||||||
|
"isDefined"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"class-name": false,
|
||||||
|
"comment-format": [
|
||||||
|
true,
|
||||||
|
"check-space"
|
||||||
|
],
|
||||||
|
"curly": true,
|
||||||
|
"eofline": false,
|
||||||
|
"forin": false,
|
||||||
|
"indent": [
|
||||||
|
true,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"interface-name": [
|
||||||
|
true,
|
||||||
|
"never-prefix"
|
||||||
|
],
|
||||||
|
"jsdoc-format": true,
|
||||||
|
"label-position": true,
|
||||||
|
"label-undefined": true,
|
||||||
|
"max-line-length": [
|
||||||
|
false,
|
||||||
|
140
|
||||||
|
],
|
||||||
|
"no-arg": true,
|
||||||
|
"no-bitwise": false,
|
||||||
|
"no-console": [
|
||||||
|
true,
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"time",
|
||||||
|
"timeEnd",
|
||||||
|
"trace"
|
||||||
|
],
|
||||||
|
"no-construct": true,
|
||||||
|
"no-debugger": true,
|
||||||
|
"no-duplicate-key": true,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-empty": true,
|
||||||
|
// "no-eval": true,
|
||||||
|
"no-string-literal": false,
|
||||||
|
"no-trailing-comma": true,
|
||||||
|
"no-trailing-whitespace": true,
|
||||||
|
"no-unused-expression": false,
|
||||||
|
"no-unused-variable": true,
|
||||||
|
"no-unreachable": true,
|
||||||
|
"no-use-before-declare": false,
|
||||||
|
"one-line": [
|
||||||
|
true,
|
||||||
|
"check-open-brace",
|
||||||
|
"check-catch",
|
||||||
|
"check-else",
|
||||||
|
"check-whitespace"
|
||||||
|
],
|
||||||
|
"quotemark": [
|
||||||
|
true,
|
||||||
|
"double"
|
||||||
|
],
|
||||||
|
"radix": true,
|
||||||
|
"semicolon": [
|
||||||
|
true,
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"triple-equals": [
|
||||||
|
true,
|
||||||
|
"allow-null-check"
|
||||||
|
],
|
||||||
|
"typedef": [
|
||||||
|
true,
|
||||||
|
"call-signature",
|
||||||
|
"parameter",
|
||||||
|
"property-declaration",
|
||||||
|
"variable-declaration"
|
||||||
|
],
|
||||||
|
"typedef-whitespace": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"call-signature": "nospace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index-signature": "space"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"use-strict": [
|
||||||
|
true,
|
||||||
|
"check-module",
|
||||||
|
"check-function"
|
||||||
|
],
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": [
|
||||||
|
false,
|
||||||
|
"check-branch",
|
||||||
|
"check-decl",
|
||||||
|
"check-operator",
|
||||||
|
"check-separator",
|
||||||
|
"check-type"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
39
tsrpc.config.ts
Normal file
39
tsrpc.config.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { TsrpcConfig } from 'tsrpc-cli';
|
||||||
|
|
||||||
|
const tsrpcConf: TsrpcConfig = {
|
||||||
|
// Generate ServiceProto
|
||||||
|
proto: [
|
||||||
|
{
|
||||||
|
ptlDir: 'src/shared/protocols', // Protocol dir
|
||||||
|
output: 'src/shared/protocols/serviceProto.ts', // Path for generated ServiceProto
|
||||||
|
apiDir: 'src/api', // API dir
|
||||||
|
docDir: 'docs', // API documents dir
|
||||||
|
// ptlTemplate: CodeTemplate.getExtendedPtl(),
|
||||||
|
// msgTemplate: CodeTemplate.getExtendedMsg(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Sync shared code
|
||||||
|
sync: [
|
||||||
|
{
|
||||||
|
from: 'src/shared',
|
||||||
|
to: '../frontend/assets/scripts/shared',
|
||||||
|
type: 'symlink' // Change this to 'copy' if your environment not support symlink
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Dev server
|
||||||
|
dev: {
|
||||||
|
autoProto: true, // Auto regenerate proto
|
||||||
|
autoSync: true, // Auto sync when file changed
|
||||||
|
autoApi: true, // Auto create API when ServiceProto updated
|
||||||
|
watch: 'src', // Restart dev server when these files changed
|
||||||
|
entry: 'src/index.ts', // Dev server command: node -r ts-node/register {entry}
|
||||||
|
},
|
||||||
|
// Build config
|
||||||
|
build: {
|
||||||
|
autoProto: true, // Auto generate proto before build
|
||||||
|
autoSync: true, // Auto sync before build
|
||||||
|
autoApi: true, // Auto generate API before build
|
||||||
|
outDir: 'dist', // Clean this dir before build
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default tsrpcConf;
|
Loading…
Reference in New Issue
Block a user