@gama-platform/gama-client 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.
@@ -0,0 +1,560 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __export = (target, all) => {
26
+ for (var name in all)
27
+ __defProp(target, name, { get: all[name], enumerable: true });
28
+ };
29
+ var __copyProps = (to, from, except, desc) => {
30
+ if (from && typeof from === "object" || typeof from === "function") {
31
+ for (let key of __getOwnPropNames(from))
32
+ if (!__hasOwnProp.call(to, key) && key !== except)
33
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
34
+ }
35
+ return to;
36
+ };
37
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
38
+ // If the importer is in node compatibility mode or this is not an ESM
39
+ // file that has been converted to a CommonJS file using a Babel-
40
+ // compatible transform (i.e. "__esModule" has not been set), then set
41
+ // "default" to the CommonJS "module.exports" for node compatibility.
42
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
43
+ mod
44
+ ));
45
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
46
+
47
+ // src/gama_client.ts
48
+ var gama_client_exports = {};
49
+ __export(gama_client_exports, {
50
+ default: () => GamaClient
51
+ });
52
+ module.exports = __toCommonJS(gama_client_exports);
53
+ var import_ws = __toESM(require("ws"), 1);
54
+
55
+ // src/constants.ts
56
+ var GAMA_ERROR_MESSAGES = [
57
+ "SimulationStatusError",
58
+ "SimulationErrorDialog",
59
+ "SimulationError",
60
+ "RuntimeError",
61
+ "GamaServerError",
62
+ "MalformedRequest",
63
+ "UnableToExecuteRequest"
64
+ ];
65
+
66
+ // src/gama_client.ts
67
+ var import_logtape = require("@logtape/logtape");
68
+ var logger = (0, import_logtape.getLogger)(["GAMA-library", "GAMA-client"]);
69
+ var GamaClient = class {
70
+ //websocket of the client. needs to be initialized by using the asynchronous connectGama() to be used
71
+ /**
72
+ *
73
+ * @param port port of gama server you want to reach
74
+ * @param host host of the gama server you want to reach
75
+ */
76
+ constructor(port, host) {
77
+ // json object detailing the state of the gama server, including: if connected, experiment details, errors from the server, and loading status
78
+ this.port = 1e3;
79
+ //default port number pointing to the gama server. can be redefined when using the constructor
80
+ this.host = "localhost";
81
+ this.jsonGamaState = {
82
+ connected: false,
83
+ model_path: "",
84
+ experiment_state: "NONE",
85
+ loading: false,
86
+ content_error: "",
87
+ experiment_id: "",
88
+ experiment_name: ""
89
+ };
90
+ this.port = port || 1e3;
91
+ this.host = host || "localhost";
92
+ }
93
+ //? GETTERS
94
+ isConnected() {
95
+ return this.jsonGamaState.connected;
96
+ }
97
+ getExperimentState() {
98
+ return this.jsonGamaState.experiment_state;
99
+ }
100
+ isLoading() {
101
+ return this.jsonGamaState.loading;
102
+ }
103
+ getContentError() {
104
+ return this.jsonGamaState.content_error;
105
+ }
106
+ getExperimentId() {
107
+ return this.jsonGamaState.experiment_id;
108
+ }
109
+ getModelPath() {
110
+ return this.jsonGamaState.model_path;
111
+ }
112
+ getExperimentName() {
113
+ return this.jsonGamaState.experiment_name;
114
+ }
115
+ getReadyState() {
116
+ return this.gama_socket.readyState;
117
+ }
118
+ getPort() {
119
+ return this.port;
120
+ }
121
+ getHost() {
122
+ return this.host;
123
+ }
124
+ getSocket() {
125
+ return this.gama_socket;
126
+ }
127
+ //? SETTERS --------------------------------------------------------------------------------------------------------------------------------------
128
+ setConnected(connected) {
129
+ this.jsonGamaState.connected = connected;
130
+ }
131
+ setExperimentState(state) {
132
+ this.jsonGamaState.experiment_state = state;
133
+ }
134
+ setLoading(loading) {
135
+ this.jsonGamaState.loading = loading;
136
+ }
137
+ setContentError(content_error) {
138
+ this.jsonGamaState.content_error = content_error;
139
+ }
140
+ setExperimentId(experiment_id) {
141
+ this.jsonGamaState.experiment_id = experiment_id;
142
+ }
143
+ setExperimentName(experiment_name) {
144
+ this.jsonGamaState.experiment_name = experiment_name;
145
+ }
146
+ setModelPath(model_path) {
147
+ this.jsonGamaState.model_path = model_path;
148
+ }
149
+ //? INTERNAL UTILITIES ---------------------------------------------------------------------------------------------------------------------------
150
+ /**
151
+ * internal function to avoid unecessary boilerplate code,
152
+ * checks if gamasocket exists, and if it's ready to accept a new message
153
+ */
154
+ socketCheck() {
155
+ if (!this.gama_socket) {
156
+ throw new Error("No socket connected to GAMA Server found");
157
+ } else if (!this.jsonGamaState.connected) {
158
+ throw new Error("Gama is not connected");
159
+ } else if (!(this.getReadyState() === import_ws.default.OPEN || this.getReadyState() === import_ws.default.CONNECTING)) {
160
+ throw new Error("socket not in the OPEN state");
161
+ } else {
162
+ logger.trace("Websocket is connected and open");
163
+ this.setConnected(true);
164
+ }
165
+ }
166
+ /**
167
+ * internal function that contains a simple try catch and stringifies a json payload to send it to the websocket
168
+ * @param payload json payload to be sent
169
+ */
170
+ sendPayload(payload) {
171
+ try {
172
+ this.gama_socket.send(JSON.stringify(payload));
173
+ logger.debug("sent message to websocket:{payload}", { payload });
174
+ } catch (error) {
175
+ throw new Error(`couldn't send the message to the websocket:${error}`);
176
+ }
177
+ }
178
+ /**
179
+ * internal function that returns the string of an experiment to run
180
+ * it represents the last used experiment or the new one if any specified
181
+ * @param new_exp_id id of the experiment passed by the user in parameter. used by default, sets the current experience to itself
182
+ * @returns the string of the Id of the last used experiment. Used if no new_exp_id is given
183
+ */
184
+ getId(new_exp_id) {
185
+ if (new_exp_id) {
186
+ this.setExperimentId(new_exp_id);
187
+ return new_exp_id;
188
+ } else {
189
+ if (this.getExperimentId() === "") throw new Error("no current experiment to be called");
190
+ return this.getExperimentId();
191
+ }
192
+ }
193
+ /**
194
+ * async function that closes the websocket connection, and runs the callback function passed in parameter if any.
195
+ * When called, creates a promise that either rejects after 15 seconds to avoid timeout lockdowns,
196
+ * or resolves after the close internalListener fires.
197
+ * @param optional callback Function to be called after the websocket's connection is closed
198
+ */
199
+ async closeConnection(callback) {
200
+ if (!this.gama_socket || this.getReadyState() === import_ws.default.CLOSED) {
201
+ logger.warn("Websocket already closed, running the callback function");
202
+ if (callback) callback();
203
+ return;
204
+ }
205
+ if (this.getReadyState() === import_ws.default.OPEN || this.getReadyState() === import_ws.default.CONNECTING) {
206
+ await new Promise((resolve, reject) => {
207
+ const timer = setTimeout(() => {
208
+ this.gama_socket.removeEventListener("close", internalListener);
209
+ reject(new Error("Websocket timed out"));
210
+ }, 15e3);
211
+ const internalListener = () => {
212
+ clearTimeout(timer);
213
+ this.gama_socket.removeEventListener("close", internalListener);
214
+ resolve();
215
+ };
216
+ this.gama_socket.addEventListener("close", internalListener);
217
+ this.gama_socket.close();
218
+ });
219
+ if (callback) callback();
220
+ }
221
+ }
222
+ /**
223
+ * Connects the websocket client with gama server and manage the messages received
224
+ * this function is asynchronous, it needs to be called with await. This is because
225
+ * other functions need the websocket to be created and in the state "OPEN" to start
226
+ * sending messages, which is not done when the function has finished it's execution
227
+ * @returns WebSocket properly initialised at the end of the asynchronous execution
228
+ */
229
+ async connectGama() {
230
+ return new Promise((resolve, reject) => {
231
+ if (this.gama_socket && (this.getReadyState() === import_ws.default.OPEN || this.getReadyState() === import_ws.default.CONNECTING)) {
232
+ this.setConnected(true);
233
+ logger.info("Already connected or connecting. Skipping. status:{status}", { status: this.getReadyState() });
234
+ return resolve();
235
+ }
236
+ try {
237
+ this.gama_socket = new import_ws.default(`ws://${this.host}:${this.port}`);
238
+ this.gama_socket.onopen = () => {
239
+ this.setConnected(true);
240
+ logger.info("created new connection to {host}:{port}", { host: this.host, port: this.port });
241
+ this.gama_socket.onclose = () => {
242
+ this.setConnected(false);
243
+ this.setExperimentState("NONE");
244
+ logger.info("successfully closed the websocket.");
245
+ };
246
+ const simulationStatus = (event) => {
247
+ const message = JSON.parse(event.data);
248
+ if (message.type === "SimulationStatus") {
249
+ this.setExperimentState(message.content);
250
+ this.setExperimentId(message.exp_id);
251
+ }
252
+ logger.info("JsonGamaState:{state}", { state: this.getExperimentName() });
253
+ };
254
+ this.gama_socket.addEventListener("message", simulationStatus);
255
+ return resolve();
256
+ };
257
+ this.gama_socket.onerror = (error) => {
258
+ this.setConnected(false);
259
+ if (error.error.code == "ECONNREFUSED") {
260
+ logger.trace(`full stack trace for Error CONNREFUSED {error}`, { error });
261
+ logger.error("The platform can't connect to GAMA at address {host}:{port}", { host: this.host, port: this.port });
262
+ reject(new Error(`Failed to connect to GAMA at ${this.host}:${this.port} with error code ECONNREFUSED`));
263
+ } else {
264
+ logger.error(`An error happened within the Gama Server WebSocket
265
+ {error}`, { error });
266
+ reject(new Error(`Failed to connect to GAMA at ${this.host}:${this.port}`));
267
+ }
268
+ };
269
+ } catch (e) {
270
+ const err = e;
271
+ logger.error("Synchronous error when creating the websocket:{error}", { error: err.message });
272
+ reject(e);
273
+ }
274
+ });
275
+ }
276
+ /**
277
+ * This function is used to watch on messages stream and look for a response to the command initiated.
278
+ * it resolves if the message received is of the same type specified in the parameter
279
+ * and throws an error if it's of any type specified in the GAMA_ERROR_MESSAGES specified in the constants file
280
+ * @returns returns a promise containing the response's message's content
281
+ */
282
+ async success(successMessage) {
283
+ return new Promise((resolve, reject) => {
284
+ const onMessage = (event) => {
285
+ const message = JSON.parse(event.data);
286
+ const type = message.type;
287
+ if (type === successMessage) {
288
+ this.gama_socket.removeEventListener("message", onMessage);
289
+ resolve(message);
290
+ } else if (GAMA_ERROR_MESSAGES.includes(type)) {
291
+ this.gama_socket.removeEventListener("message", onMessage);
292
+ this.setContentError(message.content);
293
+ reject(`Couldn't execute command on the Gama Server. ${type}: ${JSON.stringify(message.content)}`);
294
+ }
295
+ };
296
+ this.gama_socket.addEventListener("message", onMessage);
297
+ });
298
+ }
299
+ /**
300
+ * function used to check for a specific message on the websocket.
301
+ * returns a resolved boolean promise once the provided message is found
302
+ * @param messageType the basic type of the message you want to analyse
303
+ * @param field what part of the message to analyse
304
+ * @param expectedValue what you expect the field value to be
305
+ * @returns a resolved promise containing a boolean
306
+ */
307
+ //Voir pour retourner le message au lieu de juste retourner un booléen ?
308
+ async listenFor(messageType, field, expectedValue) {
309
+ if (!this.gama_socket) {
310
+ throw new Error("couldn't find an active gama socket when creating a listener:");
311
+ }
312
+ return new Promise((resolve, reject) => {
313
+ const listener = (event) => {
314
+ const message = JSON.parse(event.data);
315
+ logger.debug("message: {message}", { message });
316
+ const type = message.type;
317
+ if (type === messageType && message[field] === expectedValue) {
318
+ clearTimeout(timer);
319
+ resolve(true);
320
+ this.gama_socket.removeEventListener("message", listener);
321
+ }
322
+ };
323
+ const timer = setTimeout(() => {
324
+ this.gama_socket.removeEventListener("message", listener);
325
+ reject(new Error("Websocket timed out"));
326
+ }, 15e3);
327
+ this.gama_socket.addEventListener("message", listener);
328
+ logger.debug("added an event listener to the gama_socket");
329
+ });
330
+ }
331
+ async readyCheck() {
332
+ const isReady = new Promise(async (resolve, reject) => {
333
+ if (this.getExperimentState() === "PAUSED" || "RUNNING") {
334
+ resolve(true);
335
+ } else {
336
+ return await this.listenFor("SimulationStatus", "content", "PAUSED");
337
+ }
338
+ });
339
+ }
340
+ //? GAMA FUNCTIONS ---------------------------------------------------------------------------------------------------------------------------
341
+ /**
342
+ * loads and launches an experiment using the absolute path of it's model and
343
+ * the identifier of the experiment. Resolves when the server answers with a SimulationStatus of type "PAUSED"
344
+ * @param model_path absolute path pointing to the model cointaining the experiment
345
+ * @param experiment id of the experiment to load
346
+ */
347
+ async loadExperiment(model_path, experiment) {
348
+ this.socketCheck();
349
+ const payload = {
350
+ "type": "load",
351
+ "model": model_path,
352
+ "experiment": experiment
353
+ };
354
+ this.sendPayload(payload);
355
+ this.setModelPath(model_path);
356
+ this.setExperimentName(experiment);
357
+ this.setExperimentId(experiment);
358
+ await this.success("CommandExecutedSuccessfully");
359
+ return await this.readyCheck();
360
+ }
361
+ /**
362
+ * Starts or resumes the experiment specified.
363
+ * @param exp_id string name of the experiment to pause or resume
364
+ * @param sync boolean used if an end condition was specified when loading a simulation. the command will return only the SimulationEnded message if true, and both a response and a SimulationEnded message if false
365
+ * when starting the experiment
366
+ */
367
+ async play(exp_id, sync) {
368
+ this.socketCheck();
369
+ if (this.getExperimentState() === "NOTREADY") {
370
+ logger.warn("Simulation not ready yet, waiting for PAUSED simulationstatus");
371
+ await this.listenFor("Simulationstatus", "content", "PAUSED");
372
+ } else if (this.getExperimentState() === "PAUSED") {
373
+ const payload = __spreadValues({
374
+ "type": "play",
375
+ "exp_id": this.getId()
376
+ }, sync && { "sync": sync });
377
+ this.sendPayload(payload);
378
+ return await this.success("CommandExecutedSuccessfully");
379
+ } else if (this.getExperimentState() === "RUNNING") {
380
+ logger.warn("cannot unpause a running simulation");
381
+ }
382
+ }
383
+ /**
384
+ * Pauses the experiment specified.
385
+ * @param exp_id optionnal parameter, will default to last used experiment
386
+ */
387
+ async pause(exp_id) {
388
+ this.socketCheck();
389
+ const payload = {
390
+ "type": "pause",
391
+ "exp_id": this.getId(exp_id)
392
+ };
393
+ this.sendPayload(payload);
394
+ return await this.success("CommandExecutedSuccessfully");
395
+ }
396
+ async reload(exp_id, parameters, until) {
397
+ this.socketCheck();
398
+ if (this.getExperimentState() === "NOTREADY") {
399
+ await this.listenFor("Simulationstatus", "content", "PAUSED");
400
+ }
401
+ const payload = __spreadValues(__spreadValues({
402
+ "type": "reload",
403
+ "exp_id": this.getId(exp_id)
404
+ }, parameters && { "parameters": parameters }), until && { "until": until });
405
+ this.sendPayload(payload);
406
+ return await this.success("CommandExecutedSuccessfully");
407
+ }
408
+ /**
409
+ * Sends a message to gama to order it to process a specified number of steps.
410
+ * Can only be used after the simulation has already been loaded
411
+ * @param exp_id the name of the experiment you want to step to. if not used, then the last used experiment Id will be used
412
+ * @param nb_step the number of steps you want to simulate. if none is specified, it will default to one step
413
+ */
414
+ async step(nb_step, sync, exp_id) {
415
+ this.socketCheck();
416
+ if (this.getExperimentState() === "NOTREADY") {
417
+ logger.warn("The experiment is not yet ready:{state}", { state: this.getExperimentState() });
418
+ await this.listenFor("Simulationstatus", "content", "PAUSED");
419
+ }
420
+ const exp_id_payload = exp_id ? exp_id : this.getExperimentId();
421
+ if (exp_id_payload === "") throw new Error("no experience_id specified, and no experiment in the jsongamastate");
422
+ const payload = __spreadProps(__spreadValues({
423
+ "type": "step",
424
+ "exp_id": exp_id_payload
425
+ }, nb_step && { "nb_step": nb_step }), {
426
+ "sync": true
427
+ });
428
+ this.sendPayload(payload);
429
+ return await this.success("CommandExecutedSuccessfully");
430
+ }
431
+ /**
432
+ * ! you must be sure that the type of the experiment is compatible (record) before using this
433
+ * This command is used to rollback a specific amount of steps.
434
+ * Can only be used if the experiment is of type "record"
435
+ * @param exp_id the name of the experiment you want to step to. if not used, then the last used experiment Id will be used
436
+ * @param nb_step the number of steps you want to simulate. if none is specified, it will default to one step
437
+ */
438
+ async stepback(nb_step, exp_id) {
439
+ this.socketCheck();
440
+ const payload = __spreadProps(__spreadValues({
441
+ "type": "stepBack",
442
+ "exp_id": this.getId(exp_id)
443
+ }, nb_step && { "nb_step": nb_step }), {
444
+ "sync": true
445
+ });
446
+ this.sendPayload(payload);
447
+ return await this.success("CommandExecutedSuccessfully");
448
+ }
449
+ /**
450
+ * stops the specified experiment or the current experiment if not specified
451
+ * @param exp_id optionnal parameter, leave empty to use the last used exp_id
452
+ */
453
+ async stop(exp_id) {
454
+ this.socketCheck();
455
+ if (this.getExperimentState() !== "NONE") {
456
+ try {
457
+ const payload = {
458
+ "type": "stop",
459
+ "exp_id": this.getId(exp_id)
460
+ };
461
+ this.sendPayload(payload);
462
+ return await this.success("CommandExecutedSuccessfully");
463
+ } catch (error) {
464
+ throw new Error(`couldn't stop the experiment:${error}`);
465
+ }
466
+ } else {
467
+ logger.warn(`couldn't stop the experiment, no experiment running`);
468
+ return new Promise((resolve) => {
469
+ resolve("couldn't stop experiment");
470
+ });
471
+ }
472
+ }
473
+ /**
474
+ * used to specify a fonction to be called on any message received by the websocket from the gama server
475
+ * you can only have one onMessage per client.
476
+ * @param callback the function you want to call upon receiving data through the javascript client
477
+ */
478
+ onMessage(callback) {
479
+ if (!this.gama_socket) {
480
+ throw new Error("WebSocket is not initialized");
481
+ }
482
+ this.gama_socket.on("message", (data) => {
483
+ try {
484
+ const parsed = JSON.parse(data.toString());
485
+ callback(parsed);
486
+ } catch (err) {
487
+ logger.warn("Received non-JSON message:{data}", { data });
488
+ callback(data);
489
+ }
490
+ });
491
+ }
492
+ /**
493
+ * kills the gama server.
494
+ * used to exit the gama server, closes the websocket connection and closes the gama instance
495
+ */
496
+ async killGamaServer() {
497
+ this.socketCheck();
498
+ const payload = { "type": "exit" };
499
+ this.sendPayload(payload);
500
+ }
501
+ /**
502
+ * used to run execute an action defined in an agent in an experiment.
503
+ * @param action gaml code to be run from an agent
504
+ * @param args arguments of the action
505
+ * @param agent what agent this code applies to
506
+ * @param escaped optional parameter, if true will escape the action and args before sending them to gama
507
+ * @param exp_id optionnal parameter to specify the experiment. if none is given it will instead default to the last used experiment
508
+ * @returns a stringified response containing the result of the execution of the command
509
+ */
510
+ async ask(action, args, agent, escaped, exp_id) {
511
+ this.socketCheck();
512
+ var payload = __spreadValues({
513
+ "type": "ask",
514
+ "exp_id": this.getId(exp_id),
515
+ "action": action,
516
+ "args": args,
517
+ "agent": agent
518
+ }, escaped && { "escaped": escaped });
519
+ this.sendPayload(payload);
520
+ return await this.success("CommandExecutedSuccessfully");
521
+ }
522
+ /**
523
+ * Compiles the code given in parameter and returns a message if any errors are detected.
524
+ * @param expr gaml expression to test
525
+ * @param syntax optionnal boolean, if true will only check the syntax. false will check for both syntactical and semantic errors
526
+ * @param escaped optionnal boolean, dictates if the expression is escaped or not
527
+ * @returns stringified json containing errors in the code if any
528
+ */
529
+ async validate(expr, syntax, escaped) {
530
+ this.socketCheck();
531
+ const payload = __spreadValues(__spreadValues({
532
+ "type": "validate",
533
+ "expr": expr
534
+ }, syntax && { "syntax": syntax }), escaped && { "escaped": escaped });
535
+ this.sendPayload(payload);
536
+ try {
537
+ return await this.success("CommandExecutedSuccessfully");
538
+ } catch (err) {
539
+ throw err;
540
+ }
541
+ }
542
+ /**
543
+ * This command is used to ask the server more information on a given model. When received,
544
+ * the server will compile the model and return the different components found, depending on the option picked by the client.
545
+ * @param model_path path to the model to evaluate
546
+ * @param experimentsNames optional boolean that returns the name of all the experiments of the model
547
+ * @param speciesNames optional boolean that returns all of the species' names
548
+ * @param speciesVariables optional boolean that returns all variables of the species
549
+ * @param speciesActions optional boolean that returns all actions in the species
550
+ */
551
+ async describe(model_path, experimentsNames, speciesNames, speciesVariables, speciesActions) {
552
+ this.socketCheck();
553
+ const payload = __spreadValues(__spreadValues(__spreadValues(__spreadValues({
554
+ "type": "describe",
555
+ "model": model_path
556
+ }, experimentsNames && { "experiments": experimentsNames }), speciesNames && { "speciesNames": speciesNames }), speciesActions && { "speciesActions": speciesActions }), speciesVariables && { "speciesVariables": speciesVariables });
557
+ this.sendPayload(payload);
558
+ return await this.success("CommandExecutedSuccessfully");
559
+ }
560
+ };