svelte-adapter-uws-extensions 0.1.8 → 0.1.9

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/redis/presence.js +21 -68
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-adapter-uws-extensions",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
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/presence.js CHANGED
@@ -20,57 +20,32 @@
20
20
  import { randomBytes } from 'node:crypto';
21
21
 
22
22
  /**
23
- * Lua script for atomic join: set this instance's field, expire the key,
24
- * check if the user already exists on another instance (non-stale),
25
- * and return all hash entries so the caller can skip a separate HGETALL.
23
+ * Lua script for atomic join: set this instance's field and expire the key.
26
24
  *
27
25
  * KEYS[1] = hash key
28
26
  * ARGV[1] = field to set (instanceId|userKey)
29
27
  * ARGV[2] = field value (JSON with data and ts)
30
- * ARGV[3] = "|userKey" suffix to match
31
- * ARGV[4] = now (ms)
32
- * ARGV[5] = presenceTtlMs
33
- * ARGV[6] = presenceTtl (seconds, for EXPIRE)
28
+ * ARGV[3] = presenceTtl (seconds, for EXPIRE)
34
29
  *
35
- * Returns {isFirst, field1, val1, field2, val2, ...} so a single round
36
- * trip handles HSET + EXPIRE + dedup check + HGETALL.
30
+ * Cross-instance dedup is intentionally omitted. The local localCounts
31
+ * map already prevents duplicate joins from the same user on the same
32
+ * instance (the script only runs when prevCount === 0). If the same
33
+ * user joins on a second instance, both broadcast "join" -- the client
34
+ * handles this as an idempotent Map.set on the same key.
35
+ *
36
+ * Removing the HGETALL + O(N) scan that was here before drops per-join
37
+ * Redis work from O(N) to O(1), fixing the O(N^2) total cost that
38
+ * killed the server at 2000+ concurrent joins.
37
39
  */
38
40
  const JOIN_SCRIPT = `
39
41
  local key = KEYS[1]
40
42
  local field = ARGV[1]
41
43
  local value = ARGV[2]
42
- local suffix = ARGV[3]
43
- local now = tonumber(ARGV[4])
44
- local ttlMs = tonumber(ARGV[5])
45
- local ttlSec = tonumber(ARGV[6])
44
+ local ttlSec = tonumber(ARGV[3])
46
45
 
47
46
  redis.call('hset', key, field, value)
48
47
  redis.call('expire', key, ttlSec)
49
-
50
- local all = redis.call('hgetall', key)
51
- local isFirst = 1
52
- for i = 1, #all, 2 do
53
- local f = all[i]
54
- if f ~= field and #f >= #suffix and string.sub(f, -#suffix) == suffix then
55
- local ok, parsed = pcall(cjson.decode, all[i+1])
56
- if ok and parsed.ts and (now - parsed.ts) <= ttlMs then
57
- isFirst = 0
58
- break
59
- end
60
- end
61
- end
62
-
63
- -- For small hashes return entries inline (saves a round trip).
64
- -- For large hashes return only the flag; the caller fetches the
65
- -- list via a coalesced HGETALL shared across concurrent joiners.
66
- if #all <= 200 then
67
- local result = {isFirst}
68
- for i = 1, #all do
69
- result[#result + 1] = all[i]
70
- end
71
- return result
72
- end
73
- return {isFirst}
48
+ return 1
74
49
  `;
75
50
 
76
51
  /**
@@ -416,21 +391,14 @@ export function createPresence(client, options = {}) {
416
391
  // cleaned local state -- undo any Redis write and bail out.
417
392
  if (!wsTopics.has(ws)) return;
418
393
 
419
- let all;
420
-
421
394
  if (prevCount === 0) {
422
- // New user on this instance -- single Lua call does
423
- // HSET + EXPIRE + dedup check. For small hashes the
424
- // script returns all entries inline (1 round trip).
425
- // For large hashes it returns only the dedup flag and
426
- // we fetch the list via coalesceHgetall below.
395
+ // New user on this instance -- HSET + EXPIRE in one Lua call.
427
396
  const now = Date.now();
428
397
  const field = compoundField(key);
429
398
  const value = JSON.stringify({ data, ts: now });
430
- const suffix = '|' + key;
431
- const result = await redis.eval(
399
+ await redis.eval(
432
400
  JOIN_SCRIPT, 1, hashKey(topic),
433
- field, value, suffix, now, presenceTtlMs, presenceTtl
401
+ field, value, presenceTtl
434
402
  );
435
403
 
436
404
  // Guard: if ws closed while awaiting Redis, remove the field
@@ -440,29 +408,14 @@ export function createPresence(client, options = {}) {
440
408
  return;
441
409
  }
442
410
 
443
- const isFirstGlobally = result[0];
444
-
445
- if (result.length > 1) {
446
- // Small hash: entries returned inline
447
- all = {};
448
- for (let i = 1; i < result.length; i += 2) {
449
- all[result[i]] = result[i + 1];
450
- }
451
- }
452
-
453
- if (isFirstGlobally === 1) {
454
- const payload = { key, data };
455
- platform.publish('__presence:' + topic, 'join', payload);
456
- await publishEvent(topic, 'join', payload);
457
- }
411
+ const payload = { key, data };
412
+ platform.publish('__presence:' + topic, 'join', payload);
413
+ await publishEvent(topic, 'join', payload);
458
414
  }
459
415
 
460
- // Fetch initial list via coalesced HGETALL when the script
461
- // did not return entries inline (large hash or dedup join).
416
+ // Fetch initial list via coalesced HGETALL.
462
417
  // Concurrent joiners share a single Redis round trip.
463
- if (!all) {
464
- all = await coalesceHgetall(topic);
465
- }
418
+ const all = await coalesceHgetall(topic);
466
419
 
467
420
  // Subscribe ws to presence channel (may have closed during async gap)
468
421
  try {