scoundrel-remote-eval 1.0.9 → 1.0.10

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.
@@ -22,9 +22,9 @@ export default class WebSocket {
22
22
  */
23
23
  onSocketMessage: (event: MessageEvent) => void;
24
24
  /**
25
- * @param {Event} _event
25
+ * @param {Event} event
26
26
  */
27
- onSocketOpen: (_event: Event) => void;
27
+ onSocketOpen: (event: Event) => void;
28
28
  /**
29
29
  * @param {Record<string, any>} data
30
30
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/client/connections/web-socket/index.js"],"names":[],"mappings":"AAQA;IACE;;;OAGG;IACH,gBAFW,SAAS,EAgBnB;IAbC,cAAY;IAWZ,aAAkB;IAClB,sBAAsB;IAGxB,uBAEC;IAED;;OAEG;IACH,oBAFW,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,QAI7B;IADC,0BAHgB,GAAG,KAAK,IAAI,CAGK;IAGnC;;OAEG;IACH,gBAAiB,OAFN,KAEW,UAErB;IAED;;OAEG;IACH,kBAAmB,OAFR,YAEa,UAUvB;IAED;;OAEG;IACH,eAAgB,QAFL,KAEW,UAErB;IAED;;OAEG;IACH,WAFW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAQ7B;IAED,kCAME;CACH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/client/connections/web-socket/index.js"],"names":[],"mappings":"AAQA;IACE;;;OAGG;IACH,gBAFW,SAAS,EAgBnB;IAbC,cAAY;IAWZ,aAAkB;IAClB,sBAAsB;IAGxB,uBAEC;IAED;;OAEG;IACH,oBAFW,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,QAI7B;IADC,0BAHgB,GAAG,KAAK,IAAI,CAGK;IAGnC;;OAEG;IACH,gBAAiB,OAFN,KAEW,UAErB;IAED;;OAEG;IACH,kBAAmB,OAFR,YAEa,UAUvB;IAED;;OAEG;IACH,eAAgB,OAFL,KAEU,UAEpB;IAED;;OAEG;IACH,WAFW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAQ7B;IAED,kCAME;CACH"}
@@ -26,10 +26,10 @@ export default class WebSocket {
26
26
  this.onCommandCallback(data);
27
27
  };
28
28
  /**
29
- * @param {Event} _event
29
+ * @param {Event} event
30
30
  */
31
- this.onSocketOpen = (_event) => {
32
- logger.log("onSocketOpen");
31
+ this.onSocketOpen = (event) => {
32
+ logger.log(() => ["onSocketOpen", event]);
33
33
  };
34
34
  this.waitForOpened = () => new Promise((resolve, reject) => {
35
35
  // @ts-ignore
@@ -3,8 +3,11 @@ export default class Client {
3
3
  * Creates a new Scoundrel Client
4
4
  *
5
5
  * @param {any} backend The backend connection (e.g., WebSocket)
6
+ * @param {{enableServerControl?: boolean}} [options]
6
7
  */
7
- constructor(backend: any);
8
+ constructor(backend: any, options?: {
9
+ enableServerControl?: boolean;
10
+ });
8
11
  backend: any;
9
12
  /** @type {Record<number, any>} */
10
13
  outgoingCommands: Record<number, any>;
@@ -19,6 +22,8 @@ export default class Client {
19
22
  /** @type {Record<number, any>} */
20
23
  objects: Record<number, any>;
21
24
  objectsCount: number;
25
+ /** @type {boolean} */
26
+ serverControlEnabled: boolean;
22
27
  /**
23
28
  * Closes the client connection
24
29
  */
@@ -175,6 +180,7 @@ export default class Client {
175
180
  * @returns {Reference}
176
181
  */
177
182
  spawnReference(id: string): Reference;
183
+ enableServerControl(): void;
178
184
  }
179
185
  import Reference from "./reference.js";
180
186
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.js"],"names":[],"mappings":"AASA;IACE;;;;OAIG;IACH,qBAFW,GAAG,EAwBb;IArBC,aAAsB;IAGtB,kCAAkC;IAClC,kBADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACJ;IAC1B,qBAA0B;IAC1B,8BAA8B;IAE9B,kCAAkC;IAClC,UADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACZ;IAElB,kCAAkC;IAClC,UADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACZ;IAElB,wCAAwC;IACxC,YADW,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAChB;IAEpB,kCAAkC;IAClC,SADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACb;IAEjB,qBAAqB;IAGvB;;OAEG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,mCALW,MAAM,cACN,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,GAAG,CAAC,CAWxB;IAED;;;;;;;OAOG;IACH,gDALW,MAAM,cACN,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,SAAS,CAAC,CAY9B;IAED;;;;;OAKG;IACH,8BAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAU9B;IAED;;;;;OAKG;IACH,mBAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAe9B;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAY9B;IAED;;;;;;OAMG;IACH,kCAJW,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,SAAS,CAAC,CAe9B;IAED;;;;OAIG;IACH,qBAHW,GAAG,GACD,OAAO,CAQnB;IAED;;;;;;;;OAQG;IACH,YAAa,0EANV;QAAqB,OAAO,EAApB,MAAM;QACO,UAAU,EAAvB,MAAM;QACI,IAAI,EAAd,GAAG;QACW,KAAK,GAAnB,MAAM;QACQ,UAAU,GAAxB,MAAM;KAEkE,UA+GlF;IAED;;;;;OAKG;IACH,cAHW,GAAG,GACD,GAAG,CAwBf;IAED;;;;;;OAMG;IACH,mDAJW,MAAM,iBACN,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAW9B;IAED;;;;;;OAMG;IACH,sCAJW,MAAM,iBACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CASxB;IAED;;;;;OAKG;IACH,yBAHW,MAAM,iBACN,GAAG,QAMb;IAED;;;;;OAKG;IACH,oBAHW,MAAM,GACJ,GAAG,CAIf;IAED;;;;;OAKG;IACH,2BAHW,MAAM,kBACN,GAAG,QAMb;IAED;;;;;OAKG;IACH,iCAHW,MAAM,GACJ,GAAG,CAIf;IAED;;;;OAIG;IACH,4BAHW,MAAM,QACN,GAAG,QAIb;IAED;;;;;OAKG;IACH,qBAJW,MAAM,QACN,GAAG,GACD,OAAO,CAAC,GAAG,CAAC,CAexB;IAED;;;OAGG;IACH,WAFW,GAAG,QAIb;IAED;;;;;OAKG;IACH,gCAHW,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CAMxB;IAED;;;;;OAKG;IACH,mBAHW,MAAM,GACJ,SAAS,CAQrB;CACF;sBAjdqB,gBAAgB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.js"],"names":[],"mappings":"AASA;IACE;;;;;OAKG;IACH,qBAHW,GAAG,YACH;QAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAAC,EA2BzC;IAxBC,aAAsB;IAGtB,kCAAkC;IAClC,kBADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACJ;IAC1B,qBAA0B;IAC1B,8BAA8B;IAE9B,kCAAkC;IAClC,UADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACZ;IAElB,kCAAkC;IAClC,UADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACZ;IAElB,wCAAwC;IACxC,YADW,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAChB;IAEpB,kCAAkC;IAClC,SADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACb;IAEjB,qBAAqB;IAErB,sBAAsB;IACtB,sBADW,OAAO,CAC8C;IAGlE;;OAEG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,mCALW,MAAM,cACN,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,GAAG,CAAC,CAWxB;IAED;;;;;;;OAOG;IACH,gDALW,MAAM,cACN,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,SAAS,CAAC,CAY9B;IAED;;;;;OAKG;IACH,8BAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAM9B;IAED;;;;;OAKG;IACH,mBAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAe9B;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAY9B;IAED;;;;;;OAMG;IACH,kCAJW,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,SAAS,CAAC,CAe9B;IAED;;;;OAIG;IACH,qBAHW,GAAG,GACD,OAAO,CAQnB;IAED;;;;;;;;OAQG;IACH,YAAa,0EANV;QAAqB,OAAO,EAApB,MAAM;QACO,UAAU,EAAvB,MAAM;QACI,IAAI,EAAd,GAAG;QACW,KAAK,GAAnB,MAAM;QACQ,UAAU,GAAxB,MAAM;KAEkE,UAkJlF;IAED;;;;;OAKG;IACH,cAHW,GAAG,GACD,GAAG,CAwBf;IAED;;;;;;OAMG;IACH,mDAJW,MAAM,iBACN,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAW9B;IAED;;;;;;OAMG;IACH,sCAJW,MAAM,iBACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CASxB;IAED;;;;;OAKG;IACH,yBAHW,MAAM,iBACN,GAAG,QAMb;IAED;;;;;OAKG;IACH,oBAHW,MAAM,GACJ,GAAG,CAIf;IAED;;;;;OAKG;IACH,2BAHW,MAAM,kBACN,GAAG,QAMb;IAED;;;;;OAKG;IACH,iCAHW,MAAM,GACJ,GAAG,CAIf;IAED;;;;OAIG;IACH,4BAHW,MAAM,QACN,GAAG,QAIb;IAED;;;;;OAKG;IACH,qBAJW,MAAM,QACN,GAAG,GACD,OAAO,CAAC,GAAG,CAAC,CAexB;IAED;;;OAGG;IACH,WAFW,GAAG,QAIb;IAED;;;;;OAKG;IACH,gCAHW,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CAMxB;IAED;;;;;OAKG;IACH,mBAHW,MAAM,GACJ,SAAS,CAQrB;IAED,4BAEC;CACF;sBAxfqB,gBAAgB"}
@@ -8,8 +8,9 @@ export default class Client {
8
8
  * Creates a new Scoundrel Client
9
9
  *
10
10
  * @param {any} backend The backend connection (e.g., WebSocket)
11
+ * @param {{enableServerControl?: boolean}} [options]
11
12
  */
12
- constructor(backend) {
13
+ constructor(backend, options = {}) {
13
14
  /**
14
15
  * Handles an incoming command from the backend
15
16
  * @param {object} args
@@ -25,6 +26,28 @@ export default class Client {
25
26
  if (!command) {
26
27
  throw new Error(`No command key given in data: ${Object.keys(restArgs).join(", ")}`);
27
28
  }
29
+ else if (command == "command_response") {
30
+ if (!(commandID in this.outgoingCommands)) {
31
+ throw new Error(`Outgoing command ${commandID} not found: ${Object.keys(this.outgoingCommands).join(", ")}`);
32
+ }
33
+ const savedCommand = this.outgoingCommands[commandID];
34
+ delete this.outgoingCommands[commandID];
35
+ if (error) {
36
+ const errorToThrow = new Error(error);
37
+ if (errorStack) {
38
+ errorToThrow.stack = `${errorStack}\n\n${errorToThrow.stack}`;
39
+ }
40
+ savedCommand.reject(errorToThrow);
41
+ }
42
+ else {
43
+ logger.log(() => [`Resolving command ${commandID} with data`, data]);
44
+ savedCommand.resolve(data.data);
45
+ }
46
+ }
47
+ else if (!this.serverControlEnabled) {
48
+ this.send({ command: "command_response", command_id: commandID, error: "Server control is disabled" });
49
+ return;
50
+ }
28
51
  else if (command == "get_object") {
29
52
  const serverObject = this._getRegisteredObject(data.object_name);
30
53
  let object;
@@ -65,7 +88,14 @@ export default class Client {
65
88
  if (!method)
66
89
  throw new Error(`No method called '${data.method_name}' on a '${object.constructor.name}'`);
67
90
  const response = method.call(object, ...data.args);
68
- this.respondToCommand(commandID, { response });
91
+ if (data.with == "reference") {
92
+ const objectId = ++this.objectsCount;
93
+ this.objects[objectId] = response;
94
+ this.respondToCommand(commandID, { response: objectId });
95
+ }
96
+ else {
97
+ this.respondToCommand(commandID, { response });
98
+ }
69
99
  }
70
100
  else if (command == "serialize_reference") {
71
101
  const referenceId = data.reference_id;
@@ -91,22 +121,30 @@ export default class Client {
91
121
  this.respondToCommand(commandID, { response: attribute });
92
122
  }
93
123
  }
94
- else if (command == "command_response") {
95
- if (!(commandID in this.outgoingCommands)) {
96
- throw new Error(`Outgoing command ${commandID} not found: ${Object.keys(this.outgoingCommands).join(", ")}`);
97
- }
98
- const savedCommand = this.outgoingCommands[commandID];
99
- delete this.outgoingCommands[commandID];
100
- if (error) {
101
- const errorToThrow = new Error(error);
102
- if (errorStack) {
103
- errorToThrow.stack = `${errorStack}\n\n${errorToThrow.stack}`;
124
+ else if (command == "eval") {
125
+ const respondWithResult = (evalResult) => {
126
+ if (data.with_reference) {
127
+ const objectId = ++this.objectsCount;
128
+ this.objects[objectId] = evalResult;
129
+ this.respondToCommand(commandID, { object_id: objectId });
104
130
  }
105
- savedCommand.reject(errorToThrow);
131
+ else {
132
+ this.respondToCommand(commandID, { response: evalResult });
133
+ }
134
+ };
135
+ const evalResult = eval(data.eval_string);
136
+ if (evalResult && typeof evalResult.then == "function") {
137
+ evalResult.then(respondWithResult).catch((promiseError) => {
138
+ if (promiseError instanceof Error) {
139
+ this.send({ command: "command_response", command_id: commandID, error: promiseError.message, errorStack: promiseError.stack });
140
+ }
141
+ else {
142
+ this.send({ command: "command_response", command_id: commandID, error: String(promiseError) });
143
+ }
144
+ });
106
145
  }
107
146
  else {
108
- logger.log(() => [`Resolving command ${commandID} with data`, data]);
109
- savedCommand.resolve(data.data);
147
+ respondWithResult(evalResult);
110
148
  }
111
149
  }
112
150
  else {
@@ -138,6 +176,8 @@ export default class Client {
138
176
  /** @type {Record<number, any>} */
139
177
  this.objects = {};
140
178
  this.objectsCount = 0;
179
+ /** @type {boolean} */
180
+ this.serverControlEnabled = Boolean(options.enableServerControl);
141
181
  }
142
182
  /**
143
183
  * Closes the client connection
@@ -187,12 +227,8 @@ export default class Client {
187
227
  * @returns {Promise<Reference>}
188
228
  */
189
229
  async evalWithReference(evalString) {
190
- const result = await this.sendCommand("eval", {
191
- eval_string: evalString,
192
- with_reference: true
193
- });
194
- const id = result.object_id;
195
- return this.spawnReference(id);
230
+ const evalReference = await this.getObject("eval");
231
+ return await evalReference.callMethodWithReference("call", null, evalString);
196
232
  }
197
233
  /**
198
234
  * Imports a module and returns a reference to it
@@ -410,4 +446,7 @@ export default class Client {
410
446
  this.references[id] = reference;
411
447
  return reference;
412
448
  }
449
+ enableServerControl() {
450
+ this.serverControlEnabled = true;
451
+ }
413
452
  }
@@ -1 +1 @@
1
- {"version":3,"file":"reference-proxy.d.ts","sourceRoot":"","sources":["../../src/client/reference-proxy.js"],"names":[],"mappings":";AA8CA;;;GAGG;AACH,+CAHW,GAAG,oBAGwE"}
1
+ {"version":3,"file":"reference-proxy.d.ts","sourceRoot":"","sources":["../../src/client/reference-proxy.js"],"names":[],"mappings":";AAyCA;;;GAGG;AACH,+CAHW,GAAG,oBAGwE"}
@@ -27,14 +27,10 @@ const proxyObjectHandler = {
27
27
  * @param {any} newValue
28
28
  */
29
29
  set(receiver, prop, newValue) {
30
+ void receiver;
31
+ void prop;
32
+ void newValue;
30
33
  throw new Error("set property isn't supported yet");
31
- // @ts-expect-error
32
- if (typeof receiver == "function")
33
- receiver = receiver(); // eslint-disable-line no-unreachable
34
- // @ts-expect-error
35
- if (!(prop in receiver))
36
- throw new PropertyNotFoundError(`Property not found: ${prop}`); // eslint-disable-line no-undef
37
- return Reflect.set(receiver, prop, newValue);
38
34
  }
39
35
  };
40
36
  /**
@@ -8,13 +8,20 @@ export default class ScoundrelServer {
8
8
  /** @type {Client[]} */
9
9
  clients: Client[];
10
10
  events: EventEmitter<any>;
11
+ /** @type {SingleEventEmitter<(client: Client) => void>} */
12
+ onNewClientEventEmitter: SingleEventEmitter<(client: Client) => void>;
13
+ onNewClient: (listener: (client: Client) => void) => void;
14
+ /** @returns {void} */
11
15
  close(): void;
16
+ /** @returns {Client[]} */
12
17
  getClients(): Client[];
13
18
  /**
14
19
  * @param {import("./connections/web-socket/client.js").default} clientBackend
20
+ * @returns {void}
15
21
  */
16
- onNewClient: (clientBackend: import("./connections/web-socket/client.js").default) => void;
22
+ onNewClientFromBackend: (clientBackend: import("./connections/web-socket/client.js").default) => void;
17
23
  }
18
24
  import Client from "../client/index.js";
19
25
  import EventEmitter from "events";
26
+ import SingleEventEmitter from "../utils/single-event-emitter.js";
20
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.js"],"names":[],"mappings":"AAKA;IACE;;;OAGG;IACH,qBAFW,OAAO,mCAAmC,EAAE,OAAO,EAU7D;IAPC,6DAAsB;IAGtB,uBAAuB;IACvB,SADW,MAAM,EAAE,CACF;IAEjB,0BAAgC;IAGlC,cAAgC;IAChC,uBAAoC;IAEpC;;OAEG;IACH,cAAe,eAFJ,OAAO,oCAAoC,EAAE,OAE5B,UAK3B;CACF;mBA9BkB,oBAAoB;yBACd,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.js"],"names":[],"mappings":"AAMA;IACE;;;OAGG;IACH,qBAFW,OAAO,mCAAmC,EAAE,OAAO,EAc7D;IAXC,6DAAsB;IAGtB,uBAAuB;IACvB,SADW,MAAM,EAAE,CACF;IAEjB,0BAAgC;IAEhC,2DAA2D;IAC3D,yBADW,kBAAkB,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,CACA;IACvD,iCAFuC,MAAM,KAAK,IAAI,UAEK;IAG7D,sBAAsB;IACtB,SADc,IAAI,CACc;IAEhC,0BAA0B;IAC1B,cADc,MAAM,EAAE,CACc;IAEpC;;;OAGG;IACH,yBAA0B,eAHf,OAAO,oCAAoC,EAAE,OAGjB,KAF1B,IAAI,CAShB;CACF;mBAzCkB,oBAAoB;yBACd,QAAQ;+BACF,kCAAkC"}
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
  import Client from "../client/index.js";
3
3
  import EventEmitter from "events";
4
+ import SingleEventEmitter from "../utils/single-event-emitter.js";
4
5
  export default class ScoundrelServer {
5
6
  /**
6
7
  * Creates a new Scoundrel server
@@ -9,18 +10,25 @@ export default class ScoundrelServer {
9
10
  constructor(backend) {
10
11
  /**
11
12
  * @param {import("./connections/web-socket/client.js").default} clientBackend
13
+ * @returns {void}
12
14
  */
13
- this.onNewClient = (clientBackend) => {
14
- const client = new Client(clientBackend);
15
+ this.onNewClientFromBackend = (clientBackend) => {
16
+ const client = new Client(clientBackend, { enableServerControl: true });
15
17
  this.clients.push(client);
16
18
  this.events.emit("newClient", client);
19
+ this.onNewClientEventEmitter.emit([client]);
17
20
  };
18
21
  this.backend = backend;
19
- this.backend.onNewClient(this.onNewClient);
22
+ this.backend.onNewClient(this.onNewClientFromBackend);
20
23
  /** @type {Client[]} */
21
24
  this.clients = [];
22
25
  this.events = new EventEmitter();
26
+ /** @type {SingleEventEmitter<(client: Client) => void>} */
27
+ this.onNewClientEventEmitter = new SingleEventEmitter();
28
+ this.onNewClient = this.onNewClientEventEmitter.connector();
23
29
  }
30
+ /** @returns {void} */
24
31
  close() { this.backend.close(); }
32
+ /** @returns {Client[]} */
25
33
  getClients() { return this.clients; }
26
34
  }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * SingleEventEmitter: one listener at a time, strongly typed via JSDoc.
3
+ *
4
+ * @template {(...args: any[]) => any} Listener
5
+ */
6
+ export default class SingleEventEmitter<Listener extends (...args: any[]) => any> {
7
+ /**
8
+ * @param {{ strict?: boolean }=} options
9
+ * - strict (default true): throw if a listener is already connected
10
+ */
11
+ constructor(options?: {
12
+ strict?: boolean;
13
+ } | undefined);
14
+ /**
15
+ * Connect a listener (type-checked).
16
+ * @param {Listener} listener
17
+ * @returns {void}
18
+ */
19
+ connect(listener: Listener): void;
20
+ /**
21
+ * Disconnect only if the same listener is currently connected.
22
+ * @param {Listener} listener
23
+ * @returns {void}
24
+ */
25
+ disconnect(listener: Listener): void;
26
+ /** @returns {void} */
27
+ clear(): void;
28
+ /** @returns {boolean} */
29
+ hasListener(): boolean;
30
+ /**
31
+ * Emit the event to the connected listener (if any).
32
+ * Arguments are type-checked against Listener parameters.
33
+ * @param {...Parameters<Listener>} args
34
+ * @returns {boolean} true if a listener was called
35
+ */
36
+ emit(...args: Parameters<Listener>[]): boolean;
37
+ /**
38
+ * Create a "connect method" you can expose as `onNewClient` etc.
39
+ * The returned function is type-checked as (listener: Listener) => void.
40
+ *
41
+ * @returns {(listener: Listener) => void}
42
+ */
43
+ connector(): (listener: Listener) => void;
44
+ #private;
45
+ }
46
+ //# sourceMappingURL=single-event-emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"single-event-emitter.d.ts","sourceRoot":"","sources":["../../src/utils/single-event-emitter.js"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wCAFuC,QAAQ,SAAlC,CAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAI;IASpC;;;OAGG;IACH,sBAHW;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,YAAC,EAK/B;IAED;;;;OAIG;IACH,kBAHW,QAAQ,GACN,IAAI,CAOhB;IAED;;;;OAIG;IACH,qBAHW,QAAQ,GACN,IAAI,CAIhB;IAED,sBAAsB;IACtB,SADc,IAAI,CAGjB;IAED,yBAAyB;IACzB,eADc,OAAO,CAGpB;IAED;;;;;OAKG;IACH,cAHc,UAAU,CAAC,QAAQ,CAAC,EAAA,GACrB,OAAO,CAOnB;IAED;;;;;OAKG;IACH,aAFa,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAKxC;;CACF"}
@@ -0,0 +1,86 @@
1
+ // @ts-check
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _SingleEventEmitter_listener, _SingleEventEmitter_strict;
14
+ /**
15
+ * SingleEventEmitter: one listener at a time, strongly typed via JSDoc.
16
+ *
17
+ * @template {(...args: any[]) => any} Listener
18
+ */
19
+ class SingleEventEmitter {
20
+ /**
21
+ * @param {{ strict?: boolean }=} options
22
+ * - strict (default true): throw if a listener is already connected
23
+ */
24
+ constructor(options) {
25
+ /** @type {Listener | null} */
26
+ _SingleEventEmitter_listener.set(this, null
27
+ /** @type {boolean} */
28
+ );
29
+ /** @type {boolean} */
30
+ _SingleEventEmitter_strict.set(this, void 0);
31
+ __classPrivateFieldSet(this, _SingleEventEmitter_strict, options?.strict ?? true, "f");
32
+ }
33
+ /**
34
+ * Connect a listener (type-checked).
35
+ * @param {Listener} listener
36
+ * @returns {void}
37
+ */
38
+ connect(listener) {
39
+ if (__classPrivateFieldGet(this, _SingleEventEmitter_listener, "f") && __classPrivateFieldGet(this, _SingleEventEmitter_strict, "f")) {
40
+ throw new Error("SingleEventEmitter already has a listener connected");
41
+ }
42
+ __classPrivateFieldSet(this, _SingleEventEmitter_listener, listener, "f");
43
+ }
44
+ /**
45
+ * Disconnect only if the same listener is currently connected.
46
+ * @param {Listener} listener
47
+ * @returns {void}
48
+ */
49
+ disconnect(listener) {
50
+ if (__classPrivateFieldGet(this, _SingleEventEmitter_listener, "f") === listener)
51
+ __classPrivateFieldSet(this, _SingleEventEmitter_listener, null, "f");
52
+ }
53
+ /** @returns {void} */
54
+ clear() {
55
+ __classPrivateFieldSet(this, _SingleEventEmitter_listener, null, "f");
56
+ }
57
+ /** @returns {boolean} */
58
+ hasListener() {
59
+ return __classPrivateFieldGet(this, _SingleEventEmitter_listener, "f") !== null;
60
+ }
61
+ /**
62
+ * Emit the event to the connected listener (if any).
63
+ * Arguments are type-checked against Listener parameters.
64
+ * @param {...Parameters<Listener>} args
65
+ * @returns {boolean} true if a listener was called
66
+ */
67
+ emit(...args) {
68
+ const fn = __classPrivateFieldGet(this, _SingleEventEmitter_listener, "f");
69
+ if (!fn)
70
+ return false;
71
+ fn(...args);
72
+ return true;
73
+ }
74
+ /**
75
+ * Create a "connect method" you can expose as `onNewClient` etc.
76
+ * The returned function is type-checked as (listener: Listener) => void.
77
+ *
78
+ * @returns {(listener: Listener) => void}
79
+ */
80
+ connector() {
81
+ // bind() keeps `this`, and the return type is enforced by the JSDoc above
82
+ return this.connect.bind(this);
83
+ }
84
+ }
85
+ _SingleEventEmitter_listener = new WeakMap(), _SingleEventEmitter_strict = new WeakMap();
86
+ export default SingleEventEmitter;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "scoundrel-remote-eval",
3
3
  "type": "module",
4
- "version": "1.0.9",
4
+ "version": "1.0.10",
5
5
  "description": "",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
8
8
  "files": ["build/**"],
9
9
  "scripts": {
10
+ "all-checks": "npm run typecheck && npm run lint && npm run test",
10
11
  "build": "rm -rf build && tsc",
11
12
  "lint": "eslint",
12
13
  "prepublishOnly": "npm run build",