diff --git a/examples/custom-http-res/.gitignore b/examples/custom-http-res/.gitignore new file mode 100644 index 0000000..d84f0da --- /dev/null +++ b/examples/custom-http-res/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_STORE \ No newline at end of file diff --git a/examples/custom-http-res/.mocharc.js b/examples/custom-http-res/.mocharc.js new file mode 100644 index 0000000..b6cf8de --- /dev/null +++ b/examples/custom-http-res/.mocharc.js @@ -0,0 +1,8 @@ +module.exports = { + require: [ + 'ts-node/register', + ], + timeout: 999999, + exit: true, + 'preserve-symlinks': true +} \ No newline at end of file diff --git a/examples/custom-http-res/.vscode/launch.json b/examples/custom-http-res/.vscode/launch.json new file mode 100644 index 0000000..9ba4218 --- /dev/null +++ b/examples/custom-http-res/.vscode/launch.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/examples/custom-http-res/.vscode/settings.json b/examples/custom-http-res/.vscode/settings.json new file mode 100644 index 0000000..00ad71f --- /dev/null +++ b/examples/custom-http-res/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules\\typescript\\lib" +} \ No newline at end of file diff --git a/examples/custom-http-res/README.md b/examples/custom-http-res/README.md new file mode 100644 index 0000000..2f34866 --- /dev/null +++ b/examples/custom-http-res/README.md @@ -0,0 +1,39 @@ +# TSRPC Server + +## Run +### Local Dev Server +``` +npm run dev +``` + +### Unit Test +Execute `npm run dev` first, then execute: +``` +npm run test +``` + +### Build +``` +npm run build +``` + +--- + +## Files +### Generate ServiceProto +``` +npm run proto +``` + +### Generate API templates +``` +npm run api +``` + +### Sync shared code to client + +``` +npm run sync +``` + +> If you chose symlink when using `create-tsrpc-app`, it would re-create the symlink instead of copy files. diff --git a/examples/custom-http-res/package.json b/examples/custom-http-res/package.json new file mode 100644 index 0000000..1669f31 --- /dev/null +++ b/examples/custom-http-res/package.json @@ -0,0 +1,26 @@ +{ + "name": "custom-http-res-.", + "version": "0.1.0", + "main": "index.js", + "private": true, + "scripts": { + "proto": "tsrpc proto -i src/shared/protocols -o src/shared/protocols/serviceProto.ts", + "sync": "tsrpc sync --from src/shared --to ../frontend/src/shared", + "api": "tsrpc api -i src/shared/protocols/serviceProto.ts -o src/api", + "dev": "onchange \"src/**/*.ts\" -i -k -- ts-node \"src/index.ts\"", + "test": "mocha test/**/*.test.ts", + "build": "tsrpc build" + }, + "devDependencies": { + "@types/mocha": "^8.2.2", + "@types/node": "^15.12.5", + "mocha": "^9.0.1", + "onchange": "^7.1.0", + "ts-node": "^10.0.0", + "tsrpc-cli": "^2.0.3", + "typescript": "^4.3.4" + }, + "dependencies": { + "tsrpc": "^3.0.4" + } +} diff --git a/examples/custom-http-res/src/api/ApiAddData.ts b/examples/custom-http-res/src/api/ApiAddData.ts new file mode 100644 index 0000000..c064789 --- /dev/null +++ b/examples/custom-http-res/src/api/ApiAddData.ts @@ -0,0 +1,26 @@ +import { ApiCall } from "tsrpc"; +import { ReqAddData, ResAddData } from "../shared/protocols/PtlAddData"; +import { AllData } from "./ApiGetData"; + +// This is a demo code file +// Feel free to delete it + +export async function ApiAddData(call: ApiCall) { + // Error + if (call.req.content === '') { + call.error('Content is empty'); + return; + } + + let time = new Date(); + AllData.unshift({ + content: call.req.content, + time: time + }) + console.log('AllData', AllData) + + // Success + call.succ({ + time: time + }); +} \ No newline at end of file diff --git a/examples/custom-http-res/src/api/ApiGetData.ts b/examples/custom-http-res/src/api/ApiGetData.ts new file mode 100644 index 0000000..2996ee4 --- /dev/null +++ b/examples/custom-http-res/src/api/ApiGetData.ts @@ -0,0 +1,13 @@ +import { ApiCall } from "tsrpc"; +import { ReqGetData, ResGetData } from "../shared/protocols/PtlGetData"; + +// This is a demo code file +// Feel free to delete it + +export async function ApiGetData(call: ApiCall) { + call.succ({ + data: AllData + }) +} + +export const AllData: { content: string, time: Date }[] = []; \ No newline at end of file diff --git a/examples/custom-http-res/src/index.ts b/examples/custom-http-res/src/index.ts new file mode 100644 index 0000000..8fed94d --- /dev/null +++ b/examples/custom-http-res/src/index.ts @@ -0,0 +1,45 @@ +import * as path from "path"; +import { HttpConnection, HttpServer } from "tsrpc"; +import { serviceProto } from "./shared/protocols/serviceProto"; + +// Create the Server +const server = new HttpServer(serviceProto, { + port: 3000, + cors: '*' +}); + +// Custom HTTP Reponse +server.flows.preRecvBufferFlow.push(v => { + let conn = v.conn as HttpConnection; + + if (conn.httpReq.method === 'GET') { + // 特定 URL: /test + if (conn.httpReq.url === '/test') { + conn.httpRes.end('test test') + return undefined; + } + + // 默认 GET 响应 + conn.httpRes.end('Hello World'); + return undefined; + } + + return v; +}) + +// Entry function +async function main() { + // Auto implement APIs + await server.autoImplementApi(path.resolve(__dirname, 'api')); + + // TODO + // Prepare something... (e.g. connect the db) + + await server.start(); +}; + +main().catch(e => { + // Exit if any error during the startup + server.logger.error(e); + process.exit(-1); +}); \ No newline at end of file diff --git a/examples/custom-http-res/src/shared/protocols/PtlAddData.ts b/examples/custom-http-res/src/shared/protocols/PtlAddData.ts new file mode 100644 index 0000000..7f442ae --- /dev/null +++ b/examples/custom-http-res/src/shared/protocols/PtlAddData.ts @@ -0,0 +1,10 @@ +// This is a demo code file +// Feel free to delete it + +export interface ReqAddData { + content: string; +} + +export interface ResAddData { + time: Date +} \ No newline at end of file diff --git a/examples/custom-http-res/src/shared/protocols/PtlGetData.ts b/examples/custom-http-res/src/shared/protocols/PtlGetData.ts new file mode 100644 index 0000000..8ac35a7 --- /dev/null +++ b/examples/custom-http-res/src/shared/protocols/PtlGetData.ts @@ -0,0 +1,13 @@ +// This is a demo code file +// Feel free to delete it + +export interface ReqGetData { + +} + +export interface ResGetData { + data: { + content: string, + time: Date + }[] +} \ No newline at end of file diff --git a/examples/custom-http-res/src/shared/protocols/serviceProto.ts b/examples/custom-http-res/src/shared/protocols/serviceProto.ts new file mode 100644 index 0000000..e7207a9 --- /dev/null +++ b/examples/custom-http-res/src/shared/protocols/serviceProto.ts @@ -0,0 +1,98 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqAddData, ResAddData } from './PtlAddData'; +import { ReqGetData, ResGetData } from './PtlGetData'; + +// This is a demo service proto file (auto generated) +// Feel free to delete it + +export interface ServiceType { + api: { + "AddData": { + req: ReqAddData, + res: ResAddData + }, + "GetData": { + req: ReqGetData, + res: ResGetData + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "version": 1, + "services": [ + { + "id": 0, + "name": "AddData", + "type": "api" + }, + { + "id": 1, + "name": "GetData", + "type": "api" + } + ], + "types": { + "PtlAddData/ReqAddData": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "content", + "type": { + "type": "String" + } + } + ] + }, + "PtlAddData/ResAddData": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "time", + "type": { + "type": "Date" + } + } + ] + }, + "PtlGetData/ReqGetData": { + "type": "Interface" + }, + "PtlGetData/ResGetData": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "data", + "type": { + "type": "Array", + "elementType": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "content", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "time", + "type": { + "type": "Date" + } + } + ] + } + } + } + ] + } + } +}; \ No newline at end of file diff --git a/examples/custom-http-res/test/api/ApiAddData.test.ts b/examples/custom-http-res/test/api/ApiAddData.test.ts new file mode 100644 index 0000000..d9a0bbe --- /dev/null +++ b/examples/custom-http-res/test/api/ApiAddData.test.ts @@ -0,0 +1,30 @@ +import assert from 'assert'; +import { HttpClient, TsrpcError } from 'tsrpc'; +import { serviceProto } 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('ApiAddData', function () { + let client = new HttpClient(serviceProto, { + server: 'http://127.0.0.1:3000', + logger: console + }); + + it('Success', async function () { + let ret = await client.callApi('AddData', { + content: 'TEST' + }); + assert.strictEqual(ret.isSucc, true); + }); + + it('Error', async function () { + let ret = await client.callApi('AddData', { + content: '' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Content is empty') + }); + }) +}) \ No newline at end of file diff --git a/examples/custom-http-res/test/api/ApiGetData.test.ts b/examples/custom-http-res/test/api/ApiGetData.test.ts new file mode 100644 index 0000000..b63e13e --- /dev/null +++ b/examples/custom-http-res/test/api/ApiGetData.test.ts @@ -0,0 +1,26 @@ +import assert from 'assert'; +import { HttpClient } from 'tsrpc'; +import { serviceProto } 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('ApiGetData', function () { + let client = new HttpClient(serviceProto, { + server: 'http://127.0.0.1:3000', + logger: console + }); + + it('Add & Get', async function () { + let ret1 = await client.callApi('GetData', {}); + assert.strictEqual(ret1.isSucc, true); + + let ret2 = await client.callApi('AddData', { content: 'AABBCC' }); + assert.strictEqual(ret2.isSucc, true); + + let ret3 = await client.callApi('GetData', {}); + assert.strictEqual(ret3.isSucc, true); + assert.strictEqual(ret3.res!.data.length, ret1.res!.data.length + 1); + assert.strictEqual(ret3.res!.data[0].content, 'AABBCC'); + }); +}) \ No newline at end of file diff --git a/examples/custom-http-res/test/tsconfig.json b/examples/custom-http-res/test/tsconfig.json new file mode 100644 index 0000000..9fc6bcd --- /dev/null +++ b/examples/custom-http-res/test/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "lib": [ + "es2018" + ], + "module": "commonjs", + "target": "es2018", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node" + } +} \ No newline at end of file diff --git a/examples/custom-http-res/tsconfig.json b/examples/custom-http-res/tsconfig.json new file mode 100644 index 0000000..d18498f --- /dev/null +++ b/examples/custom-http-res/tsconfig.json @@ -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" + ] +} \ No newline at end of file