@ultimat3/realtime 1.1.0 → 1.2.0
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 +3 -3
- package/src/socket.ts +16 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/realtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Three-tier realtime: channels, live queries, local-first sync — one protocol, one mutator shape",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"test": "bun test"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@ultimat3/core": "1.
|
|
34
|
-
"@ultimat3/query": "1.
|
|
33
|
+
"@ultimat3/core": "1.2.0",
|
|
34
|
+
"@ultimat3/query": "1.2.0"
|
|
35
35
|
}
|
|
36
36
|
}
|
package/src/socket.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// the transport or the change buffer, never here. `sync` is stateless: nothing on this object
|
|
5
5
|
// survives a restart, and nothing needs to.
|
|
6
6
|
|
|
7
|
-
import { type Actor, type Clock, systemClock, uuid } from '@ultimat3/core';
|
|
7
|
+
import { type Actor, type Clock, recordConnection, systemClock, uuid } from '@ultimat3/core';
|
|
8
8
|
import { encode, type Frame } from './sync-protocol';
|
|
9
9
|
|
|
10
10
|
export const CLOSE = {
|
|
@@ -162,12 +162,24 @@ export class SocketRegistry {
|
|
|
162
162
|
this.#idleTimeoutMs = options.idleTimeoutMs ?? 120_000;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* This package's ONE metrics call site, and it is here rather than in `sync-node.ts` because
|
|
167
|
+
* this map is the only definition of "a live connection on this node": a socket ends on the WS
|
|
168
|
+
* close callback, on the idle sweep, on the drain and on a duplicate id, and every one of those
|
|
169
|
+
* paths already had to come through `add`/`remove`. A gauge moved from the callers instead would
|
|
170
|
+
* leak on whichever path a later change forgets — and a connections gauge that only counts up is
|
|
171
|
+
* an HPA that only scales up.
|
|
172
|
+
*/
|
|
165
173
|
add(socket: SyncSocket): void {
|
|
174
|
+
const replacing = this.#sockets.has(socket.id);
|
|
166
175
|
this.#sockets.set(socket.id, socket);
|
|
176
|
+
// A re-added id replaces one connection with another; the count did not change.
|
|
177
|
+
if (!replacing) recordConnection(1);
|
|
167
178
|
}
|
|
168
179
|
|
|
169
180
|
remove(id: string): void {
|
|
170
|
-
|
|
181
|
+
// `Map.delete` answers "was it actually there", so a double close cannot decrement twice.
|
|
182
|
+
if (this.#sockets.delete(id)) recordConnection(-1);
|
|
171
183
|
}
|
|
172
184
|
|
|
173
185
|
get(id: string): SyncSocket | undefined {
|
|
@@ -189,7 +201,8 @@ export class SocketRegistry {
|
|
|
189
201
|
for (const socket of this.#sockets.values()) {
|
|
190
202
|
if (socket.idleFor(now) > this.#idleTimeoutMs) {
|
|
191
203
|
socket.close(CLOSE.idle, 'idle timeout');
|
|
192
|
-
|
|
204
|
+
// Through `remove`, not the map: the sweep is exactly the abnormal close a gauge leaks on.
|
|
205
|
+
this.remove(socket.id);
|
|
193
206
|
closed.push(socket);
|
|
194
207
|
}
|
|
195
208
|
}
|