@jskit-ai/realtime 0.1.27 → 0.1.29

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,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/realtime",
4
- version: "0.1.27",
4
+ version: "0.1.29",
5
5
  kind: "runtime",
6
6
  description: "Thin, generic realtime runtime wrappers for socket.io server and client.",
7
7
  options: {
@@ -95,7 +95,7 @@ export default Object.freeze({
95
95
  mutations: {
96
96
  dependencies: {
97
97
  runtime: {
98
- "@jskit-ai/kernel": "0.1.28",
98
+ "@jskit-ai/kernel": "0.1.30",
99
99
  "@socket.io/redis-adapter": "^8.3.0",
100
100
  "redis": "^5.8.2",
101
101
  "socket.io": "^4.8.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/realtime",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@socket.io/redis-adapter": "^8.3.0",
19
- "@jskit-ai/kernel": "0.1.28",
19
+ "@jskit-ai/kernel": "0.1.30",
20
20
  "redis": "^5.8.2",
21
21
  "socket.io": "^4.8.3",
22
22
  "socket.io-client": "^4.8.3"
@@ -8,6 +8,7 @@ import {
8
8
  createSocketIoServer,
9
9
  closeSocketIoServer,
10
10
  resolveRealtimeRedisUrl,
11
+ resolveRealtimeRedisNamespace,
11
12
  configureSocketIoRedisAdapter,
12
13
  closeSocketIoRedisConnections
13
14
  } from "./runtime.js";
@@ -684,8 +685,10 @@ class RealtimeServiceProvider {
684
685
 
685
686
  const env = typeof app.has === "function" && app.has("jskit.env") ? app.make("jskit.env") : {};
686
687
  const redisUrl = resolveRealtimeRedisUrl(env);
688
+ const redisNamespace = resolveRealtimeRedisNamespace(env);
687
689
  this.redisConnection = await configureSocketIoRedisAdapter(this.socketIoServer, {
688
- redisUrl
690
+ redisUrl,
691
+ redisNamespace
689
692
  });
690
693
  }
691
694
 
@@ -4,7 +4,8 @@ import { createClient as createRedisClient } from "redis";
4
4
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
5
5
 
6
6
  const SOCKET_IO_PATH = "/socket.io";
7
- const REALTIME_REDIS_URL_ENV_KEY = "REALTIME_REDIS_URL";
7
+ const REDIS_URL_ENV_KEY = "REDIS_URL";
8
+ const REDIS_NAMESPACE_ENV_KEY = "REDIS_NAMESPACE";
8
9
 
9
10
  function resolveHttpServer({ httpServer = null, fastify = null } = {}) {
10
11
  if (httpServer && typeof httpServer === "object") {
@@ -59,18 +60,39 @@ async function closeSocketIoServer(io) {
59
60
 
60
61
  function resolveRealtimeRedisUrl(env = {}) {
61
62
  const source = env && typeof env === "object" && !Array.isArray(env) ? env : {};
62
- return normalizeText(source[REALTIME_REDIS_URL_ENV_KEY]);
63
+ return normalizeText(source[REDIS_URL_ENV_KEY]);
64
+ }
65
+
66
+ function resolveRealtimeRedisNamespace(env = {}) {
67
+ const source = env && typeof env === "object" && !Array.isArray(env) ? env : {};
68
+ return normalizeText(source[REDIS_NAMESPACE_ENV_KEY]);
69
+ }
70
+
71
+ function buildSocketIoRedisAdapterKey(redisNamespace = "") {
72
+ const normalizedRedisNamespace = normalizeText(redisNamespace).replace(/:+$/g, "");
73
+ if (!normalizedRedisNamespace) {
74
+ return "";
75
+ }
76
+ return `${normalizedRedisNamespace}:socket.io`;
63
77
  }
64
78
 
65
79
  async function configureSocketIoRedisAdapter(
66
80
  io,
67
- { redisUrl = "" } = {}
81
+ {
82
+ redisUrl = "",
83
+ redisNamespace = "",
84
+ createRedisAdapter = createSocketIoRedisAdapter,
85
+ createRedisConnection = createRedisClient
86
+ } = {}
68
87
  ) {
69
88
  const normalizedRedisUrl = normalizeText(redisUrl);
89
+ const adapterKey = buildSocketIoRedisAdapterKey(redisNamespace);
70
90
  if (!normalizedRedisUrl) {
71
91
  return Object.freeze({
72
92
  enabled: false,
73
93
  redisUrl: "",
94
+ redisNamespace: "",
95
+ adapterKey: "",
74
96
  pubClient: null,
75
97
  subClient: null
76
98
  });
@@ -79,7 +101,7 @@ async function configureSocketIoRedisAdapter(
79
101
  throw new Error("configureSocketIoRedisAdapter requires socket.io server instance with adapter().");
80
102
  }
81
103
 
82
- const pubClient = createRedisClient({
104
+ const pubClient = createRedisConnection({
83
105
  url: normalizedRedisUrl
84
106
  });
85
107
  const subClient = pubClient.duplicate();
@@ -87,7 +109,8 @@ async function configureSocketIoRedisAdapter(
87
109
  try {
88
110
  await pubClient.connect();
89
111
  await subClient.connect();
90
- io.adapter(createSocketIoRedisAdapter(pubClient, subClient));
112
+ const adapterOptions = adapterKey ? { key: adapterKey } : undefined;
113
+ io.adapter(createRedisAdapter(pubClient, subClient, adapterOptions));
91
114
  } catch (error) {
92
115
  await closeSocketIoRedisConnections({
93
116
  pubClient,
@@ -99,6 +122,8 @@ async function configureSocketIoRedisAdapter(
99
122
  return Object.freeze({
100
123
  enabled: true,
101
124
  redisUrl: normalizedRedisUrl,
125
+ redisNamespace: normalizeText(redisNamespace),
126
+ adapterKey,
102
127
  pubClient,
103
128
  subClient
104
129
  });
@@ -127,8 +152,10 @@ async function closeSocketIoRedisConnections({ pubClient = null, subClient = nul
127
152
  export {
128
153
  createSocketIoServer,
129
154
  closeSocketIoServer,
130
- REALTIME_REDIS_URL_ENV_KEY,
155
+ REDIS_URL_ENV_KEY,
156
+ REDIS_NAMESPACE_ENV_KEY,
131
157
  resolveRealtimeRedisUrl,
158
+ resolveRealtimeRedisNamespace,
132
159
  configureSocketIoRedisAdapter,
133
160
  closeSocketIoRedisConnections
134
161
  };
@@ -2,10 +2,12 @@ import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
 
4
4
  import {
5
- REALTIME_REDIS_URL_ENV_KEY,
5
+ REDIS_URL_ENV_KEY,
6
+ REDIS_NAMESPACE_ENV_KEY,
6
7
  createSocketIoServer,
7
8
  closeSocketIoServer,
8
9
  resolveRealtimeRedisUrl,
10
+ resolveRealtimeRedisNamespace,
9
11
  configureSocketIoRedisAdapter,
10
12
  closeSocketIoRedisConnections
11
13
  } from "../src/server/runtime.js";
@@ -108,7 +110,15 @@ test("closeSocketIoServer swallows ERR_SERVER_NOT_RUNNING", async () => {
108
110
 
109
111
  test("resolveRealtimeRedisUrl reads and normalizes env URL", () => {
110
112
  assert.equal(resolveRealtimeRedisUrl({}), "");
111
- assert.equal(resolveRealtimeRedisUrl({ [REALTIME_REDIS_URL_ENV_KEY]: " redis://localhost:6379 " }), "redis://localhost:6379");
113
+ assert.equal(resolveRealtimeRedisUrl({ [REDIS_URL_ENV_KEY]: " redis://localhost:6379 " }), "redis://localhost:6379");
114
+ });
115
+
116
+ test("resolveRealtimeRedisNamespace reads and normalizes env namespace", () => {
117
+ assert.equal(resolveRealtimeRedisNamespace({}), "");
118
+ assert.equal(
119
+ resolveRealtimeRedisNamespace({ [REDIS_NAMESPACE_ENV_KEY]: " app:production " }),
120
+ "app:production"
121
+ );
112
122
  });
113
123
 
114
124
  test("configureSocketIoRedisAdapter keeps memory mode when URL is empty", async () => {
@@ -147,3 +157,49 @@ test("closeSocketIoRedisConnections closes both clients when present", async ()
147
157
 
148
158
  assert.deepEqual(calls, ["sub.quit", "pub.quit"]);
149
159
  });
160
+
161
+ test("configureSocketIoRedisAdapter applies namespaced adapter key when redis namespace is set", async () => {
162
+ const adapterCalls = [];
163
+ const io = {
164
+ adapter(adapter) {
165
+ adapterCalls.push(adapter);
166
+ }
167
+ };
168
+ const connectionCalls = [];
169
+ const createRedisConnection = ({ url }) => {
170
+ const client = {
171
+ url,
172
+ async connect() {
173
+ connectionCalls.push(`${url}:connect`);
174
+ },
175
+ async quit() {},
176
+ duplicate() {
177
+ return {
178
+ async connect() {
179
+ connectionCalls.push(`${url}:duplicate.connect`);
180
+ },
181
+ async quit() {}
182
+ };
183
+ }
184
+ };
185
+ return client;
186
+ };
187
+ const createRedisAdapter = (pubClient, subClient, options) => ({
188
+ pubClient,
189
+ subClient,
190
+ options
191
+ });
192
+
193
+ const result = await configureSocketIoRedisAdapter(io, {
194
+ redisUrl: "redis://localhost:6379",
195
+ redisNamespace: "my-app:production",
196
+ createRedisConnection,
197
+ createRedisAdapter
198
+ });
199
+
200
+ assert.equal(connectionCalls.length, 2);
201
+ assert.equal(adapterCalls.length, 1);
202
+ assert.equal(adapterCalls[0].options?.key, "my-app:production:socket.io");
203
+ assert.equal(result.adapterKey, "my-app:production:socket.io");
204
+ assert.equal(result.redisNamespace, "my-app:production");
205
+ });