svelte-adapter-uws-extensions 0.1.6 → 0.1.7
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 +1 -1
- package/redis/cursor.js +49 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-adapter-uws-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Redis and Postgres extensions for svelte-adapter-uws - distributed pub/sub, replay buffers, presence tracking, rate limiting, groups, and DB change notifications",
|
|
5
5
|
"author": "Kevin Radziszewski",
|
|
6
6
|
"license": "MIT",
|
package/redis/cursor.js
CHANGED
|
@@ -221,6 +221,45 @@ export function createCursor(client, options = {}) {
|
|
|
221
221
|
redis.publish(channel, msg).catch(() => {});
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Flush all coalesced entries for a topic as a single "bulk" event.
|
|
226
|
+
* The client receives one event with all cursor positions instead of
|
|
227
|
+
* N individual events landing in the same microtask. This turns N
|
|
228
|
+
* store updates per frame into one, and reduces Redis PUBLISH calls
|
|
229
|
+
* from N to 1 per flush window.
|
|
230
|
+
*
|
|
231
|
+
* Each entry is still persisted individually to the Redis hash so
|
|
232
|
+
* the per-key TTL and staleness detection work unchanged.
|
|
233
|
+
*/
|
|
234
|
+
function flushBulk(topic, dirty) {
|
|
235
|
+
const entries = [];
|
|
236
|
+
const now = Date.now();
|
|
237
|
+
let flushPlatform = null;
|
|
238
|
+
|
|
239
|
+
for (const [k, v] of dirty) {
|
|
240
|
+
entries.push({ key: k, user: v.user, data: v.data });
|
|
241
|
+
flushPlatform = v.platform;
|
|
242
|
+
// Persist each entry individually to Redis hash
|
|
243
|
+
redis.hset(hashKey(topic), k, JSON.stringify({ user: v.user, data: v.data, ts: now })).catch(() => {});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
redis.expire(hashKey(topic), cursorTtl).catch(() => {});
|
|
247
|
+
|
|
248
|
+
if (flushPlatform) {
|
|
249
|
+
// Single local broadcast with all positions
|
|
250
|
+
flushPlatform.publish('__cursor:' + topic, 'bulk', entries);
|
|
251
|
+
|
|
252
|
+
// Single relay to other instances
|
|
253
|
+
const msg = JSON.stringify({
|
|
254
|
+
instanceId,
|
|
255
|
+
topic,
|
|
256
|
+
event: 'bulk',
|
|
257
|
+
payload: entries
|
|
258
|
+
});
|
|
259
|
+
redis.publish(channel, msg).catch(() => {});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
224
263
|
function broadcast(topic, key, user, data, platform) {
|
|
225
264
|
if (topicThrottleMs <= 0) {
|
|
226
265
|
doBroadcast(topic, key, user, data, platform);
|
|
@@ -243,8 +282,13 @@ export function createCursor(client, options = {}) {
|
|
|
243
282
|
if (now - state.lastFlush >= topicThrottleMs) {
|
|
244
283
|
if (state.timer) { clearTimeout(state.timer); state.timer = null; }
|
|
245
284
|
state.lastFlush = now;
|
|
246
|
-
|
|
285
|
+
if (state.dirty.size === 1) {
|
|
286
|
+
// Single entry: use normal event so the client does not
|
|
287
|
+
// need to handle bulk for the common non-contended case.
|
|
288
|
+
const [k, v] = state.dirty.entries().next().value;
|
|
247
289
|
doBroadcast(topic, k, v.user, v.data, v.platform);
|
|
290
|
+
} else {
|
|
291
|
+
flushBulk(topic, state.dirty);
|
|
248
292
|
}
|
|
249
293
|
state.dirty.clear();
|
|
250
294
|
return;
|
|
@@ -257,8 +301,11 @@ export function createCursor(client, options = {}) {
|
|
|
257
301
|
if (!s) return;
|
|
258
302
|
s.timer = null;
|
|
259
303
|
s.lastFlush = Date.now();
|
|
260
|
-
|
|
304
|
+
if (s.dirty.size === 1) {
|
|
305
|
+
const [k, v] = s.dirty.entries().next().value;
|
|
261
306
|
doBroadcast(topic, k, v.user, v.data, v.platform);
|
|
307
|
+
} else {
|
|
308
|
+
flushBulk(topic, s.dirty);
|
|
262
309
|
}
|
|
263
310
|
s.dirty.clear();
|
|
264
311
|
}, topicThrottleMs - (now - state.lastFlush));
|