This commit is contained in:
2022-05-04 11:13:09 +08:00
parent 3d331ee10d
commit 0b76b5935d
68 changed files with 38788 additions and 1 deletions

2
backend1/.env Normal file
View File

@@ -0,0 +1,2 @@
PORT = 4000
TZ = Asia/Taipei

4
backend1/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules
dist
.DS_STORE
*.meta

11
backend1/.mocharc.js Normal file
View File

@@ -0,0 +1,11 @@
module.exports = {
require: [
'ts-node/register',
],
timeout: 999999,
exit: true,
spec: [
'./test/**/*.test.ts'
],
'preserve-symlinks': true
}

30
backend1/.vscode/launch.json vendored Normal file
View 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
backend1/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib"
}

30
backend1/Dockerfile Normal file
View 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
backend1/README.md Normal file
View 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
```

3686
backend1/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
backend1/package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"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": {
"dotenv": "^16.0.0",
"tsrpc": "^3.1.3"
}
}

View 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>): Promise<void> {
let playerId: number = roomInstance.join(call.req, call.conn);
call.succ({
playerId: playerId,
gameState: roomInstance.gameSystem.state
});
}

40
backend1/src/index.ts Normal file
View File

@@ -0,0 +1,40 @@
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";
require("dotenv").config();
// 创建 TSRPC WebSocket Server
export const server: WsServer<ServiceType> = new WsServer(serviceProto, {
port: +process.env.PORT! || 3000,
json: true
});
// 断开连接后退出房间
server.flows.postDisconnectFlow.push(v => {
let conn: WsConnection<ServiceType> = v.conn as WsConnection<ServiceType>;
if (conn.playerId) {
roomInstance.leave(conn.playerId, conn);
}
return v;
});
export const roomInstance: Room = new Room(server);
// 初始化
async function init(): Promise<void> {
// 挂载 API 接口
await server.autoImplementApi(path.resolve(__dirname, "api"));
// TODO
// Prepare something... (e.g. connect the db)
}
// 启动入口点
async function main(): Promise<void> {
await init();
await server.start();
}
main();

101
backend1/src/models/Room.ts Normal file
View 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>): number {
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): void {
this.pendingInputs.push(input);
}
sync(): void {
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>): void {
this.conns.removeOne(v => v.playerId === playerId);
this.applyInput({
type: "PlayerLeave",
playerId: playerId
});
}
}
declare module "tsrpc" {
export interface WsConnection {
playerId?: number;
}
}

View 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;

View File

@@ -0,0 +1,14 @@
export const gameConfig = {
syncRate: 10,
moveSpeed: 10,
// 箭矢飞行时间(毫秒)
arrowFlyTime: 500,
// 箭矢投掷距离
arrowDistance: 8,
// 箭矢落地命中判定半径
arrowAttackRadius: 2,
// 被箭矢几种后的晕眩时间(毫秒)
arrowDizzyTime: 1000
};

View File

@@ -0,0 +1,9 @@
export type ArrowState = {
id: number,
// 谁发出的箭
fromPlayerId: number,
// 落地时间(游戏时间)
targetTime: number,
// 落点位置(游戏位置)
targetPos: { x: number, y: number }
}

View File

@@ -0,0 +1,7 @@
export interface PlayerState {
id: number,
// 位置
pos: { x: number, y: number },
// 晕眩结束时间
dizzyEndTime?: number,
}

View File

@@ -0,0 +1,15 @@
import { GameSystemState } from "../game/GameSystem";
/** 加入房间 */
export interface ReqJoin {
}
export interface ResJoin {
/** 加入房间后,自己的 ID */
playerId: number;
/** 状态同步:一次性同步当前状态 */
gameState: GameSystemState;
}
// export const conf = {}

View 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'>;

View File

@@ -0,0 +1,11 @@
import { GameSystemInput } from "../../game/GameSystem";
/**
* 服务端定期广播的同步帧
* 包含了这一段期间所有输入
*/
export interface MsgFrame {
inputs: GameSystemInput[],
/** 当前用户提交的,经服务端确认的最后一条输入的 SN */
lastSn?: number
}

View 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"
}
}
]
}
}
};

View 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("Join", {
content: "Test"
});
assert.ok(ret.isSucc);
});
it("Check content is empty", async function (): Promise<void> {
let ret: any = await client.callApi("Join", {
content: ""
});
assert.deepStrictEqual(ret, {
isSucc: false,
err: new TsrpcError("Content is empty")
});
});
after(async function (): Promise<void> {
await client.disconnect();
});
});

View 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
backend1/tsconfig.json Normal file
View 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
backend1/tslint.json Normal file
View 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
backend1/tsrpc.config.ts Normal file
View 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;