@mingzey/typedrpc 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mingzey/typedrpc",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "TypeScript-based RPC framework with support for multiple connection types including HTTP, Socket, and SocketIO or custom",
5
5
  "author": "MingZeY <1552904342@qq.com>",
6
6
  "keywords": [
@@ -9,7 +9,8 @@
9
9
  ],
10
10
  "license": "MIT",
11
11
  "files": [
12
- "dist/**/*"
12
+ "dist/**/*",
13
+ "src/**/*"
13
14
  ],
14
15
  "main": "dist/index.js",
15
16
  "type": "module",
package/src/api.ts ADDED
@@ -0,0 +1,72 @@
1
+ import type { TypedRPCAPIDefine } from "./define.js";
2
+ import type { TypedRPCRequestPacket, TypedRPCResponsePacket } from "./packet.js";
3
+ import type { TypedRPCDefineToTypedRPCAPI } from "./utils.js";
4
+
5
+
6
+ class TypedRPCAPI<T extends TypedRPCAPIDefine<any>>{
7
+ constructor(){
8
+
9
+ }
10
+
11
+ interface(callback:(context:{
12
+ serviceName:string,methodName:string,args:any[]
13
+ }) => Promise<{
14
+ request:TypedRPCRequestPacket,
15
+ response:TypedRPCResponsePacket,
16
+ }>):TypedRPCDefineToTypedRPCAPI<T>{
17
+ return new Proxy({}, {
18
+ get(target, serviceName, receiver) {
19
+ if (typeof serviceName !== 'string') {
20
+ return Reflect.get(target, serviceName, receiver);
21
+ }
22
+ return new Proxy({}, {
23
+ get(target, methodName, receiver) {
24
+ if (typeof methodName !== 'string') {
25
+ return Reflect.get(target, methodName, receiver);
26
+ }
27
+
28
+ const path = `${serviceName}.${methodName}`;
29
+ const id = `${serviceName}.${methodName}`;
30
+
31
+ return {
32
+ call: async (...args: any[]) => {
33
+ const result = await callback({
34
+ serviceName:serviceName,
35
+ methodName:methodName,
36
+ args:args,
37
+ });
38
+ if(result.response.error){
39
+ throw result.response.error;
40
+ }
41
+ return result.response.result;
42
+ },
43
+ request:async (config:{
44
+ args?:any[],
45
+ callback?:(result:any,req:TypedRPCRequestPacket,res:TypedRPCResponsePacket) => void,
46
+ error?:(error:any,req:TypedRPCRequestPacket,res:TypedRPCResponsePacket) => void,
47
+ }) => {
48
+ const result = await callback({
49
+ serviceName:serviceName,
50
+ methodName:methodName,
51
+ args:config.args || [],
52
+ });
53
+ if(result.response.error){
54
+ config.error?.(result.response.error,result.request,result.response);
55
+ }else{
56
+ config.callback?.(result.response.result,result.request,result.response);
57
+ }
58
+ },
59
+ id,
60
+ path
61
+ };
62
+ }
63
+ });
64
+ }
65
+ }) as TypedRPCDefineToTypedRPCAPI<T>;
66
+ }
67
+ }
68
+
69
+
70
+ export {
71
+ TypedRPCAPI
72
+ }
package/src/client.ts ADDED
@@ -0,0 +1,77 @@
1
+ import { TypedRPCAPI } from "./api.js";
2
+ import type { TypedRPCConnection, TypedRPCConnectionProvider } from "./connecitons/basic.js";
3
+ import { TypedRPCConnectionProviderDefault } from "./connection.js";
4
+ import { TypedRPCCore } from "./core.js";
5
+ import { TypedRPCAPIDefine } from "./define.js";
6
+
7
+ import { TypedEmitter, type TypedRPCDefineMethodBody, type TypedRPCDefineMethodName, type TypedRPCDefineServiceInstance, type TypedRPCDefineServiceName, type TypedRPCDefineToTypedRPCAPI } from "./utils.js";
8
+
9
+ type TypedRPCClientConfig<T extends TypedRPCAPIDefine<any>,R extends TypedRPCAPIDefine<any>> = {
10
+ local?:T,
11
+ remote?:R,
12
+ connection?:{
13
+ provider:TypedRPCConnectionProvider,
14
+ }
15
+ }
16
+
17
+ type TypedRPCClientEvents = {
18
+ // connection:(connection:TypedRPCConnection)=>void,
19
+ }
20
+
21
+ class TypedRPCClient<T extends TypedRPCAPIDefine<any>,R extends TypedRPCAPIDefine<any>> {
22
+
23
+ public emitter = new TypedEmitter<TypedRPCClientEvents>();
24
+ private config:TypedRPCClientConfig<T,R>;
25
+ public core:TypedRPCCore;
26
+
27
+ constructor(config?:TypedRPCClientConfig<T,R>){
28
+ const defaultConfig:TypedRPCClientConfig<T,R> = {
29
+ connection:{
30
+ provider:new TypedRPCConnectionProviderDefault(),
31
+ }
32
+ }
33
+ this.config = {...defaultConfig,...config};
34
+ this.core = new TypedRPCCore(this.config);
35
+ }
36
+
37
+ hook<S extends TypedRPCDefineServiceName<T>,M extends TypedRPCDefineMethodName<T,S>>(serviceName:S,methodName:M,config:{
38
+ handler:TypedRPCDefineMethodBody<T,S,M>,
39
+ bind?:any,
40
+ }){
41
+ return this.core.hook(serviceName,methodName,config);
42
+ }
43
+
44
+ hookService<S extends TypedRPCDefineServiceName<T>>(serviceName:S,instance:TypedRPCDefineServiceInstance<T,S>){
45
+ const methodList = TypedRPCAPIDefine.getMethodList(instance);
46
+ for(let methodName of methodList){
47
+ this.hook(serviceName,methodName as TypedRPCDefineMethodName<T,S>,{
48
+ handler:instance[methodName],
49
+ bind:instance,
50
+ })
51
+ }
52
+ }
53
+
54
+ get use(){
55
+ return this.core.handler.use.bind(this.core.handler);
56
+ }
57
+
58
+ get connect(){
59
+ return this.core.connect.bind(this.core);
60
+ }
61
+
62
+ getAPI(connection:TypedRPCConnection):TypedRPCDefineToTypedRPCAPI<R>{
63
+ const api = new TypedRPCAPI<R>();
64
+ return api.interface(async (context) => {
65
+ return await this.core.request({
66
+ connection,
67
+ serviceName:context.serviceName,
68
+ methodName:context.methodName,
69
+ args:context.args,
70
+ })
71
+ })
72
+ }
73
+ }
74
+
75
+ export {
76
+ TypedRPCClient,
77
+ }
@@ -0,0 +1,49 @@
1
+ import { TypedEmitter } from "../utils.js";
2
+
3
+ type TypedRPCConnectionEvents = {
4
+ /** 有新的请求进入时,应该由Provider调起 */
5
+ request:(context:{
6
+ data:string,
7
+ response:(data:string) => void,
8
+ }) => void;
9
+ }
10
+ abstract class TypedRPCConnection{
11
+ public emitter = new TypedEmitter<TypedRPCConnectionEvents>();
12
+ abstract request(data:string):Promise<string>;
13
+ /** 获取连接的id */
14
+ abstract getId():string;
15
+ /** 关闭连接 */
16
+ abstract close():boolean;
17
+ /** 是否关闭 */
18
+ abstract isClosed():boolean;
19
+ }
20
+
21
+ type TypedRPCConnectionProviderEvents = {
22
+ /** 新的连接建立时 */
23
+ connection:(connection:TypedRPCConnection) => void,
24
+ }
25
+ abstract class TypedRPCConnectionProvider{
26
+ public emitter = new TypedEmitter<TypedRPCConnectionProviderEvents>();
27
+ /**
28
+ * Server用,用于监听一个端口
29
+ */
30
+ abstract listen(config:{
31
+ port:number,
32
+ hostname?:string,
33
+ }):Promise<boolean>;
34
+
35
+ /**
36
+ * Server用,用于关闭监听的端口
37
+ */
38
+ abstract close():Promise<boolean>;
39
+
40
+ /**
41
+ * Client用,用于建立一个连接
42
+ */
43
+ abstract connect(target:string):Promise<TypedRPCConnection>;
44
+ }
45
+
46
+ export {
47
+ TypedRPCConnection,
48
+ TypedRPCConnectionProvider,
49
+ }
@@ -0,0 +1,184 @@
1
+
2
+ import { IdMaker } from "../utils.js";
3
+ import { TypedRPCConnection, TypedRPCConnectionProvider } from "./basic.js";
4
+
5
+
6
+ /** 基于http的connection和provider */
7
+ class TypedRPCConnectionHTTP extends TypedRPCConnection{
8
+
9
+ private id:string;
10
+ private closed:boolean = false;
11
+
12
+ constructor(private config:{
13
+ side:'client'|'server',
14
+ target?:string,
15
+ }){
16
+ super();
17
+ this.id = this.makeId();
18
+ }
19
+
20
+ private makeId(){
21
+ return IdMaker.makeId();
22
+ }
23
+
24
+ async request(data: string): Promise<string> {
25
+ if(this.config.side == 'server'){
26
+ throw new Error("The server connection cannot send RPC requests because the current connection is not bidirectional");
27
+ }
28
+ if(!this.config.target){
29
+ throw new Error("Client connection must have target");
30
+ }
31
+ // 发起请求
32
+ const response = await fetch(`http://${this.config.target}`,{
33
+ method:'POST',
34
+ body:data,
35
+ headers:{
36
+ // 必须指定typedrpc为1,否则服务器会认为这是一个普通的POST请求
37
+ typedrpc:'1',
38
+ }
39
+ }).then((res) => {
40
+ return res.text();
41
+ })
42
+ return response;
43
+ }
44
+
45
+ getId(): string {
46
+ return this.id;
47
+ }
48
+
49
+ close(): boolean {
50
+ this.closed = true;
51
+ return true;// http connection 关闭时,直接返回true
52
+ }
53
+
54
+ isClosed(): boolean {
55
+ return this.closed;
56
+ }
57
+
58
+ }
59
+
60
+ type TypedRPCConnectionProviderHTTPServer = import('http').Server;
61
+
62
+ type TypedRPCConnectionProviderHTTPConfig = {
63
+ server?:TypedRPCConnectionProviderHTTPServer,
64
+ }
65
+
66
+ type TypedRPCConnectionProviderHTTPMiddleware = (req:any,res:any) => void;
67
+
68
+ class TypedRPCConnectionProviderHTTP extends TypedRPCConnectionProvider{
69
+
70
+ public config:TypedRPCConnectionProviderHTTPConfig;
71
+ public server:TypedRPCConnectionProviderHTTPServer | null = null;
72
+ private middlewares:TypedRPCConnectionProviderHTTPMiddleware[] = [];
73
+
74
+ constructor(config?:TypedRPCConnectionProviderHTTPConfig){
75
+ super();
76
+ const defaultConfig:TypedRPCConnectionProviderHTTPConfig = {
77
+
78
+ }
79
+ this.config = {...defaultConfig,...config};
80
+ this.server = this.config.server || null;
81
+ if(this.server){
82
+ this.initServer(this.server);
83
+ }
84
+ }
85
+
86
+ public use(middleware:TypedRPCConnectionProviderHTTPMiddleware){
87
+ this.middlewares.push(middleware);
88
+ }
89
+
90
+ private initServer(server:TypedRPCConnectionProviderHTTPServer){
91
+ server.on('request',(req,res) => {
92
+ // 如果请求是不是POST,直接忽略
93
+ // 如果请求头没有typedrpc=1,直接忽略
94
+ if(req.method !== 'POST'
95
+ || req.headers['typedrpc'] !== '1'
96
+ ){
97
+ // 调用中间件
98
+ this.middlewares.forEach((middleware) => {
99
+ middleware(req,res);
100
+ })
101
+ return;
102
+ }
103
+
104
+ let data = '';
105
+ req.on('data',(chunk) => {
106
+ data += chunk.toString();
107
+ })
108
+ req.on('end',() => {
109
+ // 创建一个connection
110
+ const connection = new TypedRPCConnectionHTTP({
111
+ side:'server',
112
+ });
113
+ this.emitter.emit('connection',connection);
114
+ connection.emitter.emit('request',{
115
+ data:data,
116
+ response:(data) => {
117
+ res.end(data);
118
+ connection.close();
119
+ }
120
+ })
121
+ })
122
+ })
123
+ }
124
+
125
+ private async createServer():Promise<TypedRPCConnectionProviderHTTPServer>{
126
+ const httpSupport = await import("http").catch(() => null);
127
+ if(!httpSupport){
128
+ throw new Error("http module not found");
129
+ }
130
+ return httpSupport.default.createServer();
131
+ }
132
+
133
+ async listen(config:{
134
+ port:number,
135
+ host?:string,
136
+ }): Promise<boolean> {
137
+ if(!this.server){
138
+ this.server = await this.createServer();
139
+ this.initServer(this.server);
140
+ }
141
+
142
+ return new Promise<boolean>((resolve) => {
143
+ if(!this.server){
144
+ throw new Error("Listen before server created");
145
+ }
146
+ this.server.listen({
147
+ port:config.port,
148
+ host:config.host,
149
+ },() => {
150
+ resolve(true);
151
+ });
152
+ })
153
+
154
+ }
155
+
156
+ close(): Promise<boolean> {
157
+ return new Promise<boolean>((resolve,reject) => {
158
+ if(!this.server){
159
+ return true;
160
+ }
161
+ this.server.close((err) => {
162
+ if(err){
163
+ reject(err);
164
+ }
165
+ resolve(true);
166
+ })
167
+ this.server = null;
168
+ })
169
+ }
170
+
171
+ async connect(target: string): Promise<TypedRPCConnectionHTTP> {
172
+ const connection = new TypedRPCConnectionHTTP({
173
+ side:'client',
174
+ target,
175
+ });
176
+ this.emitter.emit('connection',connection);
177
+ return connection;
178
+ }
179
+ }
180
+
181
+ export {
182
+ TypedRPCConnectionHTTP,
183
+ TypedRPCConnectionProviderHTTP,
184
+ }
@@ -0,0 +1,247 @@
1
+ import { IdMaker, TypedEmitter } from '../utils.js';
2
+ import { TypedRPCConnection, TypedRPCConnectionProvider } from './basic.js';
3
+
4
+ type TypedRPCConnectionSocketPayload = {
5
+ type: 'request' | 'response',
6
+ id: string,
7
+ data: string,
8
+ }
9
+
10
+ type TypedRPCConnectionMessageEvents = {
11
+ receive: (data: string) => void,
12
+ }
13
+ class TypedRPCConnectionSocket extends TypedRPCConnection {
14
+ public msgEmitter = new TypedEmitter<TypedRPCConnectionMessageEvents>();
15
+ private currentId = 1;
16
+ private requests: Map<string, {
17
+ resolve: (data: string) => void
18
+ }> = new Map();
19
+
20
+ constructor(private socket: {
21
+ id: string,
22
+ send: (data: string) => void;
23
+ close: () => boolean;
24
+ isClosed: () => boolean;
25
+ }) {
26
+ super();
27
+ // 处理外部请求
28
+ this.msgEmitter.on('receive', (data) => {
29
+ const recivePlayload: TypedRPCConnectionSocketPayload = JSON.parse(data);
30
+ if (recivePlayload.type == 'request'
31
+ && recivePlayload.id
32
+ ) {
33
+ this.emitter.emit('request', {// 告知TypedRPCHandler有新请求
34
+ data: recivePlayload.data,
35
+ response: (data) => {
36
+ const sendPlayout: TypedRPCConnectionSocketPayload = {
37
+ type: 'response',
38
+ id: recivePlayload.id,
39
+ data: data,
40
+ }
41
+ this.socket.send(JSON.stringify(sendPlayout));
42
+ }
43
+ })
44
+ } else if (recivePlayload.type == 'response'
45
+ && recivePlayload.id
46
+ ) {
47
+ this.requests.get(recivePlayload.id)?.resolve(recivePlayload.data);
48
+ }
49
+ })
50
+ }
51
+
52
+ async request(data: string): Promise<string> {
53
+ const requestId = `${this.currentId++}`;
54
+ const payload: TypedRPCConnectionSocketPayload = {
55
+ type: 'request',
56
+ id: requestId,
57
+ data: data,
58
+ }
59
+
60
+ const requestPromise = new Promise<string>((resolve) => {
61
+ this.requests.set(requestId, {
62
+ resolve: resolve
63
+ })
64
+
65
+ // 发送数据
66
+ this.socket.send(JSON.stringify(payload));
67
+ }).finally(() => {
68
+ this.requests.delete(requestId);
69
+ })
70
+ return await requestPromise;
71
+ }
72
+ getId(): string {
73
+ return this.socket.id;
74
+ }
75
+ close(): boolean {
76
+ return this.socket.close();
77
+ }
78
+ isClosed(): boolean {
79
+ return this.socket.isClosed();
80
+ }
81
+
82
+ }
83
+
84
+ class TypedRPCConnectionProviderSocket extends TypedRPCConnectionProvider {
85
+
86
+ private server: import('net').Server | undefined;
87
+ private sockets: Set<import('net').Socket> = new Set();
88
+
89
+ async listen(config: { port: number; hostname?: string; }): Promise<boolean> {
90
+ const net = await import('net').catch(() => null);
91
+ if(!net){
92
+ throw new Error("net module not found");
93
+ }
94
+ const server = net.createServer((socket) => {
95
+ this.sockets.add(socket);
96
+ const connection = new TypedRPCConnectionSocket({
97
+ id: IdMaker.makeId(),
98
+ send: (data) => {
99
+ const buffer = Buffer.from(data);
100
+ const length = Buffer.alloc(4);
101
+ length.writeUInt32BE(buffer.length, 0);
102
+ socket.write(length);
103
+ socket.write(buffer);
104
+ },
105
+ close: () => {
106
+ socket.end();
107
+ return true;
108
+ },
109
+ isClosed: () => {
110
+ return socket.destroyed;
111
+ },
112
+ })
113
+ this.emitter.emit('connection', connection);
114
+
115
+ let buffer = Buffer.alloc(0);
116
+ let expectedLength: number | null = null;
117
+ socket.on('data', (chunk) => {
118
+ buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
119
+ while (buffer.length >= 4) {
120
+ if (expectedLength === null) {
121
+ // 读取消息长度
122
+ expectedLength = buffer.readUInt32BE(0);
123
+ buffer = Buffer.from(buffer.subarray(4));
124
+ }
125
+
126
+ if (buffer.length >= expectedLength) {
127
+ // 读取完整消息
128
+ const message = buffer.subarray(0, expectedLength).toString();
129
+ buffer = Buffer.from(buffer.subarray(expectedLength));
130
+ expectedLength = null;
131
+
132
+ // 处理消息
133
+ connection.msgEmitter.emit('receive', message);
134
+ } else {
135
+ break;
136
+ }
137
+ }
138
+ });
139
+ socket.on('end', () => {
140
+ connection.close();
141
+ })
142
+ socket.on('close', () => {
143
+ this.sockets.delete(socket);
144
+ })
145
+ })
146
+ this.server = server;
147
+ return new Promise((resolve) => {
148
+ server.listen(config.port, config.hostname, () => {
149
+ resolve(true);
150
+ });
151
+ })
152
+ }
153
+
154
+
155
+ async close(): Promise<boolean> {
156
+ return new Promise<boolean>((resolve, reject) => {
157
+ if (!this.server) {
158
+ return resolve(true);
159
+ }
160
+ // 关闭服务器,停止所有连接
161
+ this.server.close(() => {
162
+ resolve(true);
163
+ });
164
+ this.server = undefined;
165
+ // 关闭所有已建立的连接
166
+ for (const socket of this.sockets) {
167
+ socket.end();
168
+ }
169
+ this.sockets.clear();
170
+ })
171
+ }
172
+
173
+
174
+
175
+ connect(target: string): Promise<TypedRPCConnection> {
176
+ return new Promise<TypedRPCConnection>(async (resolve, reject) => {
177
+ const net = await import('net').catch(() => null);
178
+ if(!net){
179
+ reject(new Error("net module not found"));
180
+ return;
181
+ }
182
+ const socket = new net.Socket();
183
+ const [host, port] = target.split(':');
184
+ if (!host || !port
185
+ || !Number.isInteger(Number(port))
186
+ ) {
187
+ reject(new Error('target format error'));
188
+ return;
189
+ }
190
+ const connection = new TypedRPCConnectionSocket({
191
+ id: IdMaker.makeId(),
192
+ send: (data) => {
193
+ const buffer = Buffer.from(data);
194
+ const length = Buffer.alloc(4);
195
+ length.writeUInt32BE(buffer.length, 0);
196
+ socket.write(length);
197
+ socket.write(buffer);
198
+ },
199
+ close: () => {
200
+ socket.end();
201
+ return true;
202
+ },
203
+ isClosed: () => {
204
+ return socket.destroyed;
205
+ }
206
+ })
207
+ this.emitter.emit('connection', connection);
208
+ socket.on('connect', () => {
209
+ resolve(connection);
210
+ })
211
+ let buffer = Buffer.alloc(0);
212
+ let expectedLength: number | null = null;
213
+ socket.on('data', (chunk) => {
214
+ buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
215
+ while (buffer.length >= 4) {
216
+ if (expectedLength === null) {
217
+ // 读取消息长度
218
+ expectedLength = buffer.readUInt32BE(0);
219
+ buffer = Buffer.from(buffer.subarray(4));
220
+ }
221
+
222
+ if (buffer.length >= expectedLength) {
223
+ // 读取完整消息
224
+ const message = buffer.subarray(0, expectedLength).toString();
225
+ buffer = Buffer.from(buffer.subarray(expectedLength));
226
+ expectedLength = null;
227
+
228
+ // 处理消息
229
+ connection.msgEmitter.emit('receive', message);
230
+ } else {
231
+ break;
232
+ }
233
+ }
234
+ });
235
+ socket.on('close', () => {
236
+ connection.close();
237
+ })
238
+ socket.connect(Number(port), host);
239
+ })
240
+ }
241
+
242
+ }
243
+
244
+ export {
245
+ TypedRPCConnectionSocket,
246
+ TypedRPCConnectionProviderSocket,
247
+ }