Browse Source

probably a working typecheck for callbacks

master
nitowa 2 years ago
parent
commit
a1f4055194
9 changed files with 257 additions and 63 deletions
  1. 2
    1
      Index.ts
  2. 3
    9
      src/Backend.ts
  3. 74
    0
      src/Decorator.ts
  4. 21
    12
      src/Frontend.ts
  5. 3
    1
      src/Strings.ts
  6. 62
    29
      src/Types.ts
  7. 8
    5
      src/Utils.ts
  8. 83
    5
      test/Test.ts
  9. 1
    1
      tsconfig.json

+ 2
- 1
Index.ts View File

2
 export * from './src/Frontend';
2
 export * from './src/Frontend';
3
 export * from './src/Interfaces'
3
 export * from './src/Interfaces'
4
 export * from './src/Types';
4
 export * from './src/Types';
5
-export * from './src/Utils'
5
+export * from './src/Utils'
6
+export * from './src/Decorator'

+ 3
- 9
src/Backend.ts View File

5
 import * as T from './Types';
5
 import * as T from './Types';
6
 import * as U from './Utils';
6
 import * as U from './Utils';
7
 import * as I from './Interfaces';
7
 import * as I from './Interfaces';
8
-import { BAD_CONFIG_PARAM, UNKNOWN_RPC_IDENTIFIER, UNKNOWN_RPC_SERVER } from './Strings';
8
+import { BAD_CONFIG_PARAM, DESTROY_PREFIX, RPC_NO_NAME, UNKNOWN_RPC_IDENTIFIER, UNKNOWN_RPC_SERVER } from './Strings';
9
 
9
 
10
 export class RPCServer<
10
 export class RPCServer<
11
     InterfaceT extends T.RPCInterface = T.RPCInterface,
11
     InterfaceT extends T.RPCInterface = T.RPCInterface,
65
 
65
 
66
         let badRPC = exporters.flatMap(ex => typeof ex.RPCs === "function" ? ex.RPCs() : (ex as any)).find(rpc => !rpc.name)
66
         let badRPC = exporters.flatMap(ex => typeof ex.RPCs === "function" ? ex.RPCs() : (ex as any)).find(rpc => !rpc.name)
67
         if (badRPC) {
67
         if (badRPC) {
68
-            throw new Error(`
69
-            RPC did not provide a name. 
70
-            \nUse 'funtion name(..){ .. }' syntax instead.
71
-            \n
72
-            \n<------------OFFENDING RPC:
73
-            \n`+ badRPC.toString() + `
74
-            \n>------------OFFENDING RPC`)
68
+            throw new Error(RPC_NO_NAME(badRPC.toString()))
75
         }
69
         }
76
 
70
 
77
         try {
71
         try {
114
             if (this.conf.throwOnUnknownRPC) {
108
             if (this.conf.throwOnUnknownRPC) {
115
                 clientSocket.on("*", (packet) => {
109
                 clientSocket.on("*", (packet) => {
116
                     if (!infos.some(i => i.uniqueName === packet.data[0])) {
110
                     if (!infos.some(i => i.uniqueName === packet.data[0])) {
117
-                        if (packet.data[0].startsWith('destroy_')) return
111
+                        if (packet.data[0].startsWith(DESTROY_PREFIX)) return
118
                         this.errorHandler(clientSocket, new Error(UNKNOWN_RPC_SERVER(packet.data[0])), packet.data[0], [...packet.data].splice(1), true)
112
                         this.errorHandler(clientSocket, new Error(UNKNOWN_RPC_SERVER(packet.data[0])), packet.data[0], [...packet.data].splice(1), true)
119
                     }
113
                     }
120
                 })
114
                 })

+ 74
- 0
src/Decorator.ts View File

1
+import { CLASSNAME_ATTRIBUTE } from "./Strings"
2
+
3
+export abstract class DeserializerFactory {
4
+    static entityClasses = {}
5
+
6
+    static from(object: any) {
7
+        if(!object){
8
+            return
9
+        }
10
+
11
+        if(typeof object !== 'object'){ //definitely not a class object
12
+            return object
13
+        }
14
+        
15
+        if(Array.isArray(object)){
16
+            return object.map(DeserializerFactory.from)
17
+        }
18
+
19
+        const clazz = DeserializerFactory.entityClasses[object[CLASSNAME_ATTRIBUTE]]
20
+        delete object[CLASSNAME_ATTRIBUTE]
21
+
22
+        if(!clazz){ //anonymous object or class not registered as @Serializable
23
+            Object.keys(object).forEach(key => {
24
+                object[key] = DeserializerFactory.from(object[key])
25
+            })
26
+            return object
27
+        }
28
+
29
+        const obj = new clazz()
30
+        Object.keys(object).forEach(key => {
31
+            obj[key] = DeserializerFactory.from(object[key])
32
+        })
33
+        return obj
34
+    }
35
+
36
+    
37
+
38
+    static makeDeserializable(object: any){
39
+        if(!object)
40
+            return
41
+        
42
+        if(typeof object !== 'object')
43
+            return object
44
+        
45
+        if(object.constructor.name === 'Object'){
46
+            Object.keys(object).forEach(key => {
47
+                object[key] = DeserializerFactory.makeDeserializable(object[key])
48
+            })
49
+            return object
50
+        }
51
+
52
+        object[CLASSNAME_ATTRIBUTE] = object.constructor.name
53
+        Object.keys(object).forEach(key => {
54
+            object[key] = DeserializerFactory.makeDeserializable(object[key])
55
+        })
56
+
57
+        return object
58
+    }
59
+}
60
+
61
+export function Serializable(attr?: any) {
62
+    return function _Serializable<T extends { new(...args: any[]): {} }>(clazz: T) {
63
+        DeserializerFactory.entityClasses[clazz.name] = clazz
64
+        
65
+        return clazz
66
+        /*
67
+        return class extends clazz{
68
+            constructor(...args: any[]) {
69
+                super(...args)
70
+            }
71
+        }
72
+        */
73
+    }
74
+}

+ 21
- 12
src/Frontend.ts View File

4
 import * as T from './Types';
4
 import * as T from './Types';
5
 import * as I from './Interfaces';
5
 import * as I from './Interfaces';
6
 import { stripAfterEquals, appendComma } from './Utils';
6
 import { stripAfterEquals, appendComma } from './Utils';
7
-import { SOCKET_NOT_CONNECTED, UNKNOWN_RPC_IDENTIFIER, USER_DEFINED_TIMEOUT } from './Strings';
7
+import { DESTROY_PREFIX, SOCKET_NOT_CONNECTED, UNKNOWN_RPC_IDENTIFIER, USER_DEFINED_TIMEOUT } from './Strings';
8
+import { DeserializerFactory } from './Decorator';
9
+DeserializerFactory
8
 
10
 
9
 
11
 
10
 /**
12
 /**
117
     public async call(rpcname: string, ...args: any[]): Promise<any> {
119
     public async call(rpcname: string, ...args: any[]): Promise<any> {
118
         if (!this.socket) throw new Error(SOCKET_NOT_CONNECTED)
120
         if (!this.socket) throw new Error(SOCKET_NOT_CONNECTED)
119
 
121
 
120
-
121
         try {
122
         try {
122
             if(!this.conf.callTimeoutMs || this.conf.callTimeoutMs <= 0)
123
             if(!this.conf.callTimeoutMs || this.conf.callTimeoutMs <= 0)
123
                 return await this.socket.call.apply(this.socket, [rpcname, ...args])
124
                 return await this.socket.call.apply(this.socket, [rpcname, ...args])
124
             else 
125
             else 
125
-                return await Promise.race([
126
-                    this.socket.call.apply(this.socket, [rpcname, ...args]),
127
-                    new Promise((_, rej) => {
128
-                        setTimeout(_ => rej(USER_DEFINED_TIMEOUT(this.conf.callTimeoutMs)), this.conf.callTimeoutMs)
129
-                    }) 
130
-                ])
126
+                if(this.conf.callTimeoutMs){
127
+                    return await Promise.race([
128
+                        this.socket.call.apply(this.socket, [rpcname, ...args]),
129
+                        new Promise((_, rej) => {
130
+                            setTimeout(_ => rej(USER_DEFINED_TIMEOUT(this.conf.callTimeoutMs)), this.conf.callTimeoutMs)
131
+                        }) 
132
+                    ])
133
+                }else{
134
+                    return await this.socket.call.apply(this.socket, [rpcname, ...args])
135
+                }
136
+                
131
         } catch (e) {
137
         } catch (e) {
132
             this.emit('error', e)
138
             this.emit('error', e)
133
             throw e
139
             throw e
203
         const headerArgs = fnArgs.join(",")
209
         const headerArgs = fnArgs.join(",")
204
         const argParams = fnArgs.map(stripAfterEquals).join(",")
210
         const argParams = fnArgs.map(stripAfterEquals).join(",")
205
         sesame = appendComma(sesame)
211
         sesame = appendComma(sesame)
206
-
212
+        const deserializer = DeserializerFactory
207
         return eval(`async (${headerArgs}) => { 
213
         return eval(`async (${headerArgs}) => { 
208
-            return await this.call("${fnName}", ${sesame} ${argParams})
214
+            const returnvalue = await this.call("${fnName}", ${sesame} ${argParams})
215
+            return deserializer.from(returnvalue)
209
         }`)
216
         }`)
210
     }
217
     }
211
 
218
 
223
         const argParams = fnArgs.map(stripAfterEquals).join(",")
230
         const argParams = fnArgs.map(stripAfterEquals).join(",")
224
         sesame = appendComma(sesame, true)
231
         sesame = appendComma(sesame, true)
225
         headerArgs = fnArgs.length > 0 ? headerArgs + "," : headerArgs
232
         headerArgs = fnArgs.length > 0 ? headerArgs + "," : headerArgs
233
+        const deserializer = DeserializerFactory
234
+        const destroy_prefix = DESTROY_PREFIX
226
 
235
 
227
         const frontendHookStr = `
236
         const frontendHookStr = `
228
         async (${headerArgs} $__callback__$) => {
237
         async (${headerArgs} $__callback__$) => {
231
                 if(r){
240
                 if(r){
232
                     if(r.uuid){
241
                     if(r.uuid){
233
                         $__callback__$['destroy'] = () => {
242
                         $__callback__$['destroy'] = () => {
234
-                            this.socket.fire('destroy_'+r.uuid)
243
+                            this.socket.fire(destroy_prefix+r.uuid)
235
                             this.socket.unhook(r.uuid) 
244
                             this.socket.unhook(r.uuid) 
236
                         }
245
                         }
237
                         this.socket.hook(r.uuid, $__callback__$)
246
                         this.socket.hook(r.uuid, $__callback__$)
238
                     }
247
                     }
239
-                    return r.return
248
+                    return deserializer.from(r.return)
240
                 }else{
249
                 }else{
241
                     throw new Error("Empty response")
250
                     throw new Error("Empty response")
242
                 }
251
                 }

+ 3
- 1
src/Strings.ts View File

11
 \n
11
 \n
12
 \n<------------OFFENDING RPC:
12
 \n<------------OFFENDING RPC:
13
 \n${name}
13
 \n${name}
14
-\n>------------OFFENDING RPC`
14
+\n>------------OFFENDING RPC`
15
+export const CLASSNAME_ATTRIBUTE = "$__CLASSNAME__$"
16
+export const DESTROY_PREFIX = "$__DESTROY__$_"

+ 62
- 29
src/Types.ts View File

5
 export type PioBindListener = (...args: any) => void
5
 export type PioBindListener = (...args: any) => void
6
 export type PioHookListener = AnyFunction
6
 export type PioHookListener = AnyFunction
7
 
7
 
8
-export type AnyFunction = (...args:any) => any
8
+export type AnyFunction = (...args: any[]) => any
9
 export type HookFunction = AnyFunction
9
 export type HookFunction = AnyFunction
10
-export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame:string|undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean> | boolean
10
+export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame: string | undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean> | boolean
11
 export type Visibility = "127.0.0.1" | "0.0.0.0"
11
 export type Visibility = "127.0.0.1" | "0.0.0.0"
12
-export type ConnectionHandler = (socket:I.Socket) => void
13
-export type ErrorHandler = (socket:I.Socket, error:any, rpcName: string, args: any[]) => void
14
-export type CloseHandler = (socket:I.Socket) =>  void
15
-export type SesameFunction = (sesame : string) => boolean
12
+export type ConnectionHandler = (socket: I.Socket) => void
13
+export type ErrorHandler = (socket: I.Socket, error: any, rpcName: string, args: any[]) => void
14
+export type CloseHandler = (socket: I.Socket) => void
15
+export type SesameFunction = (sesame: string) => boolean
16
 export type SesameConf = {
16
 export type SesameConf = {
17
     sesame?: string | SesameFunction
17
     sesame?: string | SesameFunction
18
 }
18
 }
19
 export type FrontEndHandlerType = {
19
 export type FrontEndHandlerType = {
20
-    'error' : (e: any) => void
21
-    'close' : () => void
20
+    'error': (e: any) => void
21
+    'close': () => void
22
 }
22
 }
23
 export type ClientConfig = SocketIOClient.ConnectOpts & {
23
 export type ClientConfig = SocketIOClient.ConnectOpts & {
24
     protocol?: 'http' | 'https',
24
     protocol?: 'http' | 'https',
40
 export type Outcome = "Success" | "Error"
40
 export type Outcome = "Success" | "Error"
41
 
41
 
42
 export type Respose<T> = T & { result: Outcome }
42
 export type Respose<T> = T & { result: Outcome }
43
-export type SuccessResponse<T = {}> = Respose<T> & { result: "Success" } 
44
-export type ErrorResponse<T = {}> = Respose<T> & { result: "Error", message?:string }
43
+export type SuccessResponse<T = {}> = Respose<T> & { result: "Success" }
44
+export type ErrorResponse<T = {}> = Respose<T> & { result: "Error", message?: string }
45
 
45
 
46
 export type RPCType = 'Hook' | 'Unhook' | 'Call'
46
 export type RPCType = 'Hook' | 'Unhook' | 'Call'
47
 
47
 
53
 
53
 
54
 export type HookRPC<Name, Func extends AnyFunction> = {
54
 export type HookRPC<Name, Func extends AnyFunction> = {
55
     name: Name
55
     name: Name
56
-    hook: Func
56
+    hook: AnyFunction
57
     onCallback?: AnyFunction
57
     onCallback?: AnyFunction
58
     onDestroy?: HookCloseFunction<ReturnType<Func> extends Promise<infer T> ? T : ReturnType<Func>>
58
     onDestroy?: HookCloseFunction<ReturnType<Func> extends Promise<infer T> ? T : ReturnType<Func>>
59
 }
59
 }
60
 
60
 
61
-export type RPC<Name, Func extends AnyFunction> = HookRPC<Name, Func> | CallRPC<Name,Func> | Func
61
+export type RPC<Name, Func extends AnyFunction> = HookRPC<Name, Func> | CallRPC<Name, Func> | Func
62
 
62
 
63
 export type RPCInterface<Impl extends RPCInterface = {}> = {
63
 export type RPCInterface<Impl extends RPCInterface = {}> = {
64
-    [grp in string] : {
65
-        [rpc in string] : AnyFunction
66
-    } 
64
+    [grp in string]: {
65
+        [rpc in string]: AnyFunction
66
+    }
67
 } & Impl
67
 } & Impl
68
 
68
 
69
 export type exportT = {
69
 export type exportT = {
71
 }
71
 }
72
 
72
 
73
 export type RPCDefinitions<Ifc extends RPCInterface> = {
73
 export type RPCDefinitions<Ifc extends RPCInterface> = {
74
-    [grp in keyof Ifc]:( { [rpc in keyof Ifc[grp]]: RPC<rpc, Ifc[grp][rpc]> }[keyof Ifc[grp]] )[]
74
+    [grp in keyof Ifc]: ({ 
75
+        [rpc in keyof Ifc[grp]]: RPC<rpc, Ifc[grp][rpc]> 
76
+    }[keyof Ifc[grp]])[]
75
 }
77
 }
76
 
78
 
77
 export type BaseInfo = {
79
 export type BaseInfo = {
80
     argNames: string[],
82
     argNames: string[],
81
 }
83
 }
82
 
84
 
83
-export type HookInfo<SubresT = {}> = BaseInfo & { 
84
-    type: 'Hook', 
85
-    generator: (socket?:I.Socket) => (...args:any[]) => SubresT
85
+export type HookInfo<SubresT = {}> = BaseInfo & {
86
+    type: 'Hook',
87
+    generator: (socket?: I.Socket) => (...args: any[]) => SubresT
86
 }
88
 }
87
 
89
 
88
 export type CallInfo = BaseInfo & {
90
 export type CallInfo = BaseInfo & {
90
     call: AnyFunction
92
     call: AnyFunction
91
 }
93
 }
92
 
94
 
93
-export type RpcInfo = HookInfo |  CallInfo
94
-export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } 
95
+export type RpcInfo = HookInfo | CallInfo
96
+export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
95
 
97
 
96
 export type OnFunction = <T extends "error" | "close">(type: T, f: FrontEndHandlerType[T]) => void
98
 export type OnFunction = <T extends "error" | "close">(type: T, f: FrontEndHandlerType[T]) => void
97
-export type HookCloseFunction<T> = (res: T, rpc:HookRPC<any, any>) => any
98
-
99
-
100
-export type AsyncIfc<Ifc extends RPCInterface> = { [grp in keyof Ifc]: {[rpcname in keyof Ifc[grp]] : AsyncAnyFunction<Ifc[grp][rpcname]> } }
101
-
102
-export type AsyncAnyFunction<F extends AnyFunction = AnyFunction> = F extends (...args: Parameters<F>) => infer R 
103
-                                                                    ? ((...args: Parameters<F>) => R extends Promise<any> ? R : Promise<R> ) 
104
-                                                                    : Promise<any>
99
+export type HookCloseFunction<T> = (res: T, rpc: HookRPC<any, any>) => any
100
+
101
+
102
+export type AsyncIfc<Ifc extends RPCInterface> = { [grp in keyof Ifc]: { [rpcname in keyof Ifc[grp]]: AsyncAnyFunction<Ifc[grp][rpcname]> } }
103
+
104
+export type AsyncAnyFunction<F extends AnyFunction = AnyFunction> = F extends (...args: Parameters<F>) => infer R
105
+    ? ((...args: Parameters<F>) => R extends Promise<any> ? R : Promise<R>)
106
+    : Promise<any>
107
+
108
+type DYN_PARAM<
109
+    A = void, 
110
+    B = void,
111
+    C = void,
112
+    D = void,
113
+    E = void,
114
+    F = void,
115
+    G = void,
116
+    H = void,
117
+> = H extends void ?
118
+     G extends void ?
119
+      F extends void ?
120
+       E extends void ?
121
+        D extends void ?
122
+         C extends void ?
123
+          B extends void ?
124
+           A extends void ?
125
+            []
126
+            : [A]
127
+          : [A,B]
128
+         :[A,B,C]
129
+        :[A,B,C,D]
130
+       :[A,B,C,D,E]
131
+      :[A,B,C,D,E,F]
132
+     :[A,B,C,D,E,F,G]
133
+    :[A,B,C,D,E,F,G,H] 
134
+
135
+type Destroyable = { destroy: () => void }
136
+export type Callback<A0 = void, A1 = void, A2 = void, A3 = void, A4 = void, A5 = void, A6 = void, A7 = void> = 
137
+    (this: Destroyable, ...args: DYN_PARAM<A0, A1, A2, A3, A4, A5, A6, A7>) => void 

+ 8
- 5
src/Utils.ts View File

3
 import * as T from "./Types";
3
 import * as T from "./Types";
4
 import * as I from "./Interfaces";
4
 import * as I from "./Interfaces";
5
 import { Socket } from "socket.io"
5
 import { Socket } from "socket.io"
6
-import { CALL_NOT_FOUND, RPC_BAD_TYPE, RPC_NO_NAME } from "./Strings";
6
+import { CALL_NOT_FOUND, DESTROY_PREFIX, RPC_BAD_TYPE, RPC_NO_NAME } from "./Strings";
7
+import { DeserializerFactory } from "./Decorator";
7
 
8
 
8
 /**
9
 /**
9
  * Translate an RPC to RPCInfo for serialization.
10
  * Translate an RPC to RPCInfo for serialization.
79
     const argsArr = extractArgs(rpcFunction)
80
     const argsArr = extractArgs(rpcFunction)
80
     const args = argsArr.join(',')
81
     const args = argsArr.join(',')
81
     const argsStr = argsArr.map(stripAfterEquals).join(',')
82
     const argsStr = argsArr.map(stripAfterEquals).join(',')
83
+    const deserializer = DeserializerFactory 
82
 
84
 
83
     const callStr = `async (${args}) => {
85
     const callStr = `async (${args}) => {
84
         try{
86
         try{
85
-            return await rpcFunction(${argsStr})
87
+            const res = await rpcFunction(${argsStr})
88
+            return deserializer.makeDeserializable(res)
86
         }catch(e){
89
         }catch(e){
87
             errorHandler($__socket__$, e, rpcName, [${args}])
90
             errorHandler($__socket__$, e, rpcName, [${args}])
88
         }
91
         }
113
         : callArgs
116
         : callArgs
114
 
117
 
115
     callArgs = appendComma(callArgs, false)
118
     callArgs = appendComma(callArgs, false)
116
-
119
+    const destroy_prefix = DESTROY_PREFIX
117
     const hookStr = `
120
     const hookStr = `
118
     ($__socket__$) => async (${args}) => {
121
     ($__socket__$) => async (${args}) => {
119
         try{
122
         try{
123
                 ${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``}
126
                 ${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``}
124
                 $__socket__$.call.apply($__socket__$, [uuid, ...cbargs])
127
                 $__socket__$.call.apply($__socket__$, [uuid, ...cbargs])
125
             })
128
             })
126
-            ${rpc.onDestroy ? `$__socket__$.bind('destroy_'+uuid, () => {
129
+            ${rpc.onDestroy ? `$__socket__$.bind(destroy_prefix+uuid, () => {
127
                 rpc.onDestroy(res, rpc)
130
                 rpc.onDestroy(res, rpc)
128
             })` : ``}
131
             })` : ``}
129
             return {'uuid': uuid, 'return': res}
132
             return {'uuid': uuid, 'return': res}
144
  */
147
  */
145
 const extractArgs = (f: Function): string[] => {
148
 const extractArgs = (f: Function): string[] => {
146
     let fn: string
149
     let fn: string
147
-    fn = (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(") + 1)
150
+    fn = (fn = String(f)).substring(0, fn.indexOf(")")).substring(fn.indexOf("(") + 1)
148
     return fn !== "" ? fn.split(',') : []
151
     return fn !== "" ? fn.split(',') : []
149
 }
152
 }
150
 
153
 

+ 83
- 5
test/Test.ts View File

1
 import { describe, it } from "mocha";
1
 import { describe, it } from "mocha";
2
-import { RPCServer, RPCSocket } from '../Index'
2
+import { RPCServer, RPCSocket, Serializable } from '../Index'
3
 import { RPCExporter, Socket } from "../src/Interfaces";
3
 import { RPCExporter, Socket } from "../src/Interfaces";
4
-import { ConnectedSocket } from "../src/Types";
4
+import { ConnectedSocket, Callback } from "../src/Types";
5
 import * as log from 'why-is-node-running';
5
 import * as log from 'why-is-node-running';
6
 import * as http from 'http';
6
 import * as http from 'http';
7
 import * as express from 'express';
7
 import * as express from 'express';
614
 type SesameTestIfc = {
614
 type SesameTestIfc = {
615
     test: {
615
     test: {
616
         checkCandy: () => Promise<string>
616
         checkCandy: () => Promise<string>
617
-        subscribe: (callback: Function) => Promise<topicDTO>
617
+        subscribe: (callback: Callback<string>) => Promise<topicDTO>
618
         manyParams: <A = string, B = number, C = boolean, D = Object>(a: A, b: B, c: C, d: D) => Promise<[A, B, C, D]>
618
         manyParams: <A = string, B = number, C = boolean, D = Object>(a: A, b: B, c: C, d: D) => Promise<[A, B, C, D]>
619
     }
619
     }
620
 }
620
 }
688
     })
688
     })
689
 
689
 
690
     it('callback should work with sesame', (done) => {
690
     it('callback should work with sesame', (done) => {
691
-        client.test.subscribe((c) => {
691
+        client.test.subscribe(function(c){
692
             if (c === candy) {
692
             if (c === candy) {
693
                 done()
693
                 done()
694
             }
694
             }
1009
             done(e)
1009
             done(e)
1010
         })
1010
         })
1011
     })
1011
     })
1012
-
1013
 })
1012
 })
1013
+
1014
+describe("class (de-)serialization", () => {
1015
+
1016
+    @Serializable()
1017
+    class SubClass{
1018
+        fString = "F"
1019
+    }
1020
+
1021
+    @Serializable()
1022
+    class TestClass{
1023
+        aString = "A"
1024
+        aNumber = 46
1025
+        aObject = {
1026
+            x: "x",
1027
+            y: undefined,
1028
+            sub: new SubClass()
1029
+        }
1030
+        aClassObject = new SubClass()
1031
+
1032
+        public returnOK(){
1033
+            return "OK"
1034
+        }
1035
+    }
1036
+
1037
+    let myServer: RPCServer;
1038
+    let mySocket: RPCSocket;
1039
+
1040
+    before(function(done){
1041
+        myServer = new RPCServer([{
1042
+            name: "Test",
1043
+            RPCs: [
1044
+                function returnClass(){
1045
+                    return new TestClass()
1046
+                }
1047
+            ]
1048
+        }])
1049
+        myServer.listen(8084)
1050
+
1051
+        mySocket = new RPCSocket(8084, 'localhost')
1052
+        mySocket.connect().then(() => done())
1053
+    })
1054
+
1055
+    after(function(done){
1056
+        mySocket.close()
1057
+        myServer.close()
1058
+        done()
1059
+    })
1060
+
1061
+
1062
+    it("receives class in call response", async () => {
1063
+        const obj: TestClass = await mySocket['Test'].returnClass()
1064
+
1065
+        expect(obj).to.be.an.instanceOf(TestClass)
1066
+        expect(obj.aString).to.be.a('string')
1067
+        expect(obj.aNumber).to.be.a('number')
1068
+        expect(obj.aObject).to.be.a('object')
1069
+        expect(obj.aObject.x).to.be.a('string')
1070
+        expect(obj.aObject.y).to.be.undefined
1071
+        expect(obj.aObject.sub).to.be.an.instanceOf(SubClass)
1072
+        expect(obj.aClassObject).to.be.an.instanceOf(SubClass)
1073
+
1074
+        expect(obj.returnOK()).to.be.equal('OK')
1075
+    })
1076
+
1077
+    it("receives class in hook response", async () => {
1078
+        const obj: TestClass = await mySocket['Test'].returnClass()
1079
+
1080
+        expect(obj).to.be.an.instanceOf(TestClass)
1081
+        expect(obj.aString).to.be.a('string')
1082
+        expect(obj.aNumber).to.be.a('number')
1083
+        expect(obj.aObject).to.be.a('object')
1084
+        expect(obj.aObject.x).to.be.a('string')
1085
+        expect(obj.aObject.y).to.be.undefined
1086
+        expect(obj.aObject.sub).to.be.an.instanceOf(SubClass)
1087
+        expect(obj.aClassObject).to.be.an.instanceOf(SubClass)
1088
+
1089
+        expect(obj.returnOK()).to.be.equal('OK')
1090
+    })
1091
+})

+ 1
- 1
tsconfig.json View File

9
       "strict": true,
9
       "strict": true,
10
       "experimentalDecorators": true
10
       "experimentalDecorators": true
11
     },
11
     },
12
-    "include": ["src/**/*.ts", "test/**/*.ts", "Index.ts", "demo.ts", "scratchpad.ts"],
12
+    "include": ["src/**/*.ts", "test/**/*.ts", "Index.ts", "demo.ts", "wat.ts"],
13
     "exclude": ["node_modules"]
13
     "exclude": ["node_modules"]
14
   }
14
   }

Loading…
Cancel
Save