diff --git a/.gitignore b/.gitignore index b3bd4f7..16dfecb 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,6 @@ dist # TernJS port file .tern-port -.DS_Store \ No newline at end of file +.DS_Store + +package-lock.json \ No newline at end of file diff --git a/first-api/backend/.vscode/launch.json b/first-api/backend/.vscode/launch.json new file mode 100644 index 0000000..9ba4218 --- /dev/null +++ b/first-api/backend/.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/first-api/backend/.vscode/settings.json b/first-api/backend/.vscode/settings.json new file mode 100644 index 0000000..00ad71f --- /dev/null +++ b/first-api/backend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules\\typescript\\lib" +} \ No newline at end of file diff --git a/first-api/backend/README.md b/first-api/backend/README.md new file mode 100644 index 0000000..7492c22 --- /dev/null +++ b/first-api/backend/README.md @@ -0,0 +1,35 @@ +# TSRPC Server + +## Run +### Local Dev Server +``` +npm run dev +``` + + + +### 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/first-api/backend/package.json b/first-api/backend/package.json new file mode 100644 index 0000000..7119eae --- /dev/null +++ b/first-api/backend/package.json @@ -0,0 +1,23 @@ +{ + "name": "first-api-backend", + "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\"", + "build": "tsrpc build" + }, + "devDependencies": { + "@types/node": "^15.12.2", + "onchange": "^7.1.0", + "ts-node": "^9.1.1", + "tsrpc-cli": "^2.0.1-dev.11", + "typescript": "^4.3.2" + }, + "dependencies": { + "tsrpc": "^3.0.0-dev.17" + } +} diff --git a/first-api/backend/src/api/ApiHello.ts b/first-api/backend/src/api/ApiHello.ts new file mode 100644 index 0000000..ea25e6d --- /dev/null +++ b/first-api/backend/src/api/ApiHello.ts @@ -0,0 +1,14 @@ +import { ApiCall } from "tsrpc"; +import { ReqHello, ResHello } from "../shared/protocols/PtlHello"; + +export async function ApiHello(call: ApiCall) { + if (call.req.name === 'World') { + call.succ({ + reply: 'Hello, ' + call.req.name, + time: new Date() + }); + } + else { + call.error('Invalid name'); + } +} \ No newline at end of file diff --git a/first-api/backend/src/index.ts b/first-api/backend/src/index.ts new file mode 100644 index 0000000..7dcc249 --- /dev/null +++ b/first-api/backend/src/index.ts @@ -0,0 +1,26 @@ +import * as path from "path"; +import { HttpServer } from "tsrpc"; +import { serviceProto } from "./shared/protocols/serviceProto"; + +// Create the Server +const server = new HttpServer(serviceProto, { + port: 3000, + cors: '*' +}); + +// 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/first-api/backend/src/shared/protocols/PtlHello.ts b/first-api/backend/src/shared/protocols/PtlHello.ts new file mode 100644 index 0000000..8a46045 --- /dev/null +++ b/first-api/backend/src/shared/protocols/PtlHello.ts @@ -0,0 +1,8 @@ +export interface ReqHello { + name: string +} + +export interface ResHello { + reply: string, + time: Date +} diff --git a/first-api/backend/src/shared/protocols/serviceProto.ts b/first-api/backend/src/shared/protocols/serviceProto.ts new file mode 100644 index 0000000..7f9e724 --- /dev/null +++ b/first-api/backend/src/shared/protocols/serviceProto.ts @@ -0,0 +1,57 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqHello, ResHello } from './PtlHello'; + +export interface ServiceType { + api: { + "Hello": { + req: ReqHello, + res: ResHello + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "services": [ + { + "id": 0, + "name": "Hello", + "type": "api" + } + ], + "types": { + "PtlHello/ReqHello": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "name", + "type": { + "type": "String" + } + } + ] + }, + "PtlHello/ResHello": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "reply", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "time", + "type": { + "type": "Date" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/first-api/backend/tsconfig.json b/first-api/backend/tsconfig.json new file mode 100644 index 0000000..d18498f --- /dev/null +++ b/first-api/backend/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 diff --git a/first-api/frontend/package.json b/first-api/frontend/package.json new file mode 100644 index 0000000..2319a82 --- /dev/null +++ b/first-api/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "first-api-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "webpack serve --mode=development --open", + "build": "webpack --mode=production" + }, + "devDependencies": { + "copy-webpack-plugin": "^9.0.0", + "html-webpack-plugin": "^5.3.1", + "ts-loader": "^9.2.3", + "typescript": "^4.3.2", + "webpack": "^5.38.1", + "webpack-cli": "^4.7.2", + "webpack-dev-server": "^3.11.2" + }, + "dependencies": { + "tsrpc-browser": "^3.0.0-dev.15" + } +} diff --git a/first-api/frontend/public/index.html b/first-api/frontend/public/index.html new file mode 100644 index 0000000..0584294 --- /dev/null +++ b/first-api/frontend/public/index.html @@ -0,0 +1,16 @@ + + + + + + + + TSRPC Example + + + +

The First API

+

Open "Devtool" to see client logs.

+ + + \ No newline at end of file diff --git a/first-api/frontend/src/index.ts b/first-api/frontend/src/index.ts new file mode 100644 index 0000000..b047c69 --- /dev/null +++ b/first-api/frontend/src/index.ts @@ -0,0 +1,25 @@ +import { HttpClient } from 'tsrpc-browser'; +import { serviceProto } from './shared/protocols/serviceProto'; + +// 创建一个 TSRPC Client +let client = new HttpClient(serviceProto, { + server: 'http://127.0.0.1:3000', + logger: console +}); + +async function test() { + let ret = await client.callApi('Hello', { + name: 'World' + }); + + // Error + if (!ret.isSucc) { + alert('Error: ' + ret.err.message); + return; + } + + // Success + alert('Success: ' + ret.res.reply); +} + +window.onload = test; \ No newline at end of file diff --git a/first-api/frontend/src/shared/protocols/PtlHello.ts b/first-api/frontend/src/shared/protocols/PtlHello.ts new file mode 100644 index 0000000..8a46045 --- /dev/null +++ b/first-api/frontend/src/shared/protocols/PtlHello.ts @@ -0,0 +1,8 @@ +export interface ReqHello { + name: string +} + +export interface ResHello { + reply: string, + time: Date +} diff --git a/first-api/frontend/src/shared/protocols/serviceProto.ts b/first-api/frontend/src/shared/protocols/serviceProto.ts new file mode 100644 index 0000000..7f9e724 --- /dev/null +++ b/first-api/frontend/src/shared/protocols/serviceProto.ts @@ -0,0 +1,57 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqHello, ResHello } from './PtlHello'; + +export interface ServiceType { + api: { + "Hello": { + req: ReqHello, + res: ResHello + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "services": [ + { + "id": 0, + "name": "Hello", + "type": "api" + } + ], + "types": { + "PtlHello/ReqHello": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "name", + "type": { + "type": "String" + } + } + ] + }, + "PtlHello/ResHello": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "reply", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "time", + "type": { + "type": "Date" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/first-api/frontend/tsconfig.json b/first-api/frontend/tsconfig.json new file mode 100644 index 0000000..7517bf5 --- /dev/null +++ b/first-api/frontend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "es2018" + ], + "module": "esnext", + "target": "es2018", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "outDir": "dist", + "skipLibCheck": true, + "strict": true, + "jsx": "react-jsx", + "sourceMap": true, + "isolatedModules": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/first-api/frontend/webpack.config.js b/first-api/frontend/webpack.config.js new file mode 100644 index 0000000..062d1a4 --- /dev/null +++ b/first-api/frontend/webpack.config.js @@ -0,0 +1,58 @@ +const webpack = require('webpack'); +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +const isProduction = process.argv.indexOf('--mode=production') > -1; + +module.exports = { + entry: './src/index.ts', + output: { + filename: 'bundle.[contenthash].js', + path: path.resolve(__dirname, 'dist'), + clean: true + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'] + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: [{ + loader: 'ts-loader', + options: { + compilerOptions: isProduction ? { + "lib": [ + "dom", + "es2015.promise" + ], + "target": "es5", + } : undefined + } + }], + exclude: /node_modules/ + }, + ] + }, + plugins: [ + // Copy "public" to "dist" + new CopyWebpackPlugin({ + patterns: [{ + from: 'public', + to: '.', + toType: 'dir', + globOptions: { + gitignore: true, + ignore: [path.resolve(__dirname, 'public/index.html').replace(/\\/g, '/')] + }, + noErrorOnMissing: true + }] + }), + // Auto add