diff --git a/examples/mongodb-crud/backend/.gitignore b/examples/mongodb-crud/backend/.gitignore new file mode 100644 index 0000000..d84f0da --- /dev/null +++ b/examples/mongodb-crud/backend/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_STORE \ No newline at end of file diff --git a/examples/mongodb-crud/backend/.mocharc.js b/examples/mongodb-crud/backend/.mocharc.js new file mode 100644 index 0000000..b6cf8de --- /dev/null +++ b/examples/mongodb-crud/backend/.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/mongodb-crud/backend/.vscode/launch.json b/examples/mongodb-crud/backend/.vscode/launch.json new file mode 100644 index 0000000..9ba4218 --- /dev/null +++ b/examples/mongodb-crud/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/examples/mongodb-crud/backend/.vscode/settings.json b/examples/mongodb-crud/backend/.vscode/settings.json new file mode 100644 index 0000000..00ad71f --- /dev/null +++ b/examples/mongodb-crud/backend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules\\typescript\\lib" +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/README.md b/examples/mongodb-crud/backend/README.md new file mode 100644 index 0000000..2f34866 --- /dev/null +++ b/examples/mongodb-crud/backend/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/mongodb-crud/backend/package.json b/examples/mongodb-crud/backend/package.json new file mode 100644 index 0000000..4f2fa9b --- /dev/null +++ b/examples/mongodb-crud/backend/package.json @@ -0,0 +1,30 @@ +{ + "name": "code-backend", + "version": "0.1.0", + "main": "index.js", + "private": true, + "scripts": { + "watch:proto": "onchange \"src/shared/protocols/**/Ptl*.ts\" -- npm run protoAndSync", + "protoAndSync": "npm run proto && npm run sync", + "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/mongodb": "^3.6.18", + "@types/node": "^15.12.4", + "mocha": "^9.0.1", + "onchange": "^7.1.0", + "ts-node": "^10.0.0", + "tsrpc-cli": "^2.0.3", + "typescript": "^4.3.4" + }, + "dependencies": { + "mongodb": "^3.6.9", + "tsrpc": "^3.0.2" + } +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/api/ApiAddPost.ts b/examples/mongodb-crud/backend/src/api/ApiAddPost.ts new file mode 100644 index 0000000..924a185 --- /dev/null +++ b/examples/mongodb-crud/backend/src/api/ApiAddPost.ts @@ -0,0 +1,26 @@ +import { ApiCall } from "tsrpc"; +import { Global } from "../models/Global"; +import { ReqAddPost, ResAddPost } from "../shared/protocols/PtlAddPost"; + +export async function ApiAddPost(call: ApiCall) { + console.log('cccgasdgasd', call.service.conf) + if (call.service.conf) { + // if (没登录) { + // call.error('你还没登录'); + // return; + // } + } + + let op = await Global.collection('Post').insertOne({ + ...call.req.newPost, + create: { + uid: 'xxx', + time: new Date() + }, + visitedNum: 0 + }); + + call.succ({ + insertedId: op.insertedId.toHexString() + }) +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/api/ApiDelPost.ts b/examples/mongodb-crud/backend/src/api/ApiDelPost.ts new file mode 100644 index 0000000..90cbbd5 --- /dev/null +++ b/examples/mongodb-crud/backend/src/api/ApiDelPost.ts @@ -0,0 +1,12 @@ +import { ObjectID } from 'mongodb'; +import { ApiCall } from "tsrpc"; +import { Global } from "../models/Global"; +import { ReqDelPost, ResDelPost } from "../shared/protocols/PtlDelPost"; + +export async function ApiDelPost(call: ApiCall) { + let op = await Global.collection('Post').deleteOne({ + _id: new ObjectID(call.req._id) + }) + + call.succ({}); +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/api/ApiGetPost.ts b/examples/mongodb-crud/backend/src/api/ApiGetPost.ts new file mode 100644 index 0000000..1bf035e --- /dev/null +++ b/examples/mongodb-crud/backend/src/api/ApiGetPost.ts @@ -0,0 +1,22 @@ +import { ObjectID } from 'mongodb'; +import { ApiCall } from "tsrpc"; +import { Global } from "../models/Global"; +import { ReqGetPost, ResGetPost } from "../shared/protocols/PtlGetPost"; + +export async function ApiGetPost(call: ApiCall) { + let op = await Global.collection('Post').findOne({ + _id: new ObjectID(call.req._id) + }); + + if (!op) { + call.error('Post 不存在'); + return; + } + + call.succ({ + post: { + ...op, + _id: op._id.toHexString() + } + }) +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/api/ApiUpdatePost.ts b/examples/mongodb-crud/backend/src/api/ApiUpdatePost.ts new file mode 100644 index 0000000..24c8605 --- /dev/null +++ b/examples/mongodb-crud/backend/src/api/ApiUpdatePost.ts @@ -0,0 +1,22 @@ +import { ObjectID } from 'mongodb'; +import { ApiCall } from "tsrpc"; +import { Global } from "../models/Global"; +import { ReqUpdatePost, ResUpdatePost } from "../shared/protocols/PtlUpdatePost"; + +export async function ApiUpdatePost(call: ApiCall) { + let { _id, ...update } = call.req.update; + + let op = await Global.collection('Post').updateOne({ + _id: new ObjectID(_id) + }, { + $set: update + }); + + console.log('ssss', { + matchedCount: op.matchedCount + }) + + call.succ({ + matchedCount: op.matchedCount + }) +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/index.ts b/examples/mongodb-crud/backend/src/index.ts new file mode 100644 index 0000000..497bea6 --- /dev/null +++ b/examples/mongodb-crud/backend/src/index.ts @@ -0,0 +1,26 @@ +import * as path from "path"; +import { HttpServer } from "tsrpc"; +import { Global } from "./models/Global"; +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')); + + await Global.init(server.logger); + + 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/mongodb-crud/backend/src/models/BackConfig.ts b/examples/mongodb-crud/backend/src/models/BackConfig.ts new file mode 100644 index 0000000..4ff60d0 --- /dev/null +++ b/examples/mongodb-crud/backend/src/models/BackConfig.ts @@ -0,0 +1,4 @@ +export const BackConfig = { + // Please replace by your db + mongoDb: 'mongodb://username:password@xxx.com:27017/test?authSource=admin', +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/models/Global.ts b/examples/mongodb-crud/backend/src/models/Global.ts new file mode 100644 index 0000000..7e31c83 --- /dev/null +++ b/examples/mongodb-crud/backend/src/models/Global.ts @@ -0,0 +1,40 @@ +import { Collection, Db, MongoClient } from "mongodb"; +import { Logger } from "tsrpc"; +import { BackConfig } from "./BackConfig"; +import { DbPost } from "./dbItems/DbPost"; + +export class Global { + + static db: Db; + + static async init(logger?: Logger) { + this.db = await this._getMongoDb(BackConfig.mongoDb, logger); + } + + private static _getMongoDb(uri: string, logger?: Logger): Promise { + logger?.log(`Start connecting db...(${uri})`) + let promise = new Promise((rs, rj) => { + MongoClient.connect(uri, { + useUnifiedTopology: true, + }, (err, client) => { + if (err) { + logger?.error('× Failed connected db.', err) + rj(err); + } else { + logger?.log(`√ Connect db succ. (${uri})`) + rs(client.db()); + } + }) + }) + return promise; + } + + static collection(col: T): Collection { + return this.db.collection(col); + } + +} + +export interface DbCollectionType { + Post: DbPost +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/models/dbItems/DbPost.ts b/examples/mongodb-crud/backend/src/models/dbItems/DbPost.ts new file mode 100644 index 0000000..7d535e5 --- /dev/null +++ b/examples/mongodb-crud/backend/src/models/dbItems/DbPost.ts @@ -0,0 +1,7 @@ +import { ObjectID } from "bson"; +import { Overwrite } from "tsrpc"; +import { Post } from "../../shared/protocols/models/Post"; + +export type DbPost = Overwrite \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/shared/protocols/PtlAddPost.ts b/examples/mongodb-crud/backend/src/shared/protocols/PtlAddPost.ts new file mode 100644 index 0000000..c4f367e --- /dev/null +++ b/examples/mongodb-crud/backend/src/shared/protocols/PtlAddPost.ts @@ -0,0 +1,9 @@ +import { Post } from "./models/Post"; + +export interface ReqAddPost { + newPost: Omit; +} + +export interface ResAddPost { + insertedId: string; +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/shared/protocols/PtlDelPost.ts b/examples/mongodb-crud/backend/src/shared/protocols/PtlDelPost.ts new file mode 100644 index 0000000..026fec7 --- /dev/null +++ b/examples/mongodb-crud/backend/src/shared/protocols/PtlDelPost.ts @@ -0,0 +1,7 @@ +export interface ReqDelPost { + _id: string; +} + +export interface ResDelPost { + +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/shared/protocols/PtlGetPost.ts b/examples/mongodb-crud/backend/src/shared/protocols/PtlGetPost.ts new file mode 100644 index 0000000..a6d169f --- /dev/null +++ b/examples/mongodb-crud/backend/src/shared/protocols/PtlGetPost.ts @@ -0,0 +1,9 @@ +import { Post } from "./models/Post"; + +export interface ReqGetPost { + _id: string; +} + +export interface ResGetPost { + post: Post; +} diff --git a/examples/mongodb-crud/backend/src/shared/protocols/PtlUpdatePost.ts b/examples/mongodb-crud/backend/src/shared/protocols/PtlUpdatePost.ts new file mode 100644 index 0000000..20ccc71 --- /dev/null +++ b/examples/mongodb-crud/backend/src/shared/protocols/PtlUpdatePost.ts @@ -0,0 +1,9 @@ +import { Post } from "./models/Post"; + +export interface ReqUpdatePost { + update: { _id: string } & Partial>; +} + +export interface ResUpdatePost { + matchedCount: number; +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/shared/protocols/models/Post.ts b/examples/mongodb-crud/backend/src/shared/protocols/models/Post.ts new file mode 100644 index 0000000..67b21e2 --- /dev/null +++ b/examples/mongodb-crud/backend/src/shared/protocols/models/Post.ts @@ -0,0 +1,17 @@ +export interface Post { + _id: string; + author: string; + title: string; + content: string; + visitedNum: number; + + create: { + uid: string; + time: Date; + } + + update?: { + uid: string, + time: Date + } +} \ No newline at end of file diff --git a/examples/mongodb-crud/backend/src/shared/protocols/serviceProto.ts b/examples/mongodb-crud/backend/src/shared/protocols/serviceProto.ts new file mode 100644 index 0000000..6b3a764 --- /dev/null +++ b/examples/mongodb-crud/backend/src/shared/protocols/serviceProto.ts @@ -0,0 +1,276 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqAddPost, ResAddPost } from './PtlAddPost'; +import { ReqDelPost, ResDelPost } from './PtlDelPost'; +import { ReqGetPost, ResGetPost } from './PtlGetPost'; +import { ReqUpdatePost, ResUpdatePost } from './PtlUpdatePost'; + +export interface ServiceType { + api: { + "AddPost": { + req: ReqAddPost, + res: ResAddPost + }, + "DelPost": { + req: ReqDelPost, + res: ResDelPost + }, + "GetPost": { + req: ReqGetPost, + res: ResGetPost + }, + "UpdatePost": { + req: ReqUpdatePost, + res: ResUpdatePost + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "version": 16, + "services": [ + { + "id": 0, + "name": "AddPost", + "type": "api" + }, + { + "id": 1, + "name": "DelPost", + "type": "api" + }, + { + "id": 2, + "name": "GetPost", + "type": "api" + }, + { + "id": 3, + "name": "UpdatePost", + "type": "api" + } + ], + "types": { + "PtlAddPost/ReqAddPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "newPost", + "type": { + "target": { + "type": "Reference", + "target": "models/Post/Post" + }, + "keys": [ + "_id", + "create", + "update", + "visitedNum" + ], + "type": "Omit" + } + } + ] + }, + "models/Post/Post": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "_id", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "author", + "type": { + "type": "String" + } + }, + { + "id": 2, + "name": "title", + "type": { + "type": "String" + } + }, + { + "id": 3, + "name": "content", + "type": { + "type": "String" + } + }, + { + "id": 4, + "name": "visitedNum", + "type": { + "type": "Number" + } + }, + { + "id": 5, + "name": "create", + "type": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "uid", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "time", + "type": { + "type": "Date" + } + } + ] + } + }, + { + "id": 6, + "name": "update", + "type": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "uid", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "time", + "type": { + "type": "Date" + } + } + ] + }, + "optional": true + } + ] + }, + "PtlAddPost/ResAddPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "insertedId", + "type": { + "type": "String" + } + } + ] + }, + "PtlDelPost/ReqDelPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "_id", + "type": { + "type": "String" + } + } + ] + }, + "PtlDelPost/ResDelPost": { + "type": "Interface" + }, + "PtlGetPost/ReqGetPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "_id", + "type": { + "type": "String" + } + } + ] + }, + "PtlGetPost/ResGetPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "post", + "type": { + "type": "Reference", + "target": "models/Post/Post" + } + } + ] + }, + "PtlUpdatePost/ReqUpdatePost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "update", + "type": { + "type": "Intersection", + "members": [ + { + "id": 1, + "type": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "_id", + "type": { + "type": "String" + } + } + ] + } + }, + { + "id": 3, + "type": { + "type": "Partial", + "target": { + "target": { + "type": "Reference", + "target": "models/Post/Post" + }, + "keys": [ + "title", + "content" + ], + "type": "Pick" + } + } + } + ] + } + } + ] + }, + "PtlUpdatePost/ResUpdatePost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "matchedCount", + "type": { + "type": "Number" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/examples/mongodb-crud/backend/test/api/post.test.ts b/examples/mongodb-crud/backend/test/api/post.test.ts new file mode 100644 index 0000000..f443a39 --- /dev/null +++ b/examples/mongodb-crud/backend/test/api/post.test.ts @@ -0,0 +1,40 @@ +import { HttpClient } from 'tsrpc'; +import { Post } from '../../src/shared/protocols/models/Post'; +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('ApiGetSalary', function () { + let client = new HttpClient(serviceProto, { + server: 'http://127.0.0.1:3000', + logger: console + }); + + let post!: Post; + it('Success', async function () { + let retAdd = await client.callApi('AddPost', { + newPost: { + author: 'k8w', + title: 'TSPRC', + content: '太好用了' + } + }); + + let retGet = await client.callApi('GetPost', { + _id: retAdd.res!.insertedId + }); + + post = retGet.res!.post; + post.title = 'TSRPC 123'; + post.content = 'xxxxxxxx'; + + let retUpdate = await client.callApi('UpdatePost', { + update: post + }); + + let retDel = await client.callApi('DelPost', { + _id: post._id + }) + }); +}) \ No newline at end of file diff --git a/examples/mongodb-crud/backend/test/tsconfig.json b/examples/mongodb-crud/backend/test/tsconfig.json new file mode 100644 index 0000000..194d0d1 --- /dev/null +++ b/examples/mongodb-crud/backend/test/tsconfig.json @@ -0,0 +1,72 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/examples/mongodb-crud/backend/tsconfig.json b/examples/mongodb-crud/backend/tsconfig.json new file mode 100644 index 0000000..d18498f --- /dev/null +++ b/examples/mongodb-crud/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/examples/mongodb-crud/frontend/.gitignore b/examples/mongodb-crud/frontend/.gitignore new file mode 100644 index 0000000..ae16b5a --- /dev/null +++ b/examples/mongodb-crud/frontend/.gitignore @@ -0,0 +1,3 @@ +.DS_STORE +node_modules +dist \ No newline at end of file diff --git a/examples/mongodb-crud/frontend/package.json b/examples/mongodb-crud/frontend/package.json new file mode 100644 index 0000000..410cf45 --- /dev/null +++ b/examples/mongodb-crud/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "code-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "webpack serve --mode=development --open", + "build": "webpack --mode=production" + }, + "devDependencies": { + "copy-webpack-plugin": "^9.0.1", + "html-webpack-plugin": "^5.3.2", + "ts-loader": "^9.2.3", + "typescript": "^4.3.4", + "webpack": "^5.40.0", + "webpack-cli": "^4.7.2", + "webpack-dev-server": "^3.11.2" + }, + "dependencies": { + "tsrpc-browser": "^3.0.3" + }, + "browserslist": [ + "defaults" + ] +} diff --git a/examples/mongodb-crud/frontend/public/favicon.ico b/examples/mongodb-crud/frontend/public/favicon.ico new file mode 100644 index 0000000..361535c Binary files /dev/null and b/examples/mongodb-crud/frontend/public/favicon.ico differ diff --git a/examples/mongodb-crud/frontend/public/index.html b/examples/mongodb-crud/frontend/public/index.html new file mode 100644 index 0000000..cc99704 --- /dev/null +++ b/examples/mongodb-crud/frontend/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + TSRPC Browser + + + +

TSRPC CRUD

+ +

See console.

+ + + \ No newline at end of file diff --git a/examples/mongodb-crud/frontend/src/index.ts b/examples/mongodb-crud/frontend/src/index.ts new file mode 100644 index 0000000..2cdf8b7 --- /dev/null +++ b/examples/mongodb-crud/frontend/src/index.ts @@ -0,0 +1,33 @@ +import { HttpClient } from 'tsrpc-browser'; +import { serviceProto } from './shared/protocols/serviceProto'; + +let client = new HttpClient(serviceProto, { + server: 'http://127.0.0.1:3000', + logger: console +}); + +window.onload = async () => { + let retAdd = await client.callApi('AddPost', { + newPost: { + author: 'k8w', + title: 'TSPRC', + content: '太好用了' + } + }); + + let retGet = await client.callApi('GetPost', { + _id: retAdd.res!.insertedId + }); + + let post = retGet.res!.post; + post.title = 'TSRPC 123'; + post.content = 'xxxxxxxx'; + + let retUpdate = await client.callApi('UpdatePost', { + update: post + }); + + let retDel = await client.callApi('DelPost', { + _id: post._id + }) +} \ No newline at end of file diff --git a/examples/mongodb-crud/frontend/src/shared/protocols/PtlAddPost.ts b/examples/mongodb-crud/frontend/src/shared/protocols/PtlAddPost.ts new file mode 100644 index 0000000..c4f367e --- /dev/null +++ b/examples/mongodb-crud/frontend/src/shared/protocols/PtlAddPost.ts @@ -0,0 +1,9 @@ +import { Post } from "./models/Post"; + +export interface ReqAddPost { + newPost: Omit; +} + +export interface ResAddPost { + insertedId: string; +} \ No newline at end of file diff --git a/examples/mongodb-crud/frontend/src/shared/protocols/PtlDelPost.ts b/examples/mongodb-crud/frontend/src/shared/protocols/PtlDelPost.ts new file mode 100644 index 0000000..026fec7 --- /dev/null +++ b/examples/mongodb-crud/frontend/src/shared/protocols/PtlDelPost.ts @@ -0,0 +1,7 @@ +export interface ReqDelPost { + _id: string; +} + +export interface ResDelPost { + +} \ No newline at end of file diff --git a/examples/mongodb-crud/frontend/src/shared/protocols/PtlGetPost.ts b/examples/mongodb-crud/frontend/src/shared/protocols/PtlGetPost.ts new file mode 100644 index 0000000..a6d169f --- /dev/null +++ b/examples/mongodb-crud/frontend/src/shared/protocols/PtlGetPost.ts @@ -0,0 +1,9 @@ +import { Post } from "./models/Post"; + +export interface ReqGetPost { + _id: string; +} + +export interface ResGetPost { + post: Post; +} diff --git a/examples/mongodb-crud/frontend/src/shared/protocols/PtlUpdatePost.ts b/examples/mongodb-crud/frontend/src/shared/protocols/PtlUpdatePost.ts new file mode 100644 index 0000000..20ccc71 --- /dev/null +++ b/examples/mongodb-crud/frontend/src/shared/protocols/PtlUpdatePost.ts @@ -0,0 +1,9 @@ +import { Post } from "./models/Post"; + +export interface ReqUpdatePost { + update: { _id: string } & Partial>; +} + +export interface ResUpdatePost { + matchedCount: number; +} \ No newline at end of file diff --git a/examples/mongodb-crud/frontend/src/shared/protocols/models/Post.ts b/examples/mongodb-crud/frontend/src/shared/protocols/models/Post.ts new file mode 100644 index 0000000..67b21e2 --- /dev/null +++ b/examples/mongodb-crud/frontend/src/shared/protocols/models/Post.ts @@ -0,0 +1,17 @@ +export interface Post { + _id: string; + author: string; + title: string; + content: string; + visitedNum: number; + + create: { + uid: string; + time: Date; + } + + update?: { + uid: string, + time: Date + } +} \ No newline at end of file diff --git a/examples/mongodb-crud/frontend/src/shared/protocols/serviceProto.ts b/examples/mongodb-crud/frontend/src/shared/protocols/serviceProto.ts new file mode 100644 index 0000000..6b3a764 --- /dev/null +++ b/examples/mongodb-crud/frontend/src/shared/protocols/serviceProto.ts @@ -0,0 +1,276 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqAddPost, ResAddPost } from './PtlAddPost'; +import { ReqDelPost, ResDelPost } from './PtlDelPost'; +import { ReqGetPost, ResGetPost } from './PtlGetPost'; +import { ReqUpdatePost, ResUpdatePost } from './PtlUpdatePost'; + +export interface ServiceType { + api: { + "AddPost": { + req: ReqAddPost, + res: ResAddPost + }, + "DelPost": { + req: ReqDelPost, + res: ResDelPost + }, + "GetPost": { + req: ReqGetPost, + res: ResGetPost + }, + "UpdatePost": { + req: ReqUpdatePost, + res: ResUpdatePost + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "version": 16, + "services": [ + { + "id": 0, + "name": "AddPost", + "type": "api" + }, + { + "id": 1, + "name": "DelPost", + "type": "api" + }, + { + "id": 2, + "name": "GetPost", + "type": "api" + }, + { + "id": 3, + "name": "UpdatePost", + "type": "api" + } + ], + "types": { + "PtlAddPost/ReqAddPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "newPost", + "type": { + "target": { + "type": "Reference", + "target": "models/Post/Post" + }, + "keys": [ + "_id", + "create", + "update", + "visitedNum" + ], + "type": "Omit" + } + } + ] + }, + "models/Post/Post": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "_id", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "author", + "type": { + "type": "String" + } + }, + { + "id": 2, + "name": "title", + "type": { + "type": "String" + } + }, + { + "id": 3, + "name": "content", + "type": { + "type": "String" + } + }, + { + "id": 4, + "name": "visitedNum", + "type": { + "type": "Number" + } + }, + { + "id": 5, + "name": "create", + "type": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "uid", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "time", + "type": { + "type": "Date" + } + } + ] + } + }, + { + "id": 6, + "name": "update", + "type": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "uid", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "time", + "type": { + "type": "Date" + } + } + ] + }, + "optional": true + } + ] + }, + "PtlAddPost/ResAddPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "insertedId", + "type": { + "type": "String" + } + } + ] + }, + "PtlDelPost/ReqDelPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "_id", + "type": { + "type": "String" + } + } + ] + }, + "PtlDelPost/ResDelPost": { + "type": "Interface" + }, + "PtlGetPost/ReqGetPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "_id", + "type": { + "type": "String" + } + } + ] + }, + "PtlGetPost/ResGetPost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "post", + "type": { + "type": "Reference", + "target": "models/Post/Post" + } + } + ] + }, + "PtlUpdatePost/ReqUpdatePost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "update", + "type": { + "type": "Intersection", + "members": [ + { + "id": 1, + "type": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "_id", + "type": { + "type": "String" + } + } + ] + } + }, + { + "id": 3, + "type": { + "type": "Partial", + "target": { + "target": { + "type": "Reference", + "target": "models/Post/Post" + }, + "keys": [ + "title", + "content" + ], + "type": "Pick" + } + } + } + ] + } + } + ] + }, + "PtlUpdatePost/ResUpdatePost": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "matchedCount", + "type": { + "type": "Number" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/examples/mongodb-crud/frontend/tsconfig.json b/examples/mongodb-crud/frontend/tsconfig.json new file mode 100644 index 0000000..f7f1d7c --- /dev/null +++ b/examples/mongodb-crud/frontend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "es2015" + ], + "module": "esnext", + "target": "es5", + "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/examples/mongodb-crud/frontend/webpack.config.js b/examples/mongodb-crud/frontend/webpack.config.js new file mode 100644 index 0000000..b0d3703 --- /dev/null +++ b/examples/mongodb-crud/frontend/webpack.config.js @@ -0,0 +1,56 @@ +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', '.mjs', '.cjs'] + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: [{ + loader: 'ts-loader', + options: { + // Compile to ES5 in production mode for better compatibility + // Compile to ES2018 in development for better debugging (like async/await) + compilerOptions: !isProduction ? { + "target": "es2018", + } : 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