@einaraglen/quorum 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -16,7 +16,7 @@ When a pod starts up it runs an election: it contacts every peer with a higher n
16
16
 
17
17
  ### Transport — the wire
18
18
 
19
- All coordination travels over a single internal HTTP port (default `4001`). Each pod runs a small Express server on that port with five endpoints:
19
+ All coordination travels over a single internal HTTP port (default `4001`). Each pod runs a small tinyhttp server on that port with five endpoints:
20
20
 
21
21
  | Route | Purpose |
22
22
  | ---------------------------- | --------------------------------------------- |
@@ -41,7 +41,9 @@ Four messaging patterns are available, each with a different routing model:
41
41
 
42
42
  All four deliver to the pod's local `channel` EventEmitter when the target happens to be self, with no network round-trip.
43
43
 
44
- `distribute(event, resolvePayload)` is a leader-only helper for partitioning work: it calls your callback once per peer (sorted by name for stability) and delivers the return value to each pod as a `channel` event. Useful for sending each pod its own slice of a large dataset without the leader needing to know each pod's address explicitly.
44
+ `distribute(event, resolvePayload)` is a leader-only helper for partitioning work: it calls your callback once per peer (sorted by name for stability) and delivers the return value to each pod as a `channel` event. Useful for sending each pod its own slice of a large dataset without the leader needing to know each pod's address explicitly. It's fire-and-forget — it doesn't wait for peers to acknowledge or finish, and a peer that's briefly unreachable just silently misses its share.
45
+
46
+ `distributeAndCollect(event, resolvePayload, responseEvent, responseTimeoutMs?)` is the same idea, but waits up to `responseTimeoutMs` (default `2000`) for peers to answer back on `responseEvent` and resolves with a `Map<peerName, response>`. A peer that never responds is simply absent from the map — this never rejects on a missing or slow peer. Each peer answers by calling `forum.send(responseEvent, { peer: self, response })`, since responses aren't automatically tagged with who sent them.
45
47
 
46
48
  `subscribeToPeer(peer, event, onData)` opens a live SSE connection to a named peer and delivers every event it emits under that name. Returns an unsubscribe function. Targets itself locally — no loopback HTTP connection.
47
49
 
@@ -8,6 +8,14 @@ export type ForumOptions = {
8
8
  fetchFn?: typeof fetch;
9
9
  logger?: Logger;
10
10
  };
11
+ /**
12
+ * The shape a peer must respond with for distributeAndCollect() to attribute its answer —
13
+ * responses aren't automatically tagged with who sent them, so the sender embeds its own name.
14
+ */
15
+ export type DistributeResponse<R> = {
16
+ peer: string;
17
+ response: R;
18
+ };
11
19
  export declare class Forum {
12
20
  private readonly bully;
13
21
  private readonly transport;
@@ -31,6 +39,17 @@ export declare class Forum {
31
39
  * called once per participant, in a stable order, and gets back that pod's own share to send.
32
40
  */
33
41
  distribute<T>(event: string, resolvePayload: (peer: BullyPeer, index: number, allPeers: BullyPeer[]) => T): Promise<void>;
42
+ /**
43
+ * Leader-only: like distribute(), but waits up to `responseTimeoutMs` for each peer to answer
44
+ * back on `responseEvent` and returns whatever came in. A peer that never responds is simply
45
+ * absent from the map — this never rejects or throws on a missing/slow peer.
46
+ *
47
+ * Each peer must respond with:
48
+ * `forum.send(responseEvent, { peer: self, response } satisfies DistributeResponse<R>)`
49
+ * since responses aren't automatically tagged with who sent them (the same convention
50
+ * ShardManager's own holdings-report collection uses internally).
51
+ */
52
+ distributeAndCollect<T, R>(event: string, resolvePayload: (peer: BullyPeer, index: number, allPeers: BullyPeer[]) => T, responseEvent: string, responseTimeoutMs?: number): Promise<Map<string, R>>;
34
53
  /**
35
54
  * Peer-to-peer: open a live subscription to everything a specific peer emits under one
36
55
  * channel event name (self included, via a local listener — no loopback HTTP call needed).
@@ -1 +1 @@
1
- {"version":3,"file":"forum.d.ts","sourceRoot":"","sources":["../../src/core/forum.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,YAAY,IAAI,EAAE,YAAY,EAM7B;IAED,sFAAsF;IACzE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtE;IAED,+EAA+E;IAClE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjE;IAED,kFAAkF;IACrE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnF;IAED;;;OAGG;IACU,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAa1F;IAED;;;OAGG;IACU,UAAU,CAAC,CAAC,EACvB,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,GAC3E,OAAO,CAAC,IAAI,CAAC,CAoBf;IAED;;;;OAIG;IACI,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CA+DtG;CACF"}
1
+ {"version":3,"file":"forum.d.ts","sourceRoot":"","sources":["../../src/core/forum.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,CAAA;CAAE,CAAC;AAElE,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,YAAY,IAAI,EAAE,YAAY,EAM7B;IAED,sFAAsF;IACzE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtE;IAED,+EAA+E;IAClE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjE;IAED,kFAAkF;IACrE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnF;IAED;;;OAGG;IACU,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAa1F;IAED;;;OAGG;IACU,UAAU,CAAC,CAAC,EACvB,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,GAC3E,OAAO,CAAC,IAAI,CAAC,CAoBf;IAED;;;;;;;;;OASG;IACU,oBAAoB,CAAC,CAAC,EAAE,CAAC,EACpC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,EAC5E,aAAa,EAAE,MAAM,EACrB,iBAAiB,SAAO,GACvB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAiBzB;IAED;;;;OAIG;IACI,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CA+DtG;CACF"}
@@ -86,6 +86,31 @@ export class Forum {
86
86
  return this.transport.postToPeer(peer.host, "/bully/message", { event, payload });
87
87
  }));
88
88
  }
89
+ /**
90
+ * Leader-only: like distribute(), but waits up to `responseTimeoutMs` for each peer to answer
91
+ * back on `responseEvent` and returns whatever came in. A peer that never responds is simply
92
+ * absent from the map — this never rejects or throws on a missing/slow peer.
93
+ *
94
+ * Each peer must respond with:
95
+ * `forum.send(responseEvent, { peer: self, response } satisfies DistributeResponse<R>)`
96
+ * since responses aren't automatically tagged with who sent them (the same convention
97
+ * ShardManager's own holdings-report collection uses internally).
98
+ */
99
+ async distributeAndCollect(event, resolvePayload, responseEvent, responseTimeoutMs = 2000) {
100
+ if (!this.bully.isLeader()) {
101
+ this.logger.warn(`distributeAndCollect('${event}') called while not leader, ignoring`);
102
+ return new Map();
103
+ }
104
+ const responses = new Map();
105
+ const collector = (payload) => {
106
+ responses.set(payload.peer, payload.response);
107
+ };
108
+ this.bully.channel.on(responseEvent, collector);
109
+ await this.distribute(event, resolvePayload);
110
+ await new Promise((resolve) => setTimeout(resolve, responseTimeoutMs));
111
+ this.bully.channel.off(responseEvent, collector);
112
+ return responses;
113
+ }
89
114
  /**
90
115
  * Peer-to-peer: open a live subscription to everything a specific peer emits under one
91
116
  * channel event name (self included, via a local listener — no loopback HTTP call needed).
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/core/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,oBAAoB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,OAAO,EAAE,YAAY,CAAC;CACvB,CAAC;AAEF,qBAAa,SAAS;IACpB,SAAgB,OAAO,EAAE,OAAO,KAAK,CAAC;IACtC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,MAAM,CAAC,CAAS;IAExB,YAAY,IAAI,GAAE,gBAAqB,EAMtC;IAEY,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAYnF;IAEY,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASpD;IAEM,KAAK,CAAC,EAAE,EAAE,kBAAkB,GAAG,IAAI,CA4CzC;IAEM,IAAI,IAAI,IAAI,CAElB;CACF"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/core/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,oBAAoB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,OAAO,EAAE,YAAY,CAAC;CACvB,CAAC;AAQF,qBAAa,SAAS;IACpB,SAAgB,OAAO,EAAE,OAAO,KAAK,CAAC;IACtC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,MAAM,CAAC,CAA4B;IAE3C,YAAY,IAAI,GAAE,gBAAqB,EAMtC;IAEY,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAYnF;IAEY,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASpD;IAEM,KAAK,CAAC,EAAE,EAAE,kBAAkB,GAAG,IAAI,CA8CzC;IAEM,IAAI,IAAI,IAAI,CAElB;CACF"}
@@ -1,4 +1,10 @@
1
- import express from "express";
1
+ import { App } from "@tinyhttp/app";
2
+ const readJsonBody = async (req) => {
3
+ const chunks = [];
4
+ for await (const chunk of req)
5
+ chunks.push(chunk);
6
+ return chunks.length === 0 ? {} : JSON.parse(Buffer.concat(chunks).toString("utf8"));
7
+ };
2
8
  export class Transport {
3
9
  fetchFn;
4
10
  port;
@@ -39,23 +45,23 @@ export class Transport {
39
45
  }
40
46
  }
41
47
  start(cb) {
42
- const app = express();
43
- app.use(express.json());
44
- app.get("/health", (_, res) => {
48
+ const app = new App();
49
+ const onJsonMessage = (handle) => async (req, res) => {
50
+ try {
51
+ handle(await readJsonBody(req));
52
+ res.sendStatus(200);
53
+ }
54
+ catch (err) {
55
+ this.logger.error(err);
56
+ res.status(500).json({ status: "ERROR", error: err?.message ?? String(err) });
57
+ }
58
+ };
59
+ app.get("/health", (_req, res) => {
45
60
  res.json({ status: "OK", timestamp: new Date().toISOString() });
46
61
  });
47
- app.post("/bully/election", (req, res) => {
48
- cb.onElectionMessage(req.body?.id);
49
- res.sendStatus(200);
50
- });
51
- app.post("/bully/coordinator", (req, res) => {
52
- cb.onCoordinatorMessage(req.body?.id);
53
- res.sendStatus(200);
54
- });
55
- app.post("/bully/message", (req, res) => {
56
- cb.onMessage(req.body?.event, req.body?.payload);
57
- res.sendStatus(200);
58
- });
62
+ app.post("/bully/election", onJsonMessage((body) => cb.onElectionMessage(body?.id)));
63
+ app.post("/bully/coordinator", onJsonMessage((body) => cb.onCoordinatorMessage(body?.id)));
64
+ app.post("/bully/message", onJsonMessage((body) => cb.onMessage(body?.event, body?.payload)));
59
65
  app.get("/channel/stream/:event", (req, res) => {
60
66
  const eventName = req.params.event;
61
67
  res.writeHead(200, {
@@ -67,13 +73,9 @@ export class Transport {
67
73
  cb.channel.on(eventName, forward);
68
74
  req.on("close", () => cb.channel.off(eventName, forward));
69
75
  });
70
- app.use((err, _req, res, _next) => {
71
- this.logger.error(err);
72
- res.status(500).json({ status: "ERROR", error: err?.message ?? String(err) });
73
- });
74
76
  const onListening = () => this.logger.info(`Internal coordination server listening on port ${this.port}`);
75
77
  this.server = this.internalHost
76
- ? app.listen(this.port, this.internalHost, onListening)
78
+ ? app.listen(this.port, onListening, this.internalHost)
77
79
  : app.listen(this.port, onListening);
78
80
  }
79
81
  stop() {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { QuorumOptions } from "./core/quorum";
3
3
  export { Bully } from "./core/bully";
4
4
  export type { BullyPeer, BullyCluster, Discovery, BullyOptions } from "./core/bully";
5
5
  export { Forum } from "./core/forum";
6
- export type { ForumOptions } from "./core/forum";
6
+ export type { ForumOptions, DistributeResponse } from "./core/forum";
7
7
  export { Transport } from "./core/transport";
8
8
  export type { TransportOptions, TransportCallbacks } from "./core/transport";
9
9
  export { Sharding } from "./core/sharding";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAErE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@einaraglen/quorum",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Distributed pod coordination: Bully leader election and work sharding",
5
5
  "keywords": [
6
6
  "leader-election",
@@ -36,7 +36,6 @@
36
36
  "check": "npm run format:check && npm run lint"
37
37
  },
38
38
  "devDependencies": {
39
- "@types/express": "^5.0.6",
40
39
  "@types/node": "^24.9.2",
41
40
  "oxlint": "^1.80.0",
42
41
  "prettier": "^3.9.6",
@@ -44,6 +43,6 @@
44
43
  "typescript": "^7.0.2"
45
44
  },
46
45
  "dependencies": {
47
- "express": "^5.2.1"
46
+ "@tinyhttp/app": "^3.0.11"
48
47
  }
49
48
  }