@mulingai-npm/redis 3.40.44 → 3.40.46

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.
@@ -1,4 +1,5 @@
1
1
  import { RedisClient } from '../redis-client';
2
+ import type { CacheVersionResolver } from './page-content-manager';
2
3
  export interface CachedAggregatedContent {
3
4
  id: string;
4
5
  locale: string;
@@ -10,8 +11,11 @@ export interface CachedAggregatedContent {
10
11
  }
11
12
  export declare class AggregatedContentManager {
12
13
  private redis;
14
+ private resolveVersion?;
13
15
  private static DEFAULT_TTL;
14
- constructor(redis: RedisClient);
16
+ constructor(redis: RedisClient, resolveVersion?: CacheVersionResolver);
17
+ /** `v<version>--`, or empty when no resolver was supplied. See PageContentManager. */
18
+ private versionSegment;
15
19
  private key;
16
20
  private build;
17
21
  private scan;
@@ -2,11 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AggregatedContentManager = void 0;
4
4
  class AggregatedContentManager {
5
- constructor(redis) {
5
+ constructor(redis, resolveVersion) {
6
6
  this.redis = redis;
7
+ this.resolveVersion = resolveVersion;
8
+ }
9
+ /** `v<version>--`, or empty when no resolver was supplied. See PageContentManager. */
10
+ versionSegment() {
11
+ var _a;
12
+ const version = (_a = this.resolveVersion) === null || _a === void 0 ? void 0 : _a.call(this);
13
+ return version ? `v${version}--` : '';
7
14
  }
8
15
  key(locale, domain) {
9
- return `aggregated-content--[${locale.toLowerCase()}]-[${domain.toLowerCase()}]`;
16
+ return `aggregated-content--${this.versionSegment()}[${locale.toLowerCase()}]-[${domain.toLowerCase()}]`;
10
17
  }
11
18
  build(locale, domain, payload) {
12
19
  const id = this.key(locale, domain);
@@ -41,7 +48,7 @@ class AggregatedContentManager {
41
48
  return raw ? JSON.parse(raw) : null;
42
49
  }
43
50
  async getAllAggregatedContents() {
44
- const keys = await this.scan('aggregated-content--[*');
51
+ const keys = await this.scan('aggregated-content--*[');
45
52
  if (!keys.length)
46
53
  return [];
47
54
  const p = this.redis.pipeline();
@@ -53,7 +60,7 @@ class AggregatedContentManager {
53
60
  await this.redis.unlink(this.key(locale, domain));
54
61
  }
55
62
  async clearAllAggregatedContents() {
56
- const keys = await this.scan('aggregated-content--[*');
63
+ const keys = await this.scan('aggregated-content--*[');
57
64
  if (keys.length)
58
65
  await this.redis.unlink(...keys);
59
66
  }
@@ -36,6 +36,21 @@ export declare class MulingstreamListenerManager {
36
36
  totalListeners: number;
37
37
  languageBreakdown: Record<string, number>;
38
38
  }>;
39
+ /**
40
+ * How many people in this room are ACTUALLY RECEIVING translation right now.
41
+ *
42
+ * Church passes turn on this number rather than on how many people joined. A
43
+ * pass is spent only when translation reached a human being, so somebody
44
+ * sitting on the join screen, or with the tab open and audio stopped, must
45
+ * not burn a church's pass.
46
+ *
47
+ * `isListening` is already maintained by setListeningState, so this is the
48
+ * honest count rather than a proxy for it. Falls back to counting a listener
49
+ * whose flag has never been written, because the flag arrived after some
50
+ * clients shipped and an older client that is genuinely playing audio should
51
+ * still count.
52
+ */
53
+ getReceivingCount(roomId: string): Promise<number>;
39
54
  private capKey;
40
55
  /**
41
56
  * Cache the room's listener cap. Called by the speaker service at go-live and
@@ -239,6 +239,24 @@ class MulingstreamListenerManager {
239
239
  languageBreakdown
240
240
  };
241
241
  }
242
+ /**
243
+ * How many people in this room are ACTUALLY RECEIVING translation right now.
244
+ *
245
+ * Church passes turn on this number rather than on how many people joined. A
246
+ * pass is spent only when translation reached a human being, so somebody
247
+ * sitting on the join screen, or with the tab open and audio stopped, must
248
+ * not burn a church's pass.
249
+ *
250
+ * `isListening` is already maintained by setListeningState, so this is the
251
+ * honest count rather than a proxy for it. Falls back to counting a listener
252
+ * whose flag has never been written, because the flag arrived after some
253
+ * clients shipped and an older client that is genuinely playing audio should
254
+ * still count.
255
+ */
256
+ async getReceivingCount(roomId) {
257
+ const listeners = await this.getListenersByRoom(roomId);
258
+ return listeners.filter((l) => l.isListening !== false).length;
259
+ }
242
260
  // ─── Per-room listener capacity (design B, 2026-07-09) ────────────────────
243
261
  // The cap value is the room owner's plan `max_audience`, resolved ONCE by the
244
262
  // speaker service when it goes live (it already fetches the plan entitlements)
@@ -10,10 +10,28 @@ export interface CachedPageContent {
10
10
  lastUsedAt: number | null;
11
11
  totalUsage: number;
12
12
  }
13
+ /**
14
+ * Returns the namespace the cache should currently write under. Called per
15
+ * operation rather than captured once, so a caller can change it at runtime
16
+ * (local development, where content is edited under a running process).
17
+ */
18
+ export type CacheVersionResolver = () => string;
13
19
  export declare class PageContentManager {
14
20
  private redis;
21
+ private resolveVersion?;
15
22
  private static DEFAULT_TTL;
16
- constructor(redis: RedisClient);
23
+ /**
24
+ * `resolveVersion` namespaces every key by the content it was built from.
25
+ * Content ships inside the service image, so nothing else invalidates this
26
+ * cache on deploy: without the namespace a new build keeps serving the
27
+ * previous build's content until the 30-day TTL expires.
28
+ *
29
+ * Omit it and keys keep their original un-namespaced shape, so an existing
30
+ * deployment behaves exactly as before.
31
+ */
32
+ constructor(redis: RedisClient, resolveVersion?: CacheVersionResolver);
33
+ /** `v<version>--`, or empty when no resolver was supplied. */
34
+ private versionSegment;
17
35
  private key;
18
36
  private build;
19
37
  private scan;
@@ -2,11 +2,27 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PageContentManager = void 0;
4
4
  class PageContentManager {
5
- constructor(redis) {
5
+ /**
6
+ * `resolveVersion` namespaces every key by the content it was built from.
7
+ * Content ships inside the service image, so nothing else invalidates this
8
+ * cache on deploy: without the namespace a new build keeps serving the
9
+ * previous build's content until the 30-day TTL expires.
10
+ *
11
+ * Omit it and keys keep their original un-namespaced shape, so an existing
12
+ * deployment behaves exactly as before.
13
+ */
14
+ constructor(redis, resolveVersion) {
6
15
  this.redis = redis;
16
+ this.resolveVersion = resolveVersion;
17
+ }
18
+ /** `v<version>--`, or empty when no resolver was supplied. */
19
+ versionSegment() {
20
+ var _a;
21
+ const version = (_a = this.resolveVersion) === null || _a === void 0 ? void 0 : _a.call(this);
22
+ return version ? `v${version}--` : '';
7
23
  }
8
24
  key(locale, route, domain) {
9
- return `page-content--[${locale.toLowerCase()}]-[${route.toLowerCase()}]-[${domain.toLowerCase()}]`;
25
+ return `page-content--${this.versionSegment()}[${locale.toLowerCase()}]-[${route.toLowerCase()}]-[${domain.toLowerCase()}]`;
10
26
  }
11
27
  build(locale, route, domain, payload) {
12
28
  const id = this.key(locale, route, domain);
@@ -43,7 +59,7 @@ class PageContentManager {
43
59
  return raw ? JSON.parse(raw) : null;
44
60
  }
45
61
  async getAllPageContents() {
46
- const keys = await this.scan('page-content--[*');
62
+ const keys = await this.scan('page-content--*[');
47
63
  if (!keys.length)
48
64
  return [];
49
65
  const p = this.redis.pipeline();
@@ -52,7 +68,7 @@ class PageContentManager {
52
68
  return res.flatMap(([_, v]) => (v ? [JSON.parse(v)] : []));
53
69
  }
54
70
  async getAllPageContentsByLocale(locale) {
55
- const keys = await this.scan(`page-content--[${locale.toLowerCase()}]-[*`);
71
+ const keys = await this.scan(`page-content--*[${locale.toLowerCase()}]-[*`);
56
72
  if (!keys.length)
57
73
  return [];
58
74
  const p = this.redis.pipeline();
@@ -64,12 +80,12 @@ class PageContentManager {
64
80
  await this.redis.unlink(this.key(locale, route, domain));
65
81
  }
66
82
  async clearAllPageContents() {
67
- const keys = await this.scan('page-content--[*');
83
+ const keys = await this.scan('page-content--*[');
68
84
  if (keys.length)
69
85
  await this.redis.unlink(...keys);
70
86
  }
71
87
  async clearAllPageContentsByLocale(locale) {
72
- const keys = await this.scan(`page-content--[${locale.toLowerCase()}]-[*`);
88
+ const keys = await this.scan(`page-content--*[${locale.toLowerCase()}]-[*`);
73
89
  if (keys.length)
74
90
  await this.redis.unlink(...keys);
75
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulingai-npm/redis",
3
- "version": "3.40.44",
3
+ "version": "3.40.46",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {