geonix 1.30.2 → 1.30.4

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": "geonix",
3
- "version": "1.30.2",
3
+ "version": "1.30.4",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "bin": {
@@ -34,4 +34,4 @@
34
34
  "eslint": "^10.1.0",
35
35
  "globals": "^17.4.0"
36
36
  }
37
- }
37
+ }
package/src/Codec.js CHANGED
@@ -30,4 +30,4 @@ export function decode(data) {
30
30
  } catch (e) {
31
31
  throw new Error(`Codec.decode: ${e.message}`, { cause: e });
32
32
  }
33
- }
33
+ }
package/src/Connection.js CHANGED
@@ -10,7 +10,7 @@ import { encryptPayload, decryptPayload, encryptSubject, wrapSubscription } from
10
10
  const CONNECTION_TIMEOUT = 10000;
11
11
 
12
12
  const defaultRequestOptions = {
13
- timeout: 300000
13
+ timeout: 300000,
14
14
  };
15
15
 
16
16
  const DEFAULT_CONNECTION_COUNT = 1;
@@ -21,7 +21,16 @@ const defaultConnectionOptions = {
21
21
  debug: (process.env.GX_TRANSPORT_DEBUG || process.env.TRANSPORT_DEBUG) === "true",
22
22
  maxReconnectAttempts: 30,
23
23
  pingInterval: 30000,
24
- waitOnFirstConnect: true
24
+ waitOnFirstConnect: true,
25
+ };
26
+
27
+ const TRANSPORT_TIMEOUT = 60_000;
28
+ const WATCHDOG_INTERVAL = 1000;
29
+
30
+ // env read via a function so tests can set GX_TRANSPORT_TIMEOUT after this module is imported
31
+ const getTransportTimeout = () => {
32
+ const raw = parseInt(process.env.GX_TRANSPORT_TIMEOUT, 10);
33
+ return Number.isFinite(raw) ? raw : TRANSPORT_TIMEOUT;
25
34
  };
26
35
  // -------------------------------------------------------------------------------------------------
27
36
 
@@ -32,12 +41,13 @@ const defaultConnectionOptions = {
32
41
  * encryption are activated by setting the `GX_SECRET` environment variable.
33
42
  */
34
43
  class Connection {
35
-
36
44
  #draining = false;
37
45
  #closed = false;
38
46
  #ready = false;
39
47
  #connections = [];
40
48
  #connectionRoundRobin = 0;
49
+ #disconnectedSince = Date.now();
50
+ #watchdogInterval = null;
41
51
 
42
52
  #getConnection() {
43
53
  return this.#connections[this.#connectionRoundRobin++ % this.#connections.length];
@@ -54,20 +64,52 @@ class Connection {
54
64
  async start(transport = process.env.GX_TRANSPORT || process.env.TRANSPORT || "nats://localhost") {
55
65
  const { connections: _connectionCount, ...natsOptions } = {
56
66
  ...defaultConnectionOptions,
57
- ...parseURL(transport)
67
+ ...parseURL(transport),
58
68
  };
59
69
  const connectionCount = parseInt(_connectionCount) || DEFAULT_CONNECTION_COUNT;
60
70
 
71
+ this.#startWatchdog();
72
+
61
73
  for (let i = 0; i < connectionCount; i++) {
62
74
  this.#connections.push(await connect(natsOptions));
63
75
  }
64
76
 
77
+ this.#disconnectedSince = null;
78
+
65
79
  logger.info("gx.connection.connected");
66
80
 
67
81
  this.#ready = true;
68
82
 
69
83
  this.monitorStatus();
70
- this.waitUntilClosed().catch(e => logger.error("gx.connection.waitUntilClosed:", e));
84
+ this.waitUntilClosed().catch((e) => logger.error("gx.connection.waitUntilClosed:", e));
85
+ }
86
+
87
+ // Polls the disconnect timestamp; exits the process if the transport has been
88
+ // unreachable longer than GX_TRANSPORT_TIMEOUT (default 60s, 0 disables).
89
+ #startWatchdog() {
90
+ if (this.#watchdogInterval) {
91
+ return;
92
+ }
93
+
94
+ this.#watchdogInterval = setInterval(() => {
95
+ const limit = getTransportTimeout();
96
+ if (limit === 0) {
97
+ return;
98
+ }
99
+ if (this.#disconnectedSince === null) {
100
+ return;
101
+ }
102
+
103
+ const elapsed = Date.now() - this.#disconnectedSince;
104
+ if (elapsed >= limit) {
105
+ logger.error(
106
+ `gx.connection: transport unreachable for ${Math.round(elapsed / 1000)}s (limit ${limit}ms), exiting`,
107
+ );
108
+ process.exit(1);
109
+ }
110
+ }, WATCHDOG_INTERVAL);
111
+
112
+ this.#watchdogInterval.unref?.();
71
113
  }
72
114
 
73
115
  /**
@@ -78,9 +120,17 @@ class Connection {
78
120
  for (const conn of this.#connections) {
79
121
  (async () => {
80
122
  for await (const event of conn.status()) {
123
+ if (event.type === "disconnect") {
124
+ if (this.#disconnectedSince === null) {
125
+ this.#disconnectedSince = Date.now();
126
+ }
127
+ } else if (event.type === "reconnect") {
128
+ this.#disconnectedSince = null;
129
+ }
130
+
81
131
  logger.debug("gx.connection.status", JSON.stringify(event));
82
132
  }
83
- })().catch(e => logger.error("gx.connection.status:", e));
133
+ })().catch((e) => logger.error("gx.connection.status:", e));
84
134
  }
85
135
  }
86
136
 
@@ -92,11 +142,16 @@ class Connection {
92
142
  */
93
143
  async waitUntilClosed() {
94
144
  // wait for all connections to be closed
95
- await Promise.all(this.#connections.map(connection => connection.closed()));
145
+ await Promise.all(this.#connections.map((connection) => connection.closed()));
96
146
 
97
147
  this.#closed = true;
98
148
  logger.info("gx.connection.closed");
99
149
 
150
+ if (this.#watchdogInterval) {
151
+ clearInterval(this.#watchdogInterval);
152
+ this.#watchdogInterval = null;
153
+ }
154
+
100
155
  webserver.stop();
101
156
 
102
157
  await sleep(5000);
@@ -118,9 +173,9 @@ class Connection {
118
173
 
119
174
  /**
120
175
  * Publish JSON
121
- *
122
- * @param {string} subject
123
- * @param {object} json
176
+ *
177
+ * @param {string} subject
178
+ * @param {object} json
124
179
  * @returns void
125
180
  */
126
181
  async publish(subject, json) {
@@ -150,21 +205,24 @@ class Connection {
150
205
  return;
151
206
  }
152
207
 
153
- await this.#getConnection().publish(encryptSubject(subject), encryptPayload(data != null ? Buffer.from(data) : Buffer.alloc(0)));
208
+ await this.#getConnection().publish(
209
+ encryptSubject(subject),
210
+ encryptPayload(data != null ? Buffer.from(data) : Buffer.alloc(0)),
211
+ );
154
212
  }
155
213
 
156
214
  /**
157
215
  * Request/Reply pattern on top of pub/sub
158
- *
159
- * @param {string} subject
160
- * @param {object} json
161
- * @param {object} options
216
+ *
217
+ * @param {string} subject
218
+ * @param {object} json
219
+ * @param {object} options
162
220
  * @returns any
163
221
  */
164
222
  async request(subject, json, opts = {}) {
165
223
  const options = {
166
224
  ...defaultRequestOptions,
167
- ...opts
225
+ ...opts,
168
226
  };
169
227
 
170
228
  const respondTo = `gx2.r.${picoid(16)}`;
@@ -235,9 +293,8 @@ class Connection {
235
293
  async drain() {
236
294
  this.#draining = true;
237
295
 
238
- await Promise.all(this.#connections.map(connection => connection.drain()));
296
+ await Promise.all(this.#connections.map((connection) => connection.drain()));
239
297
  }
240
-
241
298
  }
242
299
 
243
300
  /**
@@ -247,7 +304,7 @@ class Connection {
247
304
  * @type {Connection}
248
305
  */
249
306
  export const connection = new Connection();
250
- connection.start().catch(e => {
307
+ connection.start().catch((e) => {
251
308
  logger.error("gx.connection.start:", e);
252
309
  process.exit(1);
253
310
  });
package/src/Crypto.js CHANGED
@@ -3,7 +3,11 @@ import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes }
3
3
  const _secret = process.env.GX_SECRET || null;
4
4
 
5
5
  // Subject key is derived separately so HMAC-subject and AES-payload never share key material.
6
- const _subjectKey = _secret ? createHash("sha256").update(_secret + "\x00subject").digest() : null;
6
+ const _subjectKey = _secret
7
+ ? createHash("sha256")
8
+ .update(_secret + "\x00subject")
9
+ .digest()
10
+ : null;
7
11
 
8
12
  /**
9
13
  * AES-256-GCM key derived from `GX_SECRET`, or `null` when encryption is disabled.
@@ -11,7 +15,11 @@ const _subjectKey = _secret ? createHash("sha256").update(_secret + "\x00subject
11
15
  *
12
16
  * @type {Buffer|null}
13
17
  */
14
- export const _payloadKey = _secret ? createHash("sha256").update(_secret + "\x00payload").digest() : null;
18
+ export const _payloadKey = _secret
19
+ ? createHash("sha256")
20
+ .update(_secret + "\x00payload")
21
+ .digest()
22
+ : null;
15
23
 
16
24
  /**
17
25
  * Encrypts `data` with AES-256-GCM using {@link _payloadKey}. Returns `data` unchanged when
@@ -21,7 +29,9 @@ export const _payloadKey = _secret ? createHash("sha256").update(_secret + "\x00
21
29
  * @returns {Buffer}
22
30
  */
23
31
  export function encryptPayload(data) {
24
- if (!_payloadKey) { return data; }
32
+ if (!_payloadKey) {
33
+ return data;
34
+ }
25
35
  const buf = data != null ? Buffer.from(data) : Buffer.alloc(0);
26
36
  const iv = randomBytes(12);
27
37
  const cipher = createCipheriv("aes-256-gcm", _payloadKey, iv);
@@ -42,7 +52,9 @@ export function encryptPayload(data) {
42
52
  * @returns {Buffer}
43
53
  */
44
54
  export function decryptPayload(data) {
45
- if (!_payloadKey) { return data; }
55
+ if (!_payloadKey) {
56
+ return data;
57
+ }
46
58
  const buf = Buffer.from(data);
47
59
  const decipher = createDecipheriv("aes-256-gcm", _payloadKey, buf.subarray(0, 12));
48
60
  decipher.setAuthTag(buf.subarray(12, 28));
@@ -64,10 +76,12 @@ export function encryptSubject(subject) {
64
76
  return subject;
65
77
  }
66
78
 
67
- return subject.split(".").map(seg =>
68
- (seg === "*" || seg === ">") ? seg
69
- : createHmac("sha256", _subjectKey).update(seg).digest("hex").slice(0, 32)
70
- ).join(".");
79
+ return subject
80
+ .split(".")
81
+ .map((seg) =>
82
+ seg === "*" || seg === ">" ? seg : createHmac("sha256", _subjectKey).update(seg).digest("hex").slice(0, 32),
83
+ )
84
+ .join(".");
71
85
  }
72
86
 
73
87
  /**
@@ -94,7 +108,7 @@ export function wrapSubscription(sub) {
94
108
  }
95
109
 
96
110
  return { value: { ...value, data: decryptPayload(value.data) }, done: false };
97
- }
111
+ },
98
112
  };
99
113
  },
100
114
  drain: () => sub.drain(),
package/src/Gateway.js CHANGED
@@ -15,12 +15,14 @@ const MAX_SESSIONS = 16384;
15
15
  // Real client IP — checks well-known CDN/proxy headers before falling back to socket address
16
16
  function getClientIp(req) {
17
17
  const header =
18
- req.headers["cf-connecting-ip"] || // Cloudflare
19
- req.headers["true-client-ip"] || // Cloudflare Enterprise / Akamai
20
- req.headers["x-real-ip"] || // nginx
21
- req.headers["x-forwarded-for"]; // standard (may be comma-separated list)
18
+ req.headers["cf-connecting-ip"] || // Cloudflare
19
+ req.headers["true-client-ip"] || // Cloudflare Enterprise / Akamai
20
+ req.headers["x-real-ip"] || // nginx
21
+ req.headers["x-forwarded-for"]; // standard (may be comma-separated list)
22
22
 
23
- if (header) { return header.split(",")[0].trim(); }
23
+ if (header) {
24
+ return header.split(",")[0].trim();
25
+ }
24
26
  return req.socket?.remoteAddress || "unknown";
25
27
  }
26
28
 
@@ -39,12 +41,12 @@ const stats = {
39
41
  requests: 0,
40
42
  proxied: 0,
41
43
  proxied_over_nats: 0,
42
- debug_requests: 0
44
+ debug_requests: 0,
43
45
  };
44
46
 
45
47
  const defaultOpts = {
46
- beforeRequest: (_req, _res) => { },
47
- afterRequest: (_req, _res) => { }
48
+ beforeRequest: (_req, _res) => {},
49
+ afterRequest: (_req, _res) => {},
48
50
  };
49
51
 
50
52
  /**
@@ -53,7 +55,6 @@ const defaultOpts = {
53
55
  * dynamically as services join and leave the bus.
54
56
  */
55
57
  export class Gateway {
56
-
57
58
  /**
58
59
  * Creates and starts a new Gateway instance.
59
60
  *
@@ -85,7 +86,7 @@ export class Gateway {
85
86
 
86
87
  this.#opts = { ...this.#opts, ...opts };
87
88
 
88
- this.#start().catch(e => logger.error("gx.gateway.start:", e));
89
+ this.#start().catch((e) => logger.error("gx.gateway.start:", e));
89
90
  }
90
91
 
91
92
  async #start(port = 8080) {
@@ -179,7 +180,7 @@ export class Gateway {
179
180
  this.#api.all("*", (req, res) => {
180
181
  res.status(404).send({
181
182
  error: 404,
182
- source: "gw"
183
+ source: "gw",
183
184
  });
184
185
  });
185
186
 
@@ -253,7 +254,7 @@ export class Gateway {
253
254
  return true;
254
255
  };
255
256
 
256
- entries = (await Promise.all(entries.map(processEntry))).filter(result => result === true);
257
+ entries = (await Promise.all(entries.map(processEntry))).filter((result) => result === true);
257
258
 
258
259
  if (entries.length > 0) {
259
260
  this.#rebuildRouter = true;
@@ -285,7 +286,7 @@ export class Gateway {
285
286
  });
286
287
 
287
288
  router.get("/services", (req, res) => {
288
- const services = Object.values(registry.getEntries()).map(e => (`${e.n}@${e.v}`));
289
+ const services = Object.values(registry.getEntries()).map((e) => `${e.n}@${e.v}`);
289
290
  services.sort();
290
291
  res.send(services);
291
292
  });
@@ -312,11 +313,11 @@ export class Gateway {
312
313
  node: {
313
314
  version: process.version,
314
315
  platform: process.platform,
315
- arch: process.arch
316
+ arch: process.arch,
316
317
  },
317
318
  mem: process.memoryUsage(),
318
319
  rss: process.memoryUsage.rss(),
319
- cpu: process.cpuUsage()
320
+ cpu: process.cpuUsage(),
320
321
  });
321
322
  });
322
323
 
@@ -341,19 +342,21 @@ export class Gateway {
341
342
  });
342
343
 
343
344
  const dataLoop = async () => {
344
- for await (const event of ingress) { client.write(event.data); }
345
+ for await (const event of ingress) {
346
+ client.write(event.data);
347
+ }
345
348
  };
346
349
 
347
- dataLoop().catch(e => logger.error("nats.proxy.dataLoop:", e));
350
+ dataLoop().catch((e) => logger.error("nats.proxy.dataLoop:", e));
348
351
  }
349
352
  }
350
353
 
351
354
  /**
352
355
  * Proxies websocket connection
353
- *
354
- * @param {string} target
355
- * @param {Readable} inbound
356
- * @param {Request} req
356
+ *
357
+ * @param {string} target
358
+ * @param {Readable} inbound
359
+ * @param {Request} req
357
360
  */
358
361
  #proxyWebsocket(target, inbound, req) {
359
362
  try {
@@ -366,7 +369,7 @@ export class Gateway {
366
369
 
367
370
  backend.on("open", () => {
368
371
  backend.on("message", (data, isBinary) => inbound.send(isBinary ? data : data.toString()));
369
- inbound.on("message", data => backend.send(data));
372
+ inbound.on("message", (data) => backend.send(data));
370
373
 
371
374
  backend.on("close", () => inbound.close());
372
375
  inbound.on("close", () => backend.close());
@@ -398,18 +401,22 @@ export class Gateway {
398
401
  ? `127.0.0.1:${proxy.port}`
399
402
  : entry.a?.[Math.floor(Math.random() * entry.a.length)];
400
403
 
401
- if (!backendAddr) { continue; }
404
+ if (!backendAddr) {
405
+ continue;
406
+ }
402
407
 
403
408
  // generate global endpoint list
404
409
  for (let e of entry.m) {
405
410
  const endpointMatch = endpointMatcher.exec(e);
406
411
  if (endpointMatch) {
407
412
  const endpoint = endpointMatch.groups;
408
- const parsed = endpoint.options ? Object.fromEntries(new URLSearchParams(endpoint.options)) : {};
413
+ const parsed = endpoint.options
414
+ ? Object.fromEntries(new URLSearchParams(endpoint.options))
415
+ : {};
409
416
  const parsedOrder = parseInt(parsed.order, 10);
410
417
  let options = {
411
418
  ...parsed,
412
- order: Number.isNaN(parsedOrder) ? 100 : parsedOrder
419
+ order: Number.isNaN(parsedOrder) ? 100 : parsedOrder,
413
420
  };
414
421
 
415
422
  try {
@@ -418,7 +425,7 @@ export class Gateway {
418
425
  version: semver.coerce(entry.v).version,
419
426
  options,
420
427
  endpoint,
421
- backend: [backendAddr]
428
+ backend: [backendAddr],
422
429
  });
423
430
  } catch (e) {
424
431
  logger.error("gateway.buildRouter.error:", entry);
@@ -435,7 +442,10 @@ export class Gateway {
435
442
  const url = `${endpoints[index].endpoint.verb} ${endpoints[index].endpoint.url}`;
436
443
 
437
444
  for (let n = 0; n < index; n++) {
438
- if (`${endpoints[n].endpoint.verb} ${endpoints[n].endpoint.url}` === url && endpoints[n].version === version) {
445
+ if (
446
+ `${endpoints[n].endpoint.verb} ${endpoints[n].endpoint.url}` === url &&
447
+ endpoints[n].version === version
448
+ ) {
439
449
  endpoints[n].backend = endpoints[n].backend.concat(endpoints[index].backend);
440
450
  endpoints.splice(index, 1);
441
451
  break;
@@ -509,5 +519,4 @@ export class Gateway {
509
519
  this.#isActive = false;
510
520
  clearInterval(this.#rebuildRouterInterval);
511
521
  }
512
-
513
522
  }
package/src/Logger.js CHANGED
@@ -5,12 +5,16 @@ const LEVEL = LEVELS[process.env.GX_LOG_LEVEL] ?? LEVELS.info;
5
5
  const FORMAT = process.env.GX_LOG_FORMAT === "json" ? "json" : "text";
6
6
 
7
7
  const defaultLoggerOptions = {
8
- timestamp: true
8
+ timestamp: true,
9
9
  };
10
10
 
11
11
  function serialize(val) {
12
- if (val instanceof Error) { return val.stack || val.message; }
13
- if (typeof val === "object" && val !== null) { return JSON.stringify(val); }
12
+ if (val instanceof Error) {
13
+ return val.stack || val.message;
14
+ }
15
+ if (typeof val === "object" && val !== null) {
16
+ return JSON.stringify(val);
17
+ }
14
18
  return String(val);
15
19
  }
16
20
 
@@ -22,7 +26,6 @@ function serialize(val) {
22
26
  * The output format is controlled by `GX_LOG_FORMAT` (`text` | `json`, default `text`).
23
27
  */
24
28
  export class Logger {
25
-
26
29
  #options = defaultLoggerOptions;
27
30
  #level = LEVEL;
28
31
  #format = FORMAT;
@@ -46,10 +49,17 @@ export class Logger {
46
49
  #log(level, ...args) {
47
50
  const stream = level === "error" ? process.stderr : process.stdout;
48
51
  if (this.#format === "json") {
49
- stream.write(JSON.stringify({ time: new Date().toISOString(), level, msg: args.map(serialize).join(" ") }) + "\n");
52
+ stream.write(
53
+ JSON.stringify({ time: new Date().toISOString(), level, msg: args.map(serialize).join(" ") }) + "\n",
54
+ );
50
55
  } else {
51
56
  const ts = this.#options.timestamp ? new Date().toISOString() : undefined;
52
- stream.write([ts, TAGS[level], ...args].filter($ => $ !== undefined).map(serialize).join(" ") + "\n");
57
+ stream.write(
58
+ [ts, TAGS[level], ...args]
59
+ .filter(($) => $ !== undefined)
60
+ .map(serialize)
61
+ .join(" ") + "\n",
62
+ );
53
63
  }
54
64
  }
55
65
 
@@ -104,7 +114,6 @@ export class Logger {
104
114
  setFormat(format) {
105
115
  this.#format = format === "json" ? "json" : "text";
106
116
  }
107
-
108
117
  }
109
118
 
110
119
  /**
package/src/Registry.js CHANGED
@@ -17,7 +17,6 @@ const GARBAGE_COLLECTOR_INTERVAL = 500;
17
17
  * @extends EventEmitter
18
18
  */
19
19
  class Registry extends EventEmitter {
20
-
21
20
  #isActive = false;
22
21
  #registry = {};
23
22
  #byIdentifier = new Map();
@@ -25,15 +24,15 @@ class Registry extends EventEmitter {
25
24
  constructor() {
26
25
  super();
27
26
 
28
- this.#start().catch(e => logger.error("registry.start:", e));
27
+ this.#start().catch((e) => logger.error("registry.start:", e));
29
28
  }
30
29
 
31
30
  async #start() {
32
31
  this.#isActive = true;
33
32
  await connection.waitUntilReady();
34
33
 
35
- this.#beaconListener().catch(e => logger.error("registry.beaconListener:", e));
36
- this.#garbageCollector().catch(e => logger.error("registry.garbageCollector:", e));
34
+ this.#beaconListener().catch((e) => logger.error("registry.beaconListener:", e));
35
+ this.#garbageCollector().catch((e) => logger.error("registry.garbageCollector:", e));
37
36
  }
38
37
 
39
38
  /**
@@ -69,14 +68,17 @@ class Registry extends EventEmitter {
69
68
  let firstFound = false;
70
69
  const onFirstHealthy = () => {
71
70
  if (!firstFound) {
72
- firstFound = true; resolveFirst();
71
+ firstFound = true;
72
+ resolveFirst();
73
73
  }
74
74
  };
75
75
 
76
76
  // all checks run in parallel; background ones push into data.a,
77
77
  // which is the same array reference spread into the registry entry below
78
78
  // resolve promiseFirst when all checks are done so we don't wait on the timeout
79
- Promise.all(allAddresses.map(a => this.#checkHealth(a, data.a, onFirstHealthy))).then(() => resolveFirst());
79
+ Promise.all(allAddresses.map((a) => this.#checkHealth(a, data.a, onFirstHealthy))).then(() =>
80
+ resolveFirst(),
81
+ );
80
82
 
81
83
  try {
82
84
  await withTimeout(promiseFirst, 5000);
@@ -87,7 +89,7 @@ class Registry extends EventEmitter {
87
89
 
88
90
  this.#registry[data.i] = {
89
91
  ...data,
90
- timeout: Date.now() + REGISTRY_ENTRY_TIMEOUT
92
+ timeout: Date.now() + REGISTRY_ENTRY_TIMEOUT,
91
93
  };
92
94
 
93
95
  const nameVersion = `${data.n}@${data.v}`;
@@ -124,7 +126,7 @@ class Registry extends EventEmitter {
124
126
  }
125
127
 
126
128
  // new service — dispatch registration without awaiting so the beacon loop stays unblocked
127
- this.#registerService(data).catch(e => logger.error("registry.registerService:", e));
129
+ this.#registerService(data).catch((e) => logger.error("registry.registerService:", e));
128
130
  }
129
131
  }
130
132
 
@@ -144,7 +146,9 @@ class Registry extends EventEmitter {
144
146
  const set = this.#byIdentifier.get(nameVersion);
145
147
  if (set) {
146
148
  set.delete(entry.i);
147
- if (set.size === 0) { this.#byIdentifier.delete(nameVersion); }
149
+ if (set.size === 0) {
150
+ this.#byIdentifier.delete(nameVersion);
151
+ }
148
152
  }
149
153
  this.#byIdentifier.delete(entry.i);
150
154
 
@@ -174,8 +178,11 @@ class Registry extends EventEmitter {
174
178
  */
175
179
  getEntriesForIdentifier(identifier) {
176
180
  const ids = this.#byIdentifier.get(identifier);
177
- if (!ids) { return []; }
178
- return [...ids].map(i => this.#registry[i]).filter(Boolean);
181
+ if (!ids) {
182
+ return [];
183
+ }
184
+
185
+ return [...ids].map((i) => this.#registry[i]).filter(Boolean);
179
186
  }
180
187
 
181
188
  /**
@@ -215,7 +222,6 @@ class Registry extends EventEmitter {
215
222
  return `${matches[0].n}@${matches[0].v}`;
216
223
  }
217
224
  }
218
-
219
225
  }
220
226
 
221
227
  /**
@@ -223,4 +229,4 @@ class Registry extends EventEmitter {
223
229
  *
224
230
  * @type {Registry}
225
231
  */
226
- export const registry = new Registry();
232
+ export const registry = new Registry();
package/src/Remote.js CHANGED
@@ -10,12 +10,16 @@ import { Request } from "./Request.js";
10
10
  * @param {...any} context - Optional context values forwarded to the remote method.
11
11
  * @returns {Proxy} A proxy whose properties are async functions that call the remote service.
12
12
  */
13
- export const Remote = (service, ...context) => new Proxy({}, {
14
- get: (_target, method) => {
15
- if (typeof method !== "string" || method === "then") {
16
- return undefined;
17
- }
13
+ export const Remote = (service, ...context) =>
14
+ new Proxy(
15
+ {},
16
+ {
17
+ get: (_target, method) => {
18
+ if (typeof method !== "string" || method === "then") {
19
+ return undefined;
20
+ }
18
21
 
19
- return async (...args) => Request(service, method, args, context);
20
- }
21
- });
22
+ return async (...args) => Request(service, method, args, context);
23
+ },
24
+ },
25
+ );
package/src/Request.js CHANGED
@@ -23,7 +23,7 @@ function waitForIdentifier(name, version, id, timeout) {
23
23
  return Promise.resolve(found);
24
24
  }
25
25
 
26
- return new Promise(resolve => {
26
+ return new Promise((resolve) => {
27
27
  const timer = setTimeout(() => {
28
28
  registry.removeListener("added", onAdded);
29
29
  resolve(null);
@@ -109,11 +109,15 @@ export async function directRequest(identifier, method, args, context, options,
109
109
 
110
110
  let httpResponse;
111
111
  try {
112
- const res = await fetchWithTimeout(url, {
113
- method: "POST",
114
- headers: { "content-type": contentType },
115
- body: fetchBody,
116
- }, options?.httpTimeout ?? 5000);
112
+ const res = await fetchWithTimeout(
113
+ url,
114
+ {
115
+ method: "POST",
116
+ headers: { "content-type": contentType },
117
+ body: fetchBody,
118
+ },
119
+ options?.httpTimeout ?? 5000,
120
+ );
117
121
  if (res.ok) {
118
122
  httpResponse = _payloadKey
119
123
  ? JSON.parse(decryptPayload(Buffer.from(await res.arrayBuffer())))
@@ -124,7 +128,9 @@ export async function directRequest(identifier, method, args, context, options,
124
128
  }
125
129
 
126
130
  if (httpResponse) {
127
- if (httpResponse.e) { throw Error(`Request: remote error: ${httpResponse.e}`); }
131
+ if (httpResponse.e) {
132
+ throw Error(`Request: remote error: ${httpResponse.e}`);
133
+ }
128
134
  if (isStream(httpResponse.r)) {
129
135
  return JSON.parse(await streamToString(httpResponse.r));
130
136
  }
@@ -142,19 +148,29 @@ export async function directRequest(identifier, method, args, context, options,
142
148
  m: method,
143
149
  a: args,
144
150
  c: context,
145
- o: originator
151
+ o: originator,
146
152
  },
147
- options);
153
+ options,
154
+ );
148
155
 
149
156
  // automatically process streamed response
150
157
  if (isStream(response)) {
151
158
  response = JSON.parse(await streamToString(response));
152
159
  }
153
160
  } catch (e) {
154
- logger.debug("GxError: directRequest", inspect({
155
- originator, service: service ?? identifier, method, args, context, options,
156
- error: e, duration: Date.now() - requestBegin
157
- }));
161
+ logger.debug(
162
+ "GxError: directRequest",
163
+ inspect({
164
+ originator,
165
+ service: service ?? identifier,
166
+ method,
167
+ args,
168
+ context,
169
+ options,
170
+ error: e,
171
+ duration: Date.now() - requestBegin,
172
+ }),
173
+ );
158
174
 
159
175
  throw e;
160
176
  }
@@ -199,4 +215,4 @@ export async function Subscribe(subject, callback) {
199
215
  for await (const event of subscription) {
200
216
  callback(event.data);
201
217
  }
202
- }
218
+ }
package/src/Service.js CHANGED
@@ -1,5 +1,15 @@
1
1
  import { connection } from "./Connection.js";
2
- import { picoid, sleep, hash, getSecondsSinceMidnight, OverlayObject, GeonixVersion, getFirstItemFromAsyncIterable, getNetworkAddresses, deepMerge } from "./Util.js";
2
+ import {
3
+ picoid,
4
+ sleep,
5
+ hash,
6
+ getSecondsSinceMidnight,
7
+ OverlayObject,
8
+ GeonixVersion,
9
+ getFirstItemFromAsyncIterable,
10
+ getNetworkAddresses,
11
+ deepMerge,
12
+ } from "./Util.js";
3
13
  import { webserver } from "./WebServer.js";
4
14
  import { createConnection } from "net";
5
15
  import { EOL } from "os";
@@ -18,7 +28,7 @@ const getInactivityTimeout = () => parseInt(process.env.GX_INACTIVITY_TIMEOUT) |
18
28
 
19
29
  const protectedMethodNames = ["constructor", "onStart"];
20
30
  const endpointMatcher = /^((?<options>.+)\|)?(?<verb>WS|SUB|GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS|ALL)\s(?<url>.*)/;
21
- const isEndpointFilter = methodName => endpointMatcher.test(methodName);
31
+ const isEndpointFilter = (methodName) => endpointMatcher.test(methodName);
22
32
  const ERROR_BEGIN_DELIMITER = "-".repeat(10);
23
33
  const ERROR_END_DELIMITER = "-".repeat(40);
24
34
 
@@ -33,9 +43,9 @@ const defaultServiceOptions = {
33
43
  middleware: {
34
44
  json: true,
35
45
  raw: true,
36
- cookies: true
46
+ cookies: true,
37
47
  },
38
- fullBeacon: true
48
+ fullBeacon: true,
39
49
  };
40
50
 
41
51
  /**
@@ -43,7 +53,6 @@ const defaultServiceOptions = {
43
53
  * the service on the NATS bus, expose HTTP endpoints, and begin sending beacons to the registry.
44
54
  */
45
55
  export class Service {
46
-
47
56
  /**
48
57
  * Creates a new instance of the subclass and starts it on the NATS bus.
49
58
  *
@@ -52,10 +61,10 @@ export class Service {
52
61
  */
53
62
  static start(options = {}) {
54
63
  const instance = new this();
55
- instance.#start(options).catch(e => logger.error("gx.service.start:", e));
64
+ instance.#start(options).catch((e) => logger.error("gx.service.start:", e));
56
65
  }
57
66
 
58
- // ---------------------------------------------------------------------------------------------
67
+ // ---------------------------------------------------------------------------------------------
59
68
 
60
69
  #isActive = false;
61
70
  #me = {};
@@ -78,7 +87,10 @@ export class Service {
78
87
  const lineMap = new Map();
79
88
  for (const name of fields) {
80
89
  const quoted = JSON.stringify(name);
81
- lineMap.set(name, serviceSource.findIndex(line => line.includes(quoted)));
90
+ lineMap.set(
91
+ name,
92
+ serviceSource.findIndex((line) => line.includes(quoted)),
93
+ );
82
94
  }
83
95
  fields.sort((a, b) => lineMap.get(a) - lineMap.get(b));
84
96
 
@@ -90,15 +102,21 @@ export class Service {
90
102
  // name
91
103
  n: options.name ?? this.constructor.name,
92
104
  // version
93
- v: process.env.GX_VERSION || process.env.VERSION || process.env.version || options?.version || this.version || `999.999.${getSecondsSinceMidnight()}`,
105
+ v:
106
+ process.env.GX_VERSION ||
107
+ process.env.VERSION ||
108
+ process.env.version ||
109
+ options?.version ||
110
+ this.version ||
111
+ `999.999.${getSecondsSinceMidnight()}`,
94
112
  // methods
95
113
  m: fields
96
- .filter(methodName => !protectedMethodNames.includes(methodName))
97
- .filter(methodName => !methodName.startsWith("$")),
114
+ .filter((methodName) => !protectedMethodNames.includes(methodName))
115
+ .filter((methodName) => !methodName.startsWith("$")),
98
116
  // geonix version
99
117
  gx: GeonixVersion,
100
118
  // IP addresses
101
- a: getNetworkAddresses().map(address => `${address}:${webserver.getPort()}`)
119
+ a: getNetworkAddresses().map((address) => `${address}:${webserver.getPort()}`),
102
120
  };
103
121
 
104
122
  // check if method takes context as first argument
@@ -106,21 +124,20 @@ export class Service {
106
124
  const method = this[methodName];
107
125
  this.#methodTakesContext.set(
108
126
  method,
109
- method.toString()?.match(/\((?<args>.*)\)/)?.groups?.args.startsWith("$")
127
+ method
128
+ .toString()
129
+ ?.match(/\((?<args>.*)\)/)
130
+ ?.groups?.args.startsWith("$"),
110
131
  );
111
132
  }
112
133
 
113
- this.#beacon()
114
- .catch(e => logger.error("gx.beacon:", e));
134
+ this.#beacon().catch((e) => logger.error("gx.beacon:", e));
115
135
 
116
- this.#callListener(`${this.#me.n}@${this.#me.v}`)
117
- .catch(e => logger.error("gx.queueListener:", e));
136
+ this.#callListener(`${this.#me.n}@${this.#me.v}`).catch((e) => logger.error("gx.queueListener:", e));
118
137
 
119
- this.#callListener(this.#me.i)
120
- .catch(e => logger.error("gx.directListener:", e));
138
+ this.#callListener(this.#me.i).catch((e) => logger.error("gx.directListener:", e));
121
139
 
122
- this.#webserver()
123
- .catch(e => logger.error("gx.webserver:", e));
140
+ this.#webserver().catch((e) => logger.error("gx.webserver:", e));
124
141
 
125
142
  logger.info("gx.service.start", this.#me.n, this.#me.v);
126
143
 
@@ -141,7 +158,7 @@ export class Service {
141
158
  async #beacon() {
142
159
  while (this.#isActive) {
143
160
  const payload = this.#options.fullBeacon ? this.#me : { i: this.#me.i };
144
- connection.publish("gx2.beacon", payload).catch(e => logger.warn("beacon.publish:", e));
161
+ connection.publish("gx2.beacon", payload).catch((e) => logger.warn("beacon.publish:", e));
145
162
  await sleep(BEACON_INTERVAL);
146
163
  }
147
164
  }
@@ -168,11 +185,10 @@ export class Service {
168
185
 
169
186
  /**
170
187
  * Register local endpoints with express instance
171
- * @returns
188
+ * @returns
172
189
  */
173
190
  async #webserver() {
174
- const endpoints = this.#me.m
175
- .filter(isEndpointFilter);
191
+ const endpoints = this.#me.m.filter(isEndpointFilter);
176
192
 
177
193
  const router = webserver.router();
178
194
 
@@ -181,7 +197,9 @@ export class Service {
181
197
  router.post(`/!!_gx/rpc/${hash(this.#me.i)}`, raw, async (req, res) => {
182
198
  const body = _payloadKey
183
199
  ? JSON.parse(decryptPayload(req.body))
184
- : (Buffer.isBuffer(req.body) ? JSON.parse(req.body.toString()) : req.body);
200
+ : Buffer.isBuffer(req.body)
201
+ ? JSON.parse(req.body.toString())
202
+ : req.body;
185
203
  await this.#onCall(body, (result) => {
186
204
  const payload = Buffer.from(JSON.stringify(result));
187
205
  if (_payloadKey) {
@@ -214,7 +232,7 @@ export class Service {
214
232
  let { verb, url: uri } = endpointMatcher.exec(endpoint)?.groups || {};
215
233
  verb = verb.toLowerCase();
216
234
 
217
- let handlers = (Array.isArray(this[endpoint]) ? this[endpoint] : [this[endpoint]]);
235
+ let handlers = Array.isArray(this[endpoint]) ? this[endpoint] : [this[endpoint]];
218
236
 
219
237
  const handlersBefore = this.#options?.handlers?.before ?? [];
220
238
  const handlersAfter = this.#options?.handlers?.after ?? [];
@@ -232,7 +250,14 @@ export class Service {
232
250
  // handlersBefore run as route-scoped middleware before the upgrade;
233
251
  // handlersAfter does not apply to WebSocket connections.
234
252
  if (handlersBefore.length > 0) {
235
- router.use(uri, ...handlersBefore.map(h => (...args) => h.apply(this, args)));
253
+ router.use(
254
+ uri,
255
+ ...handlersBefore.map(
256
+ (h) =>
257
+ (...args) =>
258
+ h.apply(this, args),
259
+ ),
260
+ );
236
261
  }
237
262
  router.ws(uri, this[endpoint].bind(this));
238
263
  break;
@@ -258,14 +283,14 @@ export class Service {
258
283
  handler(event.data);
259
284
  }
260
285
  };
261
- processor().catch(e => logger.error("$sub.processor:", e));
286
+ processor().catch((e) => logger.error("$sub.processor:", e));
262
287
  }
263
288
 
264
289
  /**
265
290
  * Handle individual call
266
- * @param {Object} call
267
- * @param {Function} respond
268
- * @returns
291
+ * @param {Object} call
292
+ * @param {Function} respond
293
+ * @returns
269
294
  */
270
295
  async #onCall(call, respond) {
271
296
  const { m: methodName, a: args, c: context, o: caller } = call;
@@ -302,7 +327,7 @@ export class Service {
302
327
 
303
328
  this.#connections.set(streamId, {
304
329
  client,
305
- sub: ingress
330
+ sub: ingress,
306
331
  });
307
332
 
308
333
  const cleanup = () => {
@@ -338,8 +363,11 @@ export class Service {
338
363
  cleanup();
339
364
  };
340
365
 
341
- incomingLoop().catch(e => { logger.error("$createConnection.incomingLoop:", e); cleanup(); });
342
- controlLoop().catch(e => logger.error("$createConnection.controlLoop:", e));
366
+ incomingLoop().catch((e) => {
367
+ logger.error("$createConnection.incomingLoop:", e);
368
+ cleanup();
369
+ });
370
+ controlLoop().catch((e) => logger.error("$createConnection.controlLoop:", e));
343
371
 
344
372
  return true;
345
373
  }
@@ -350,11 +378,11 @@ export class Service {
350
378
  node: {
351
379
  version: process.version,
352
380
  platform: process.platform,
353
- arch: process.arch
381
+ arch: process.arch,
354
382
  },
355
383
  mem: process.memoryUsage(),
356
384
  rss: process.memoryUsage.rss(),
357
- cpu: process.cpuUsage()
385
+ cpu: process.cpuUsage(),
358
386
  };
359
387
  }
360
388
 
@@ -369,5 +397,4 @@ export class Service {
369
397
  $stop() {
370
398
  this.#isActive = false;
371
399
  }
372
-
373
- }
400
+ }
package/src/Stream.js CHANGED
@@ -52,7 +52,7 @@ export function Stream(data) {
52
52
 
53
53
  const event = await Promise.race([
54
54
  getFirstItemFromAsyncIterable(control),
55
- new Promise(resolve => setTimeout(() => resolve(null), getStreamTimeout()))
55
+ new Promise((resolve) => setTimeout(() => resolve(null), getStreamTimeout())),
56
56
  ]);
57
57
 
58
58
  if (!event) {
@@ -68,20 +68,22 @@ export function Stream(data) {
68
68
  delete activeStreams[id];
69
69
 
70
70
  // kickstart the stream
71
- readable.on("data", chunk => connection.publishRaw(`gx2.stream.${id}.b`, chunk));
71
+ readable.on("data", (chunk) => connection.publishRaw(`gx2.stream.${id}.b`, chunk));
72
72
  readable.on("close", () => {
73
73
  connection.publishRaw(`gx2.stream.${id}.b`);
74
74
  });
75
75
  }
76
- })().catch(e => logger.error("stream.nats.handler:", e));
76
+ })().catch((e) => logger.error("stream.nats.handler:", e));
77
77
 
78
78
  const result = {
79
79
  $: "stream",
80
- id
80
+ id,
81
81
  };
82
82
 
83
83
  // get the port and addresses of the webserver
84
- const addresses = webserver.getPort() ? getNetworkAddresses().map(address => `${address}:${webserver.getPort()}`) : undefined;
84
+ const addresses = webserver.getPort()
85
+ ? getNetworkAddresses().map((address) => `${address}:${webserver.getPort()}`)
86
+ : undefined;
85
87
  if (addresses) {
86
88
  result.a = addresses;
87
89
  }
@@ -151,7 +153,11 @@ export async function getReadable(object) {
151
153
  }
152
154
  }
153
155
  };
154
- dataHandler().catch(e => { logger.error("stream.dataHandler:", e); subscription.unsubscribe(); readable.destroy(); });
156
+ dataHandler().catch((e) => {
157
+ logger.error("stream.dataHandler:", e);
158
+ subscription.unsubscribe();
159
+ readable.destroy();
160
+ });
155
161
 
156
162
  // kickstart remote stream with a blank message
157
163
  await connection.publishRaw(`gx2.stream.${object.id}.a`);
package/src/Util.js CHANGED
@@ -14,22 +14,22 @@ import { tmpdir } from "os";
14
14
 
15
15
  /**
16
16
  * Wait for {delay} ms
17
- * @param {number} delay
18
- * @returns
17
+ * @param {number} delay
18
+ * @returns
19
19
  */
20
- export const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
20
+ export const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
21
21
 
22
22
  /**
23
23
  * Wait for next tick
24
- *
25
- * @returns
24
+ *
25
+ * @returns
26
26
  */
27
- export const yieldToEventLoop = () => new Promise(resolve => setImmediate(resolve));
27
+ export const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
28
28
 
29
29
  /**
30
30
  * Parse nats:// URL
31
- * @param {string} url
32
- * @returns
31
+ * @param {string} url
32
+ * @returns
33
33
  */
34
34
  export function parseURL(url) {
35
35
  const parsed = new URL(url);
@@ -38,12 +38,12 @@ export function parseURL(url) {
38
38
  servers: `${parsed.hostname}:${parsed.port || 4222}`,
39
39
  user: parsed.password ? parsed.username : "",
40
40
  pass: parsed.password,
41
- token: parsed.username && !parsed.password ? parsed.username : undefined
41
+ token: parsed.username && !parsed.password ? parsed.username : undefined,
42
42
  };
43
43
 
44
44
  return {
45
45
  ...basic,
46
- ...Object.fromEntries(parsed.searchParams)
46
+ ...Object.fromEntries(parsed.searchParams),
47
47
  };
48
48
  }
49
49
 
@@ -51,7 +51,9 @@ const BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
51
51
  const LOG256_LOG62 = Math.log(256) / Math.log(62); // ≈ 1.3437
52
52
 
53
53
  export function encodeBase62(buffer) {
54
- if (buffer.length === 0) { return ""; }
54
+ if (buffer.length === 0) {
55
+ return "";
56
+ }
55
57
  const len = Math.ceil(buffer.length * LOG256_LOG62);
56
58
  let n = BigInt("0x" + buffer.toString("hex"));
57
59
  const chars = new Array(len);
@@ -73,17 +75,17 @@ export const picoid = (size = 16) => encodeBase62(randomBytes(size));
73
75
 
74
76
  /**
75
77
  * Get SHA256 hash of a string or a buffer
76
- * @param {string|Buffer} data
77
- * @returns
78
+ * @param {string|Buffer} data
79
+ * @returns
78
80
  */
79
81
  export const hash = (data) => createHash("sha256").update(data).digest("hex");
80
82
 
81
83
  /**
82
84
  * Create TCP or HTTP server at specified port
83
- * @param {number} port
84
- * @param {Object} pkg
85
- * @param {Function} handler
86
- * @returns
85
+ * @param {number} port
86
+ * @param {Object} pkg
87
+ * @param {Function} handler
88
+ * @returns
87
89
  */
88
90
  export const createServerAtPort = (port, pkg, handler) =>
89
91
  new Promise((resolve) => {
@@ -131,7 +133,7 @@ export const proxyHttp = (target, req, res) =>
131
133
  const remoteTarget = `${target}${req.originalUrl}`;
132
134
  const options = {
133
135
  method: req.method,
134
- headers: req.headers
136
+ headers: req.headers,
135
137
  };
136
138
 
137
139
  const protocol = req.protocol === "https" ? https : http;
@@ -151,11 +153,12 @@ export const proxyHttp = (target, req, res) =>
151
153
 
152
154
  /**
153
155
  * Create a object proxy that overlays overlay object
154
- * @param {*} object
155
- * @param {*} overlay
156
- * @returns
156
+ * @param {*} object
157
+ * @param {*} overlay
158
+ * @returns
157
159
  */
158
- export const OverlayObject = (object, overlay) => new Proxy(object, { get: (t, p) => overlay[p] !== undefined ? overlay[p] : t[p] });
160
+ export const OverlayObject = (object, overlay) =>
161
+ new Proxy(object, { get: (t, p) => (overlay[p] !== undefined ? overlay[p] : t[p]) });
159
162
 
160
163
  /**
161
164
  * The version string of the currently installed Geonix package, read from `package.json` at
@@ -173,24 +176,25 @@ export const GeonixVersion = (() => {
173
176
 
174
177
  /**
175
178
  * Chunk a stream into smaller chunks
176
- *
177
- * @param {*} chunkSize
178
- * @returns
179
+ *
180
+ * @param {*} chunkSize
181
+ * @returns
179
182
  */
180
- export const StreamChunker = (chunkSize = 65536) => new Transform({
181
- transform(chunk, _encoding, done) {
182
- let offset = 0;
183
- while (offset < chunk.length) {
184
- const sliceSize = Math.min(chunkSize, chunk.length - offset);
185
- this.push(chunk.slice(offset, offset + sliceSize));
186
- offset += sliceSize;
187
- }
188
- done();
189
- },
190
- flush(done) {
191
- done();
192
- }
193
- });
183
+ export const StreamChunker = (chunkSize = 65536) =>
184
+ new Transform({
185
+ transform(chunk, _encoding, done) {
186
+ let offset = 0;
187
+ while (offset < chunk.length) {
188
+ const sliceSize = Math.min(chunkSize, chunk.length - offset);
189
+ this.push(chunk.slice(offset, offset + sliceSize));
190
+ offset += sliceSize;
191
+ }
192
+ done();
193
+ },
194
+ flush(done) {
195
+ done();
196
+ },
197
+ });
194
198
 
195
199
  export async function getFirstItemFromAsyncIterable(asyncIterable) {
196
200
  const iterator = asyncIterable[Symbol.asyncIterator]();
@@ -246,7 +250,7 @@ export async function parseMultipart(req, _options) {
246
250
  const END_OF_HEADERS = Buffer.from("\r\n\r\n");
247
251
  const options = {
248
252
  useMemory: false,
249
- ..._options
253
+ ..._options,
250
254
  };
251
255
  const parts = [];
252
256
  let stream = req;
@@ -308,7 +312,7 @@ export async function parseMultipart(req, _options) {
308
312
  headers: {},
309
313
  bodyFile: options.useMemory ? undefined : bodyFile,
310
314
  body: options.useMemory ? [] : createWriteStream(bodyFile, { flags: "wx" }),
311
- size: 0
315
+ size: 0,
312
316
  };
313
317
  parts.push(activePart);
314
318
  };
@@ -341,7 +345,9 @@ export async function parseMultipart(req, _options) {
341
345
  break;
342
346
  }
343
347
 
344
- const isLastBoundary = combined[boundaryIndex + boundary.length] === 45 && combined[boundaryIndex + boundary.length + 1] === 45;
348
+ const isLastBoundary =
349
+ combined[boundaryIndex + boundary.length] === 45 &&
350
+ combined[boundaryIndex + boundary.length + 1] === 45;
345
351
 
346
352
  if (boundaryIndex > 0) {
347
353
  write(combined.subarray(0, boundaryIndex));
@@ -360,7 +366,8 @@ export async function parseMultipart(req, _options) {
360
366
  }
361
367
 
362
368
  activePart.headers = combined
363
- .subarray(boundaryIndex + boundary.length + 2, endOfHeaders).toString()
369
+ .subarray(boundaryIndex + boundary.length + 2, endOfHeaders)
370
+ .toString()
364
371
  .split("\r\n")
365
372
  .reduce((acc, val) => {
366
373
  const [header, value] = val.split(": ");
@@ -404,7 +411,7 @@ export async function parseMultipart(req, _options) {
404
411
  try {
405
412
  await unlink(part.bodyFile);
406
413
  } catch {
407
- // ignore errors
414
+ // ignore errors
408
415
  }
409
416
  });
410
417
  }
package/src/WebServer.js CHANGED
@@ -50,7 +50,6 @@ export const ServeStatic = (root, options = {}) => {
50
50
  };
51
51
 
52
52
  class WebServer {
53
-
54
53
  #app = express();
55
54
  #server;
56
55
  #port;
@@ -60,7 +59,9 @@ class WebServer {
60
59
  #routers = [];
61
60
 
62
61
  async start() {
63
- if (this.#started) { return; }
62
+ if (this.#started) {
63
+ return;
64
+ }
64
65
 
65
66
  this.#started = true;
66
67
 
@@ -116,15 +117,14 @@ class WebServer {
116
117
  router = await new Promise((resolve, reject) => {
117
118
  currentResolve = resolve;
118
119
 
119
- router(req, res,
120
- (error, _req, _res, _next) => {
121
- if (error) {
122
- return reject(error);
123
- }
120
+ router(req, res, (error, _req, _res, _next) => {
121
+ if (error) {
122
+ return reject(error);
123
+ }
124
124
 
125
- // go to next router
126
- resolve(routers.shift());
127
- });
125
+ // go to next router
126
+ resolve(routers.shift());
127
+ });
128
128
  });
129
129
  }
130
130
 
@@ -138,7 +138,7 @@ class WebServer {
138
138
  this.#app.all("*", (req, res) => {
139
139
  res.status(404).send({
140
140
  error: 404,
141
- source: "ws"
141
+ source: "ws",
142
142
  });
143
143
  });
144
144
 
@@ -175,7 +175,6 @@ class WebServer {
175
175
  logger.info("gx.webserver.stop");
176
176
  }
177
177
  }
178
-
179
178
  }
180
179
 
181
180
  export const webserver = new WebServer();
@@ -1,11 +0,0 @@
1
- {
2
- "editor.codeActionsOnSave": {
3
- "source.fixAll.eslint": "explicit"
4
- },
5
- "eslint.validate": [
6
- "javascript"
7
- ],
8
- "editor.tabSize": 4,
9
- "editor.detectIndentation": false,
10
- "editor.formatOnSave": true
11
- }