Try to use class instead of const function in typeScript and getting error

{Details}

  1. Versions: Nakama {3.5}, {Windows, Mac, Linux binary or Docker}, {client library (SDK) and version}
  2. Server Framework Runtime language (If relevant) {Go, TS/JS, Lua}
//getting below error 
 nakama       | {"level":"error","ts":"2024-06-10T18:24:53.871Z","caller":"server/runtime_javascript.go:175","msg":"JavaScript runtime function invalid.","key":"dune_CreateMatch"}
//=== main.ts =====
let InitModule: nkruntime.InitModule = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer)
{
    logger.warn('Tag::serverMain start Init');
    //<editor-fold desc="Dunes game">
    let dunes=new BetBlack.Dunes.DunesGameManager();
    let dune_matchInit = dunes.MatchInit;
    let dune_matchJoinAttempt = dunes.MatchJoinAttempted;
    let dune_matchJoin = dunes.MatchJoin
    let dune_matchLeave = dunes.MatchLeave;
    let dune_matchLoop = dunes.MatchLoop
    let dune_matchSignal = dunes.MatchSignal;
    let dune_matchTerminate = dunes.MatchTerminate;
    initializer.registerMatch('Dunes_Lobby', {
        matchInit: dune_matchInit,
        matchJoinAttempt: dune_matchJoinAttempt,
        matchJoin: dune_matchJoin,
        matchLeave: dune_matchLeave,
        matchLoop: dune_matchLoop,
        matchSignal: dune_matchSignal,
        matchTerminate: dune_matchTerminate
    });
    let dune_CreateMatch=dunes.CreateMatch;
    initializer.registerRpc('Dunes_CreateMatch',dune_CreateMatch);
    //</editor-fold>
    //<editor-fold desc="Pool game">
    //let pool=new BetBlack.Pool.PoolGameManager();
    //let pool_matchInit = pool.MatchInit;
    //let pool_matchJoinAttempt = pool.MatchJoinAttempted;
    //let pool_matchJoin = pool.MatchJoin
    //let pool_matchLeave = pool.MatchLeave;
    //let pool_matchLoop = pool.MatchLoop
    //let pool_matchSignal = pool.MatchSignal;
    //let pool_matchTerminate = pool.MatchTerminate;
    //initializer.registerMatch('Pool_Lobby', {
    //    matchInit: pool_matchInit,
    //    matchJoinAttempt: pool_matchJoinAttempt,
    //    matchJoin: pool_matchJoin,
    //    matchLeave: pool_matchLeave,
    //    matchLoop: pool_matchLoop,
    //   matchSignal: pool_matchSignal,
    //    matchTerminate: pool_matchTerminate
    //});
    //let pool_CreateMatch=pool.CreateMatch;
    //initializer.registerRpc('Dunes_CreateMatch',pool_CreateMatch);
    //</editor-fold>
    logger.warn('Tag::serverMain start Done');
}

//==== BaseGame.ts ======
///<reference path="MiscData.ts"/>
namespace BetBlack
{
    export class PlayerDetails
    {
        playerName: string="";
        playerAvatarId: string="";
    }

    export class MatchMakingDetails
    {
        roomId: string = "not found";
        minPlayerCount: number = 1;
        maxPlayerCount: number = 1;
        autoDestroyRoom: number = 3600;

        constructor(roomId: string, minPlayerCount: number, maxPlayerCount: number, autoDestroyRoom: number)
        {
            this.roomId = roomId;
            this.minPlayerCount = minPlayerCount;
            this.maxPlayerCount = maxPlayerCount;
            this.autoDestroyRoom = autoDestroyRoom;
        }
        // Method to display student information
        toString(): string
        {
            return `roomId: ${this.roomId}, maxPlayerCount: ${this.maxPlayerCount}, minPlayerCount: ${this.minPlayerCount}, autoDestroyRoom: ${this.autoDestroyRoom}`;
        }


    }

    export class MatchMakingResponseData
    {
        roomId: string;
        matchId: string;
        minPlayerCount: number = 1;
        maxPlayerCount: number = 1;
        currentPlayerCount: number = 0;
        playerDetails: PlayerDetails[] | null;

        constructor(roomId: string, matchId: string, minPlayerCount: number,maxPlayerCount: number, playerDetails: PlayerDetails[] | null)
        {
            this.roomId = roomId;
            this.matchId = matchId;
            this.maxPlayerCount = maxPlayerCount;
            this.minPlayerCount = minPlayerCount;
            this.playerDetails = playerDetails;
        }
    }

    export class MatchMakingResponse extends Response<MatchMakingResponseData>
    {

    }

    export interface IGameState extends  nkruntime.MatchState
    {

    }

    class WaitingMatches
    {
        public waitingMatchesByRoomId : Map<string,MatchMakingResponseData>;
        public waitingMatchesByMatchId :Map<string,MatchMakingResponseData>;

        public constructor()
        {
         this.waitingMatchesByRoomId = new Map<string,MatchMakingResponseData>();
         this.waitingMatchesByMatchId = new Map<string,MatchMakingResponseData>();
        }

        public Set(roomId :string ,matchId:string,mmr: MatchMakingResponseData) : void
        {
            this.waitingMatchesByRoomId.set(roomId,mmr);
            this.waitingMatchesByMatchId.set(matchId,mmr);
        }

        public GetMMRByRoomId(roomId:string) : MatchMakingResponseData |null
        {
            if(this.waitingMatchesByRoomId.has(roomId))
            {
                return this.waitingMatchesByRoomId.get(roomId) ||  null;
            }
            else
            {
                return null;
            }
        }

        public GetMMRByMatchId(matchId:string) : MatchMakingResponseData |null
        {
            if(this.waitingMatchesByMatchId.has(matchId))
            {
                return this.waitingMatchesByMatchId.get(matchId) ||  null;
            }
            else
            {
                return null;
            }
        }

        public DeletedByRoomId (matchId:string) : boolean
        {
            this.waitingMatchesByMatchId.delete(matchId)
            return this.waitingMatchesByRoomId.delete(matchId);
        }

        public DeletedByMatchId (matchId:string) : boolean
        {
            this.waitingMatchesByRoomId.delete(matchId)
            return this.waitingMatchesByMatchId.delete(matchId);
        }
    }

    export abstract class BaseGameManager
    {
        protected waitingMap = new WaitingMatches();
        public GetAll(): Map<string, nkruntime.RpcFunction>
        {
            let rpcFunctions: Map<string, nkruntime.RpcFunction> = new Map<string, nkruntime.RpcFunction>();
            let gameName = this.GetGameName();
            rpcFunctions.set(`${gameName}_CreateMatch`, this.CreateMatch);
            return rpcFunctions;
        }

        abstract GetGameName(): string;

        CreateMatch(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string
        {
            let responseJson = emptyResponse;
            let matchDetail: MatchMakingDetails | null = null;
            try
            {
                matchDetail = JSON.parse(payload) as MatchMakingDetails;
            } catch (error)
            {
                logger.error(`Failed to deserialize JSON: ${payload}`);
            }
            if (matchDetail == null)
            {
                return responseJson;
            } else
            {
                let mmResponseData = this.waitingMap.GetMMRByRoomId(matchDetail?.roomId);
                if (mmResponseData == null)
                {
                    var matchId = nk.matchCreate(this.GetGameName());
                    mmResponseData = new MatchMakingResponseData(matchDetail?.roomId, matchId, matchDetail.minPlayerCount, matchDetail.maxPlayerCount, null);
                    this.waitingMap.Set(matchDetail?.roomId, matchId, mmResponseData);
                }
                return JSON.stringify(mmResponseData);
            }
        }

       abstract MatchInit(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {
            [p: string]: any
        }): { state: nkruntime.MatchState, tickRate: number, label: string };

        abstract MatchJoinAttempted(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presence: nkruntime.Presence, metadata: {
            [key: string]: any
        }): { state: nkruntime.MatchState, accept: boolean, rejectMessage?: string } | null;

        MatchJoin(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]): {
            state: nkruntime.MatchState } | null
        {
            let mmr=this.waitingMap.GetMMRByMatchId(ctx.matchId);
            if(mmr != null)
            {
                mmr.currentPlayerCount+=1;
            }
            else
            {
                logger.error('Tag::serverBase MatchJoin mmr is null');
            }
            return {state};
        }

        MatchLeave(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]): { state: nkruntime.MatchState } | null
        {
            let mmr=this.waitingMap.GetMMRByMatchId(ctx.matchId);
            if(mmr != null)
            {
                mmr.currentPlayerCount-=1;
            }
            else
            {
                logger.error('Tag::serverBase MatchJoin mmr is null');
            }
            return { state};
        }

        abstract MatchLoop(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]): {
            state: nkruntime.MatchState
        } | null;

        MatchTerminate(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, graceSeconds: number): { state: nkruntime.MatchState } | null
        {
            this.waitingMap.DeletedByMatchId(ctx.matchId);
            logger.warn(`Tag::serverBase MatchTerminate mmr is ${ctx.matchId}`);
            return {state};
        }

        abstract MatchSignal(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string): {
            state: nkruntime.MatchState,
            data?: string
        } | null;

        // matchInit: nkruntime.MatchInitFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {
        //     [p: string]: any
        // }): { state: T, tickRate: number, label: string } =>
        // {
        //     return this.MatchInit(ctx, logger, nk, params);
        // }
        // matchJoinAttempt: nkruntime.MatchJoinAttemptFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, presence: nkruntime.Presence, metadata: {
        //     [key: string]: any
        // }): { state: T, accept: boolean, rejectMessage?: string } | null =>
        // {
        //     return this.MatchJoinAttempted(ctx, logger, nk, dispatcher, tick, state, presence, metadata);
        // }
        // matchJoin: nkruntime.MatchJoinFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, presences: nkruntime.Presence[]) =>
        // {
        //     return this.MatchJoin(ctx, logger, nk, dispatcher, tick, state, presences);
        // }
        // matchLeave: nkruntime.MatchLeaveFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, presences: nkruntime.Presence[]) =>
        // {
        //     return this.MatchLeave(ctx, logger, nk, dispatcher, tick, state, presences);
        // }
        // matchLoop: nkruntime.MatchLoopFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, messages: nkruntime.MatchMessage[]) =>
        // {
        //     return this.MatchLoop(ctx, logger, nk, dispatcher, tick, state, messages);
        // }
        // matchTerminate: nkruntime.MatchTerminateFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, graceSeconds: number) =>
        // {
        //     return this.MatchTerminate(ctx, logger, nk, dispatcher, tick, state, graceSeconds);
        // }
        // matchSignal: nkruntime.MatchSignalFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, data: string) =>
        // {
        //     return this.MatchSignal(ctx, logger, nk, dispatcher, tick, state, data);
        // }
    }
}

//===Dunes.ts====
namespace BetBlack.Dunes
{
    class ScoreData
    {
        public playerId:string;
        public score : number;

        constructor(playerId: string, score: number)
        {
            this.playerId = playerId;
            this.score = score;
        }
    }



    export class DunesGameManager extends BetBlack.BaseGameManager
    {
        GetGameName(): string
        {
            return 'Dunes';
        }

        MatchInit(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {
            [p: string]: any;
        }): { state: nkruntime.MatchState; tickRate: number; label: string; }
        {
            var mmr = this.waitingMap.GetMMRByMatchId(ctx.matchId);

            if (mmr == null)
            {
                let mmd= new MatchMakingDetails('Not_Found',1,1,3600);
                logger.error('Tag::Dunes error MMR is null')
                return {
                    state: {
                        matchData: mmd,
                        scores: Array.from({ length: 1 }, () => new ScoreData("", 0))
                    },
                    tickRate: 60, // 1 tick per second = 1 MatchLoop func invocations per second
                    label: this.GetGameName()
                };
            }
            let mmd= new MatchMakingDetails(mmr.roomId, mmr.minPlayerCount, mmr.maxPlayerCount,3600);
            return {
                state: {
                    matchData: mmd,
                    scores:  Array.from({ length: mmr.maxPlayerCount }, () => new ScoreData("", 0))
                },
                tickRate: 1, // 1 tick per second = 1 MatchLoop func invocations per second
                label: this.GetGameName()
            };
        }

        MatchJoinAttempted(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presence: nkruntime.Presence, metadata: {
            [key: string]: any;
        }): { state: nkruntime.MatchState; accept: boolean; rejectMessage?: string | undefined; } | null
        {
            return {
                state,
                accept: true
            };
        }


        MatchLoop(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]): {
            state: nkruntime.MatchState;
        } | null
        {


            return {
                state
            }
        }

        MatchSignal(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string): {
            state: nkruntime.MatchState;
            data?: string | undefined;
        } | null
        {
            return {
                state
            }
        }

    }
}

:tv: Media:

hi, can anybody guide me on what I am doing wrong in the above code?