@prsm/realtime 1.0.1 → 1.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prsm/realtime",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Distributed WebSocket framework with Redis-backed rooms, records, presence, channels, collections, and persistence",
5
5
  "type": "module",
6
6
  "license": "ISC",
@@ -5,9 +5,9 @@ export function createPostgresAdapter(options = {}) {
5
5
  const opts = {
6
6
  host: "localhost",
7
7
  port: 5432,
8
- database: "mesh_test",
9
- user: "mesh",
10
- password: "mesh_password",
8
+ database: "realtime_test",
9
+ user: "realtime",
10
+ password: "realtime_password",
11
11
  max: 10,
12
12
  ...options,
13
13
  }
@@ -169,10 +169,10 @@ export class RealtimeClient extends EventEmitter {
169
169
  async getConnectionMetadata(connectionId) {
170
170
  try {
171
171
  if (connectionId) {
172
- const result = await this.command("mesh/get-connection-metadata", { connectionId })
172
+ const result = await this.command("rt/get-connection-metadata", { connectionId })
173
173
  return result.metadata
174
174
  }
175
- const result = await this.command("mesh/get-my-connection-metadata")
175
+ const result = await this.command("rt/get-my-connection-metadata")
176
176
  return result.metadata
177
177
  } catch (error) {
178
178
  clientLogger.error(`Failed to get metadata for connection:`, error)
@@ -187,7 +187,7 @@ export class RealtimeClient extends EventEmitter {
187
187
  */
188
188
  async setConnectionMetadata(metadata, options) {
189
189
  try {
190
- const result = await this.command("mesh/set-my-connection-metadata", { metadata, options })
190
+ const result = await this.command("rt/set-my-connection-metadata", { metadata, options })
191
191
  return result.success
192
192
  } catch (error) {
193
193
  clientLogger.error(`Failed to set metadata for connection:`, error)
@@ -199,11 +199,11 @@ export class RealtimeClient extends EventEmitter {
199
199
  this.connection.on("message", (data) => {
200
200
  this.emit("message", data)
201
201
 
202
- if (data.command === "mesh/record-update") this._records.handleUpdate(data.payload)
203
- else if (data.command === "mesh/record-deleted") this._records.handleDeleted(data.payload)
204
- else if (data.command === "mesh/presence-update") this._presence.handleUpdate(data.payload)
205
- else if (data.command === "mesh/subscription-message") this._channels.handleMessage(data.payload)
206
- else if (data.command === "mesh/collection-diff") this._collections.handleDiff(data.payload)
202
+ if (data.command === "rt/record-update") this._records.handleUpdate(data.payload)
203
+ else if (data.command === "rt/record-deleted") this._records.handleDeleted(data.payload)
204
+ else if (data.command === "rt/presence-update") this._presence.handleUpdate(data.payload)
205
+ else if (data.command === "rt/subscription-message") this._channels.handleMessage(data.payload)
206
+ else if (data.command === "rt/collection-diff") this._collections.handleDiff(data.payload)
207
207
  else {
208
208
  const systemCommands = ["ping", "pong", "latency", "latency:request", "latency:response"]
209
209
  if (data.command && !systemCommands.includes(data.command)) {
@@ -238,7 +238,7 @@ export class RealtimeClient extends EventEmitter {
238
238
  this._lastActivityTime = Date.now()
239
239
  if (eventName === "visibilitychange" && doc.visibilityState === "visible") {
240
240
  if (this._status === Status.OFFLINE) return
241
- this.command("mesh/noop", {}, 5000)
241
+ this.command("rt/noop", {}, 5000)
242
242
  .then(() => { clientLogger.info("Tab visible, connection ok"); this.emit("republish") })
243
243
  .catch(() => { clientLogger.info("Tab visible, forcing reconnect"); this._forceReconnect() })
244
244
  }
@@ -253,7 +253,7 @@ export class RealtimeClient extends EventEmitter {
253
253
  const now = Date.now()
254
254
  const timeSinceActivity = now - this._lastActivityTime
255
255
  if (timeSinceActivity > this.options.pingTimeout && this._status === Status.ONLINE) {
256
- this.command("mesh/noop", {}, 5000).catch(() => {
256
+ this.command("rt/noop", {}, 5000).catch(() => {
257
257
  clientLogger.info(`No activity for ${timeSinceActivity}ms, forcing reconnect`)
258
258
  this._forceReconnect()
259
259
  })
@@ -91,7 +91,7 @@ export class Connection extends EventEmitter {
91
91
 
92
92
  this.emit("message", data);
93
93
 
94
- if (data.command === "mesh/assign-id") {
94
+ if (data.command === "rt/assign-id") {
95
95
  this.connectionId = data.payload;
96
96
  this.emit("id-assigned", data.payload);
97
97
  } else if (data.command === "latency:request") {
@@ -23,7 +23,7 @@ export function createChannelSubscriptions(client) {
23
23
  */
24
24
  async function subscribe(channel, callback, options) {
25
25
  subscriptions.set(channel, { callback, historyLimit: options?.historyLimit })
26
- const result = await client.command("mesh/subscribe-channel", {
26
+ const result = await client.command("rt/subscribe-channel", {
27
27
  channel,
28
28
  historyLimit: options?.historyLimit,
29
29
  since: options?.since,
@@ -40,7 +40,7 @@ export function createChannelSubscriptions(client) {
40
40
  */
41
41
  function unsubscribe(channel) {
42
42
  subscriptions.delete(channel)
43
- return client.command("mesh/unsubscribe-channel", { channel })
43
+ return client.command("rt/unsubscribe-channel", { channel })
44
44
  }
45
45
 
46
46
  /**
@@ -50,7 +50,7 @@ export function createChannelSubscriptions(client) {
50
50
  */
51
51
  async function getHistory(channel, options) {
52
52
  try {
53
- const result = await client.command("mesh/get-channel-history", {
53
+ const result = await client.command("rt/get-channel-history", {
54
54
  channel,
55
55
  limit: options?.limit,
56
56
  since: options?.since,
@@ -42,7 +42,7 @@ export function createCollectionSubscriptions(client) {
42
42
  */
43
43
  async function subscribe(collectionId, options = {}) {
44
44
  try {
45
- const result = await client.command("mesh/subscribe-collection", { collectionId })
45
+ const result = await client.command("rt/subscribe-collection", { collectionId })
46
46
  if (result.success) {
47
47
  subscriptions.set(collectionId, {
48
48
  ids: new Set(result.ids),
@@ -70,7 +70,7 @@ export function createCollectionSubscriptions(client) {
70
70
  */
71
71
  async function unsubscribe(collectionId) {
72
72
  try {
73
- const success = await client.command("mesh/unsubscribe-collection", { collectionId })
73
+ const success = await client.command("rt/unsubscribe-collection", { collectionId })
74
74
  if (success) subscriptions.delete(collectionId)
75
75
  return success
76
76
  } catch (error) {
@@ -16,7 +16,7 @@ export function createPresenceSubscriptions(client) {
16
16
  */
17
17
  async function subscribe(roomName, callback) {
18
18
  try {
19
- const result = await client.command("mesh/subscribe-presence", { roomName })
19
+ const result = await client.command("rt/subscribe-presence", { roomName })
20
20
  if (result.success) {
21
21
  subscriptions.set(roomName, callback)
22
22
  if (result.present && result.present.length > 0) await callback(result)
@@ -34,7 +34,7 @@ export function createPresenceSubscriptions(client) {
34
34
  */
35
35
  async function unsubscribe(roomName) {
36
36
  try {
37
- const success = await client.command("mesh/unsubscribe-presence", { roomName })
37
+ const success = await client.command("rt/unsubscribe-presence", { roomName })
38
38
  if (success) subscriptions.delete(roomName)
39
39
  return success
40
40
  } catch (error) {
@@ -50,7 +50,7 @@ export function createPresenceSubscriptions(client) {
50
50
  */
51
51
  async function publishState(roomName, options) {
52
52
  try {
53
- return await client.command("mesh/publish-presence-state", {
53
+ return await client.command("rt/publish-presence-state", {
54
54
  roomName,
55
55
  state: options.state,
56
56
  expireAfter: options.expireAfter,
@@ -68,7 +68,7 @@ export function createPresenceSubscriptions(client) {
68
68
  */
69
69
  async function clearState(roomName) {
70
70
  try {
71
- return await client.command("mesh/clear-presence-state", { roomName })
71
+ return await client.command("rt/clear-presence-state", { roomName })
72
72
  } catch (error) {
73
73
  clientLogger.error(`Failed to clear presence state for room ${roomName}:`, error)
74
74
  return false
@@ -79,7 +79,7 @@ export function createPresenceSubscriptions(client) {
79
79
  try {
80
80
  const handler = subscriptions.get(roomName)
81
81
  if (!handler) return false
82
- const result = await client.command("mesh/get-presence-state", { roomName }, 5000).catch((err) => {
82
+ const result = await client.command("rt/get-presence-state", { roomName }, 5000).catch((err) => {
83
83
  clientLogger.error(`Failed to get presence state for room ${roomName}:`, err)
84
84
  return { success: false }
85
85
  })
@@ -63,7 +63,7 @@ export function createRecordSubscriptions(client) {
63
63
  async function subscribe(recordId, callback, options) {
64
64
  const mode = options?.mode ?? "full"
65
65
  try {
66
- const result = await client.command("mesh/subscribe-record", { recordId, mode })
66
+ const result = await client.command("rt/subscribe-record", { recordId, mode })
67
67
  if (result.success) {
68
68
  subscriptions.set(recordId, { callback, localVersion: result.version, mode })
69
69
  if (callback) await callback({ recordId, full: result.record, version: result.version })
@@ -81,7 +81,7 @@ export function createRecordSubscriptions(client) {
81
81
  */
82
82
  async function unsubscribe(recordId) {
83
83
  try {
84
- const success = await client.command("mesh/unsubscribe-record", { recordId })
84
+ const success = await client.command("rt/unsubscribe-record", { recordId })
85
85
  if (success) subscriptions.delete(recordId)
86
86
  return success
87
87
  } catch (error) {
@@ -98,7 +98,7 @@ export function createRecordSubscriptions(client) {
98
98
  */
99
99
  async function write(recordId, newValue, options) {
100
100
  try {
101
- const result = await client.command("mesh/publish-record-update", { recordId, newValue, options })
101
+ const result = await client.command("rt/publish-record-update", { recordId, newValue, options })
102
102
  return result.success === true
103
103
  } catch (error) {
104
104
  clientLogger.error(`Failed to publish update for record ${recordId}:`, error)
@@ -9,7 +9,7 @@ export function createRoomSubscriptions(client, presence) {
9
9
  * @returns {Promise<{success: boolean, present: string[]}>}
10
10
  */
11
11
  async function join(roomName, onPresenceUpdate) {
12
- const joinResult = await client.command("mesh/join-room", { roomName })
12
+ const joinResult = await client.command("rt/join-room", { roomName })
13
13
  if (!joinResult.success) return { success: false, present: [] }
14
14
 
15
15
  joinedRooms.set(roomName, onPresenceUpdate)
@@ -25,7 +25,7 @@ export function createRoomSubscriptions(client, presence) {
25
25
  * @returns {Promise<{success: boolean}>}
26
26
  */
27
27
  async function leave(roomName) {
28
- const result = await client.command("mesh/leave-room", { roomName })
28
+ const result = await client.command("rt/leave-room", { roomName })
29
29
  if (result.success) {
30
30
  joinedRooms.delete(roomName)
31
31
  if (client.presenceSubscriptions.has(roomName)) {
@@ -41,7 +41,7 @@ export function createRoomSubscriptions(client, presence) {
41
41
  */
42
42
  async function getMetadata(roomName) {
43
43
  try {
44
- const result = await client.command("mesh/get-room-metadata", { roomName })
44
+ const result = await client.command("rt/get-room-metadata", { roomName })
45
45
  return result.metadata
46
46
  } catch (error) {
47
47
  clientLogger.error(`Failed to get metadata for room ${roomName}:`, error)
@@ -36,8 +36,8 @@ export class ChannelManager {
36
36
  const serialized = typeof message === "string" ? message : JSON.stringify(message)
37
37
  const parsedHistory = parseInt(history, 10)
38
38
  if (!isNaN(parsedHistory) && parsedHistory > 0) {
39
- await this.pubClient.rpush(`mesh:history:${channel}`, serialized)
40
- await this.pubClient.ltrim(`mesh:history:${channel}`, -parsedHistory, -1)
39
+ await this.pubClient.rpush(`rt:history:${channel}`, serialized)
40
+ await this.pubClient.ltrim(`rt:history:${channel}`, -parsedHistory, -1)
41
41
  }
42
42
  this.messageStream.publishMessage(channel, serialized, instanceId)
43
43
  await this.pubClient.publish(channel, serialized)
@@ -89,11 +89,11 @@ export class ChannelManager {
89
89
  const messages = await this.persistenceManager.getMessages(channel, since, limit)
90
90
  return messages.map((msg) => msg.message)
91
91
  } catch {
92
- const historyKey = `mesh:history:${channel}`
92
+ const historyKey = `rt:history:${channel}`
93
93
  return this.redis.lrange(historyKey, 0, limit - 1)
94
94
  }
95
95
  }
96
- const historyKey = `mesh:history:${channel}`
96
+ const historyKey = `rt:history:${channel}`
97
97
  return this.redis.lrange(historyKey, 0, limit - 1)
98
98
  }
99
99
 
@@ -38,7 +38,7 @@ export class CollectionManager {
38
38
  const ids = records.map((record) => record.id)
39
39
  const version = 1
40
40
  this.collectionSubscriptions.get(collectionId).set(connectionId, { version })
41
- await this.redis.set(`mesh:collection:${collectionId}:${connectionId}`, JSON.stringify(ids))
41
+ await this.redis.set(`rt:collection:${collectionId}:${connectionId}`, JSON.stringify(ids))
42
42
  return { ids, records, version }
43
43
  }
44
44
 
@@ -47,7 +47,7 @@ export class CollectionManager {
47
47
  if (collectionSubs?.has(connectionId)) {
48
48
  collectionSubs.delete(connectionId)
49
49
  if (collectionSubs.size === 0) this.collectionSubscriptions.delete(collectionId)
50
- await this.redis.del(`mesh:collection:${collectionId}:${connectionId}`)
50
+ await this.redis.del(`rt:collection:${collectionId}:${connectionId}`)
51
51
  return true
52
52
  }
53
53
  return false
@@ -55,7 +55,7 @@ export class CollectionManager {
55
55
 
56
56
  async publishRecordChange(recordId) {
57
57
  try {
58
- await this.redis.publish("mesh:collection:record-change", recordId)
58
+ await this.redis.publish("rt:collection:record-change", recordId)
59
59
  } catch (error) {
60
60
  this.emitError(new Error(`Failed to publish record change for ${recordId}: ${error}`))
61
61
  }
@@ -69,7 +69,7 @@ export class CollectionManager {
69
69
  subscribers.delete(connectionId)
70
70
  if (subscribers.size === 0) this.collectionSubscriptions.delete(collectionId)
71
71
  cleanupPromises.push(
72
- this.redis.del(`mesh:collection:${collectionId}:${connectionId}`).then(() => {}).catch((err) => {
72
+ this.redis.del(`rt:collection:${collectionId}:${connectionId}`).then(() => {}).catch((err) => {
73
73
  this.emitError(new Error(`Failed to clean up collection subscription for "${collectionId}": ${err}`))
74
74
  })
75
75
  )
@@ -79,7 +79,7 @@ export class CollectionManager {
79
79
 
80
80
  async listRecordsMatching(pattern, options) {
81
81
  try {
82
- const recordKeyPrefix = "mesh:record:"
82
+ const recordKeyPrefix = "rt:record:"
83
83
  const keys = []
84
84
  let cursor = "0"
85
85
  do {
@@ -1,8 +1,8 @@
1
1
  import { deepMerge, isObject } from "../../shared/index.js"
2
2
 
3
- const CONNECTIONS_HASH_KEY = "mesh:connections"
4
- const CONNECTIONS_META_HASH_KEY = "mesh:connection-meta"
5
- const INSTANCE_CONNECTIONS_KEY_PREFIX = "mesh:connections:"
3
+ const CONNECTIONS_HASH_KEY = "rt:connections"
4
+ const CONNECTIONS_META_HASH_KEY = "rt:connection-meta"
5
+ const INSTANCE_CONNECTIONS_KEY_PREFIX = "rt:connections:"
6
6
 
7
7
  export class ConnectionManager {
8
8
  constructor({ redis, instanceId, roomManager }) {
@@ -28,38 +28,38 @@ export class InstanceManager {
28
28
  }
29
29
 
30
30
  async _registerInstance() {
31
- await this.redis.sadd("mesh:instances", this.instanceId)
31
+ await this.redis.sadd("rt:instances", this.instanceId)
32
32
  }
33
33
 
34
34
  async _deregisterInstance() {
35
- await this.redis.srem("mesh:instances", this.instanceId)
36
- await this.redis.del(`mesh:instance:${this.instanceId}:heartbeat`)
35
+ await this.redis.srem("rt:instances", this.instanceId)
36
+ await this.redis.del(`rt:instance:${this.instanceId}:heartbeat`)
37
37
  }
38
38
 
39
39
  async _updateHeartbeat() {
40
- await this.redis.set(`mesh:instance:${this.instanceId}:heartbeat`, Date.now().toString(), "EX", this.heartbeatTTL)
40
+ await this.redis.set(`rt:instance:${this.instanceId}:heartbeat`, Date.now().toString(), "EX", this.heartbeatTTL)
41
41
  }
42
42
 
43
43
  async _acquireCleanupLock() {
44
- const result = await this.redis.set("mesh:cleanup:lock", this.instanceId, "EX", this.cleanupLockTTL, "NX")
44
+ const result = await this.redis.set("rt:cleanup:lock", this.instanceId, "EX", this.cleanupLockTTL, "NX")
45
45
  return result === "OK"
46
46
  }
47
47
 
48
48
  async _releaseCleanupLock() {
49
49
  const script = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end'
50
- await this.redis.eval(script, 1, "mesh:cleanup:lock", this.instanceId)
50
+ await this.redis.eval(script, 1, "rt:cleanup:lock", this.instanceId)
51
51
  }
52
52
 
53
53
  async _performCleanup() {
54
54
  try {
55
55
  const lockAcquired = await this._acquireCleanupLock()
56
56
  if (!lockAcquired) return
57
- const registeredInstances = await this.redis.smembers("mesh:instances")
58
- const allConnections = await this.redis.hgetall("mesh:connections")
57
+ const registeredInstances = await this.redis.smembers("rt:instances")
58
+ const allConnections = await this.redis.hgetall("rt:connections")
59
59
  const instanceIds = new Set([...registeredInstances, ...Object.values(allConnections)])
60
60
  for (const instanceId of instanceIds) {
61
61
  if (instanceId === this.instanceId) continue
62
- const heartbeat = await this.redis.get(`mesh:instance:${instanceId}:heartbeat`)
62
+ const heartbeat = await this.redis.get(`rt:instance:${instanceId}:heartbeat`)
63
63
  if (!heartbeat) {
64
64
  serverLogger.info(`Found dead instance: ${instanceId}`)
65
65
  await this._cleanupDeadInstance(instanceId)
@@ -74,16 +74,16 @@ export class InstanceManager {
74
74
 
75
75
  async _cleanupDeadInstance(instanceId) {
76
76
  try {
77
- const connectionsKey = `mesh:connections:${instanceId}`
77
+ const connectionsKey = `rt:connections:${instanceId}`
78
78
  const connections = await this.redis.smembers(connectionsKey)
79
79
  for (const connectionId of connections) {
80
80
  await this._cleanupConnection(connectionId)
81
81
  }
82
- const allConnections = await this.redis.hgetall("mesh:connections")
82
+ const allConnections = await this.redis.hgetall("rt:connections")
83
83
  for (const [connectionId, connInstanceId] of Object.entries(allConnections)) {
84
84
  if (connInstanceId === instanceId) await this._cleanupConnection(connectionId)
85
85
  }
86
- await this.redis.srem("mesh:instances", instanceId)
86
+ await this.redis.srem("rt:instances", instanceId)
87
87
  await this.redis.del(connectionsKey)
88
88
  serverLogger.info(`Cleaned up dead instance: ${instanceId}`)
89
89
  } catch (error) {
@@ -103,19 +103,19 @@ export class InstanceManager {
103
103
 
104
104
  async _cleanupConnection(connectionId) {
105
105
  try {
106
- const roomsKey = `mesh:connection:${connectionId}:rooms`
106
+ const roomsKey = `rt:connection:${connectionId}:rooms`
107
107
  const rooms = await this.redis.smembers(roomsKey)
108
108
  const pipeline = this.redis.pipeline()
109
109
  for (const room of rooms) {
110
- pipeline.srem(`mesh:room:${room}`, connectionId)
111
- pipeline.srem(`mesh:presence:room:${room}`, connectionId)
112
- pipeline.del(`mesh:presence:room:${room}:conn:${connectionId}`)
113
- pipeline.del(`mesh:presence:state:${room}:conn:${connectionId}`)
110
+ pipeline.srem(`rt:room:${room}`, connectionId)
111
+ pipeline.srem(`rt:presence:room:${room}`, connectionId)
112
+ pipeline.del(`rt:presence:room:${room}:conn:${connectionId}`)
113
+ pipeline.del(`rt:presence:state:${room}:conn:${connectionId}`)
114
114
  }
115
115
  pipeline.del(roomsKey)
116
- pipeline.hdel("mesh:connections", connectionId)
117
- pipeline.hdel("mesh:connection-meta", connectionId)
118
- await this._deleteMatchingKeys(`mesh:collection:*:${connectionId}`)
116
+ pipeline.hdel("rt:connections", connectionId)
117
+ pipeline.hdel("rt:connection-meta", connectionId)
118
+ await this._deleteMatchingKeys(`rt:collection:*:${connectionId}`)
119
119
  await pipeline.exec()
120
120
  serverLogger.debug(`Cleaned up stale connection: ${connectionId}`)
121
121
  } catch (error) {
@@ -7,8 +7,8 @@ export class PresenceManager {
7
7
  this.redisManager = redisManager
8
8
  this.presenceExpirationEventsEnabled = enableExpirationEvents
9
9
 
10
- this.PRESENCE_KEY_PATTERN = /^mesh:presence:room:(.+):conn:(.+)$/
11
- this.PRESENCE_STATE_KEY_PATTERN = /^mesh:presence:state:(.+):conn:(.+)$/
10
+ this.PRESENCE_KEY_PATTERN = /^rt:presence:room:(.+):conn:(.+)$/
11
+ this.PRESENCE_STATE_KEY_PATTERN = /^rt:presence:state:(.+):conn:(.+)$/
12
12
  this.trackedRooms = []
13
13
  this.roomGuards = new Map()
14
14
  this.roomTTLs = new Map()
@@ -89,9 +89,9 @@ export class PresenceManager {
89
89
  return this.defaultTTL
90
90
  }
91
91
 
92
- presenceRoomKey(roomName) { return `mesh:presence:room:${roomName}` }
93
- presenceConnectionKey(roomName, connectionId) { return `mesh:presence:room:${roomName}:conn:${connectionId}` }
94
- presenceStateKey(roomName, connectionId) { return `mesh:presence:state:${roomName}:conn:${connectionId}` }
92
+ presenceRoomKey(roomName) { return `rt:presence:room:${roomName}` }
93
+ presenceConnectionKey(roomName, connectionId) { return `rt:presence:room:${roomName}:conn:${connectionId}` }
94
+ presenceStateKey(roomName, connectionId) { return `rt:presence:state:${roomName}:conn:${connectionId}` }
95
95
 
96
96
  async markOnline(connectionId, roomName) {
97
97
  const roomKey = this.presenceRoomKey(roomName)
@@ -137,7 +137,7 @@ export class PresenceManager {
137
137
  }
138
138
 
139
139
  async _publishPresenceUpdate(roomName, connectionId, type) {
140
- const channel = `mesh:presence:updates:${roomName}`
140
+ const channel = `rt:presence:updates:${roomName}`
141
141
  const message = JSON.stringify({ type, connectionId, roomName, timestamp: Date.now() })
142
142
  await this.redis.publish(channel, message)
143
143
  }
@@ -192,7 +192,7 @@ export class PresenceManager {
192
192
  }
193
193
 
194
194
  async _publishPresenceStateUpdate(roomName, connectionId, state) {
195
- const channel = `mesh:presence:updates:${roomName}`
195
+ const channel = `rt:presence:updates:${roomName}`
196
196
  const message = JSON.stringify({ type: "state", connectionId, roomName, state, timestamp: Date.now() })
197
197
  await this.redis.publish(channel, message)
198
198
  }
@@ -23,8 +23,8 @@ export class PubSubManager {
23
23
  subscribeToInstanceChannel() {
24
24
  const channel = `${PUB_SUB_CHANNEL_PREFIX}${this.instanceId}`
25
25
  this._subscriptionPromise = new Promise((resolve, reject) => {
26
- this.subClient.subscribe(channel, RECORD_PUB_SUB_CHANNEL, "mesh:collection:record-change")
27
- this.subClient.psubscribe("mesh:presence:updates:*", (err) => {
26
+ this.subClient.subscribe(channel, RECORD_PUB_SUB_CHANNEL, "rt:collection:record-change")
27
+ this.subClient.psubscribe("rt:presence:updates:*", (err) => {
28
28
  if (err) {
29
29
  this.emitError(new Error(`Failed to subscribe to channels/patterns: ${JSON.stringify({ cause: err })}`))
30
30
  reject(err)
@@ -43,14 +43,14 @@ export class PubSubManager {
43
43
  this._handleInstancePubSubMessage(channel, message)
44
44
  } else if (channel === RECORD_PUB_SUB_CHANNEL) {
45
45
  this._handleRecordUpdatePubSubMessage(message)
46
- } else if (channel === "mesh:collection:record-change") {
46
+ } else if (channel === "rt:collection:record-change") {
47
47
  this._handleCollectionRecordChange(message)
48
48
  } else {
49
49
  const subscribers = this.getChannelSubscriptions(channel)
50
50
  if (subscribers) {
51
51
  for (const connection of subscribers) {
52
52
  if (!connection.isDead) {
53
- connection.send({ command: "mesh/subscription-message", payload: { channel, message } })
53
+ connection.send({ command: "rt/subscription-message", payload: { channel, message } })
54
54
  }
55
55
  }
56
56
  }
@@ -58,14 +58,14 @@ export class PubSubManager {
58
58
  })
59
59
 
60
60
  this.subClient.on("pmessage", async (pattern, channel, message) => {
61
- if (pattern === "mesh:presence:updates:*") {
61
+ if (pattern === "rt:presence:updates:*") {
62
62
  const subscribers = this.getChannelSubscriptions(channel)
63
63
  if (subscribers) {
64
64
  try {
65
65
  const payload = JSON.parse(message)
66
66
  subscribers.forEach((connection) => {
67
67
  if (!connection.isDead) {
68
- connection.send({ command: "mesh/presence-update", payload })
68
+ connection.send({ command: "rt/presence-update", payload })
69
69
  } else {
70
70
  subscribers.delete(connection)
71
71
  }
@@ -105,11 +105,11 @@ export class PubSubManager {
105
105
  const connection = this.connectionManager.getLocalConnection(connectionId)
106
106
  if (connection && !connection.isDead) {
107
107
  if (deleted) {
108
- connection.send({ command: "mesh/record-deleted", payload: { recordId, version } })
108
+ connection.send({ command: "rt/record-deleted", payload: { recordId, version } })
109
109
  } else if (mode === "patch" && patch) {
110
- connection.send({ command: "mesh/record-update", payload: { recordId, patch, version } })
110
+ connection.send({ command: "rt/record-update", payload: { recordId, patch, version } })
111
111
  } else if (mode === "full" && newValue !== undefined) {
112
- connection.send({ command: "mesh/record-update", payload: { recordId, full: newValue, version } })
112
+ connection.send({ command: "rt/record-update", payload: { recordId, full: newValue, version } })
113
113
  }
114
114
  } else if (!connection || connection.isDead) {
115
115
  subscribers.delete(connectionId)
@@ -168,7 +168,7 @@ export class PubSubManager {
168
168
 
169
169
  const newRecords = await this.collectionManager.resolveCollection(collectionId, connection)
170
170
  const newRecordIds = newRecords.map((record) => record.id)
171
- const previousRecordIdsKey = `mesh:collection:${collectionId}:${connectionId}`
171
+ const previousRecordIdsKey = `rt:collection:${collectionId}:${connectionId}`
172
172
  const previousRecordIdsStr = await this.pubClient.get(previousRecordIdsKey)
173
173
  const previousRecordIds = previousRecordIdsStr ? JSON.parse(previousRecordIdsStr) : []
174
174
 
@@ -199,7 +199,7 @@ export class PubSubManager {
199
199
  this.collectionManager.updateSubscriptionVersion(collectionId, connectionId, newCollectionVersion)
200
200
  await this.pubClient.set(previousRecordIdsKey, JSON.stringify(newRecordIds))
201
201
  connection.send({
202
- command: "mesh/collection-diff",
202
+ command: "rt/collection-diff",
203
203
  payload: { collectionId, added, removed, version: newCollectionVersion },
204
204
  })
205
205
  }
@@ -209,7 +209,7 @@ export class PubSubManager {
209
209
  try {
210
210
  const { record, version } = await this.recordManager.getRecordAndVersion(recordId)
211
211
  if (record) {
212
- connection.send({ command: "mesh/record-update", payload: { recordId, version, full: record } })
212
+ connection.send({ command: "rt/record-update", payload: { recordId, version, full: record } })
213
213
  }
214
214
  } catch (recordError) {
215
215
  serverLogger.info(`Record ${recordId} not found during collection update (likely deleted).`)
@@ -234,8 +234,8 @@ export class PubSubManager {
234
234
  if (this.subClient && this.subClient.status !== "end") {
235
235
  const channel = `${PUB_SUB_CHANNEL_PREFIX}${this.instanceId}`
236
236
  await Promise.all([
237
- new Promise((resolve) => { this.subClient.unsubscribe(channel, RECORD_PUB_SUB_CHANNEL, "mesh:collection:record-change", () => resolve()) }),
238
- new Promise((resolve) => { this.subClient.punsubscribe("mesh:presence:updates:*", () => resolve()) }),
237
+ new Promise((resolve) => { this.subClient.unsubscribe(channel, RECORD_PUB_SUB_CHANNEL, "rt:collection:record-change", () => resolve()) }),
238
+ new Promise((resolve) => { this.subClient.punsubscribe("rt:presence:updates:*", () => resolve()) }),
239
239
  ])
240
240
  }
241
241
  }
@@ -5,9 +5,9 @@ export class RoomManager {
5
5
  this.redis = redis
6
6
  }
7
7
 
8
- roomKey(roomName) { return `mesh:room:${roomName}` }
9
- connectionsRoomKey(connectionId) { return `mesh:connection:${connectionId}:rooms` }
10
- roomMetadataKey(roomName) { return `mesh:roommeta:${roomName}` }
8
+ roomKey(roomName) { return `rt:room:${roomName}` }
9
+ connectionsRoomKey(connectionId) { return `rt:connection:${connectionId}:rooms` }
10
+ roomMetadataKey(roomName) { return `rt:roommeta:${roomName}` }
11
11
 
12
12
  async getRoomConnectionIds(roomName) {
13
13
  return this.redis.smembers(this.roomKey(roomName))
@@ -30,8 +30,8 @@ export class RoomManager {
30
30
  }
31
31
 
32
32
  async getAllRooms() {
33
- const keys = await this.redis.keys("mesh:room:*")
34
- return keys.map((key) => key.replace("mesh:room:", ""))
33
+ const keys = await this.redis.keys("rt:room:*")
34
+ return keys.map((key) => key.replace("rt:room:", ""))
35
35
  }
36
36
 
37
37
  async removeFromRoom(roomName, connection) {
@@ -110,14 +110,14 @@ export class RoomManager {
110
110
  }
111
111
 
112
112
  async getAllMetadata() {
113
- const keys = await this.redis.keys("mesh:roommeta:*")
113
+ const keys = await this.redis.keys("rt:roommeta:*")
114
114
  const result = []
115
115
  if (keys.length === 0) return result
116
116
  const pipeline = this.redis.pipeline()
117
117
  keys.forEach((key) => pipeline.hget(key, "data"))
118
118
  const results = await pipeline.exec()
119
119
  keys.forEach((key, index) => {
120
- const roomName = key.replace("mesh:roommeta:", "")
120
+ const roomName = key.replace("rt:roommeta:", "")
121
121
  const data = results?.[index]?.[1]
122
122
  if (data) {
123
123
  try { result.push({ id: roomName, metadata: JSON.parse(data) }) }
@@ -292,7 +292,7 @@ export class RealtimeServer {
292
292
  pendingAuthDataStore.delete(req)
293
293
  await this.connectionManager.setMetadata(connection, authData)
294
294
  }
295
- connection.send({ command: "mesh/assign-id", payload: connection.id })
295
+ connection.send({ command: "rt/assign-id", payload: connection.id })
296
296
  } catch (error) {
297
297
  connection.close()
298
298
  return
@@ -490,7 +490,7 @@ export class RealtimeServer {
490
490
  if (connection) {
491
491
  metadata = await this.connectionManager.getMetadata(connection)
492
492
  } else {
493
- const metadataString = await this.redisManager.redis.hget("mesh:connection-meta", connectionId)
493
+ const metadataString = await this.redisManager.redis.hget("rt:connection-meta", connectionId)
494
494
  metadata = metadataString ? JSON.parse(metadataString) : null
495
495
  }
496
496
  return { id: connectionId, metadata }
@@ -582,9 +582,9 @@ export class RealtimeServer {
582
582
  }
583
583
 
584
584
  _registerBuiltinCommands() {
585
- this.exposeCommand("mesh/noop", async () => true)
585
+ this.exposeCommand("rt/noop", async () => true)
586
586
 
587
- this.exposeCommand("mesh/subscribe-channel", async (ctx) => {
587
+ this.exposeCommand("rt/subscribe-channel", async (ctx) => {
588
588
  const { channel, historyLimit, since } = ctx.payload
589
589
  if (!(await this.channelManager.isChannelExposed(channel, ctx.connection))) {
590
590
  return { success: false, history: [] }
@@ -601,7 +601,7 @@ export class RealtimeServer {
601
601
  }
602
602
  })
603
603
 
604
- this.exposeCommand("mesh/unsubscribe-channel", async (ctx) => {
604
+ this.exposeCommand("rt/unsubscribe-channel", async (ctx) => {
605
605
  const { channel } = ctx.payload
606
606
  const wasSubscribed = this.channelManager.removeSubscription(channel, ctx.connection)
607
607
  if (wasSubscribed && !this.channelManager.getSubscribers(channel)) {
@@ -610,7 +610,7 @@ export class RealtimeServer {
610
610
  return wasSubscribed
611
611
  })
612
612
 
613
- this.exposeCommand("mesh/get-channel-history", async (ctx) => {
613
+ this.exposeCommand("rt/get-channel-history", async (ctx) => {
614
614
  const { channel, limit, since } = ctx.payload
615
615
  if (!(await this.channelManager.isChannelExposed(channel, ctx.connection))) {
616
616
  return { success: false, history: [] }
@@ -630,44 +630,44 @@ export class RealtimeServer {
630
630
  }
631
631
  })
632
632
 
633
- this.exposeCommand("mesh/join-room", async (ctx) => {
633
+ this.exposeCommand("rt/join-room", async (ctx) => {
634
634
  const { roomName } = ctx.payload
635
635
  await this.addToRoom(roomName, ctx.connection)
636
636
  const present = await this.getRoomMembersWithMetadata(roomName)
637
637
  return { success: true, present }
638
638
  })
639
639
 
640
- this.exposeCommand("mesh/leave-room", async (ctx) => {
640
+ this.exposeCommand("rt/leave-room", async (ctx) => {
641
641
  const { roomName } = ctx.payload
642
642
  await this.removeFromRoom(roomName, ctx.connection)
643
643
  return { success: true }
644
644
  })
645
645
 
646
- this.exposeCommand("mesh/get-connection-metadata", async (ctx) => {
646
+ this.exposeCommand("rt/get-connection-metadata", async (ctx) => {
647
647
  const { connectionId } = ctx.payload
648
648
  const connection = this.connectionManager.getLocalConnection(connectionId)
649
649
  if (connection) {
650
650
  const metadata = await this.connectionManager.getMetadata(connection)
651
651
  return { metadata }
652
652
  } else {
653
- const metadata = await this.redisManager.redis.hget("mesh:connection-meta", connectionId)
653
+ const metadata = await this.redisManager.redis.hget("rt:connection-meta", connectionId)
654
654
  return { metadata: metadata ? JSON.parse(metadata) : null }
655
655
  }
656
656
  })
657
657
 
658
- this.exposeCommand("mesh/get-my-connection-metadata", async (ctx) => {
658
+ this.exposeCommand("rt/get-my-connection-metadata", async (ctx) => {
659
659
  const connectionId = ctx.connection.id
660
660
  const connection = this.connectionManager.getLocalConnection(connectionId)
661
661
  if (connection) {
662
662
  const metadata = await this.connectionManager.getMetadata(connection)
663
663
  return { metadata }
664
664
  } else {
665
- const metadata = await this.redisManager.redis.hget("mesh:connection-meta", connectionId)
665
+ const metadata = await this.redisManager.redis.hget("rt:connection-meta", connectionId)
666
666
  return { metadata: metadata ? JSON.parse(metadata) : null }
667
667
  }
668
668
  })
669
669
 
670
- this.exposeCommand("mesh/set-my-connection-metadata", async (ctx) => {
670
+ this.exposeCommand("rt/set-my-connection-metadata", async (ctx) => {
671
671
  const { metadata, options } = ctx.payload
672
672
  const connectionId = ctx.connection.id
673
673
  const connection = this.connectionManager.getLocalConnection(connectionId)
@@ -683,7 +683,7 @@ export class RealtimeServer {
683
683
  }
684
684
  })
685
685
 
686
- this.exposeCommand("mesh/get-room-metadata", async (ctx) => {
686
+ this.exposeCommand("rt/get-room-metadata", async (ctx) => {
687
687
  const { roomName } = ctx.payload
688
688
  const metadata = await this.roomManager.getMetadata(roomName)
689
689
  return { metadata }
@@ -691,7 +691,7 @@ export class RealtimeServer {
691
691
  }
692
692
 
693
693
  _registerRecordCommands() {
694
- this.exposeCommand("mesh/subscribe-record", async (ctx) => {
694
+ this.exposeCommand("rt/subscribe-record", async (ctx) => {
695
695
  const { recordId, mode = "full" } = ctx.payload
696
696
  const connectionId = ctx.connection.id
697
697
  if (!(await this.recordSubscriptionManager.isRecordExposed(recordId, ctx.connection))) {
@@ -707,12 +707,12 @@ export class RealtimeServer {
707
707
  }
708
708
  })
709
709
 
710
- this.exposeCommand("mesh/unsubscribe-record", async (ctx) => {
710
+ this.exposeCommand("rt/unsubscribe-record", async (ctx) => {
711
711
  const { recordId } = ctx.payload
712
712
  return this.recordSubscriptionManager.removeSubscription(recordId, ctx.connection.id)
713
713
  })
714
714
 
715
- this.exposeCommand("mesh/publish-record-update", async (ctx) => {
715
+ this.exposeCommand("rt/publish-record-update", async (ctx) => {
716
716
  const { recordId, newValue, options } = ctx.payload
717
717
  if (!(await this.recordSubscriptionManager.isRecordWritable(recordId, ctx.connection))) {
718
718
  throw new Error(`Record "${recordId}" is not writable by this connection.`)
@@ -725,13 +725,13 @@ export class RealtimeServer {
725
725
  }
726
726
  })
727
727
 
728
- this.exposeCommand("mesh/subscribe-presence", async (ctx) => {
728
+ this.exposeCommand("rt/subscribe-presence", async (ctx) => {
729
729
  const { roomName } = ctx.payload
730
730
  if (!(await this.presenceManager.isRoomTracked(roomName, ctx.connection))) {
731
731
  return { success: false, present: [] }
732
732
  }
733
733
  try {
734
- const presenceChannel = `mesh:presence:updates:${roomName}`
734
+ const presenceChannel = `rt:presence:updates:${roomName}`
735
735
  this.channelManager.addSubscription(presenceChannel, ctx.connection)
736
736
  if (!this.channelManager.getSubscribers(presenceChannel) || this.channelManager.getSubscribers(presenceChannel)?.size === 1) {
737
737
  await this.channelManager.subscribeToRedisChannel(presenceChannel)
@@ -747,13 +747,13 @@ export class RealtimeServer {
747
747
  }
748
748
  })
749
749
 
750
- this.exposeCommand("mesh/unsubscribe-presence", async (ctx) => {
750
+ this.exposeCommand("rt/unsubscribe-presence", async (ctx) => {
751
751
  const { roomName } = ctx.payload
752
- const presenceChannel = `mesh:presence:updates:${roomName}`
752
+ const presenceChannel = `rt:presence:updates:${roomName}`
753
753
  return this.channelManager.removeSubscription(presenceChannel, ctx.connection)
754
754
  })
755
755
 
756
- this.exposeCommand("mesh/publish-presence-state", async (ctx) => {
756
+ this.exposeCommand("rt/publish-presence-state", async (ctx) => {
757
757
  const { roomName, state, expireAfter, silent } = ctx.payload
758
758
  const connectionId = ctx.connection.id
759
759
  if (!state) return false
@@ -769,7 +769,7 @@ export class RealtimeServer {
769
769
  }
770
770
  })
771
771
 
772
- this.exposeCommand("mesh/clear-presence-state", async (ctx) => {
772
+ this.exposeCommand("rt/clear-presence-state", async (ctx) => {
773
773
  const { roomName } = ctx.payload
774
774
  const connectionId = ctx.connection.id
775
775
  if (!(await this.presenceManager.isRoomTracked(roomName, ctx.connection)) || !(await this.isInRoom(roomName, connectionId))) {
@@ -784,7 +784,7 @@ export class RealtimeServer {
784
784
  }
785
785
  })
786
786
 
787
- this.exposeCommand("mesh/get-presence-state", async (ctx) => {
787
+ this.exposeCommand("rt/get-presence-state", async (ctx) => {
788
788
  const { roomName } = ctx.payload
789
789
  if (!(await this.presenceManager.isRoomTracked(roomName, ctx.connection))) {
790
790
  return { success: false, present: [] }
@@ -801,7 +801,7 @@ export class RealtimeServer {
801
801
  }
802
802
  })
803
803
 
804
- this.exposeCommand("mesh/subscribe-collection", async (ctx) => {
804
+ this.exposeCommand("rt/subscribe-collection", async (ctx) => {
805
805
  const { collectionId } = ctx.payload
806
806
  const connectionId = ctx.connection.id
807
807
  if (!(await this.collectionManager.isCollectionExposed(collectionId, ctx.connection))) {
@@ -817,7 +817,7 @@ export class RealtimeServer {
817
817
  }
818
818
  })
819
819
 
820
- this.exposeCommand("mesh/unsubscribe-collection", async (ctx) => {
820
+ this.exposeCommand("rt/unsubscribe-collection", async (ctx) => {
821
821
  const { collectionId } = ctx.payload
822
822
  return this.collectionManager.removeSubscription(collectionId, ctx.connection.id)
823
823
  })
@@ -1,4 +1,4 @@
1
- export const PUB_SUB_CHANNEL_PREFIX = "mesh:pubsub:"
2
- export const RECORD_PUB_SUB_CHANNEL = "mesh:record-updates"
3
- export const RECORD_KEY_PREFIX = "mesh:record:"
4
- export const RECORD_VERSION_KEY_PREFIX = "mesh:record-version:"
1
+ export const PUB_SUB_CHANNEL_PREFIX = "rt:pubsub:"
2
+ export const RECORD_PUB_SUB_CHANNEL = "rt:record-updates"
3
+ export const RECORD_KEY_PREFIX = "rt:record:"
4
+ export const RECORD_VERSION_KEY_PREFIX = "rt:record-version:"
@@ -13,7 +13,7 @@ export class Logger {
13
13
  constructor(config) {
14
14
  this.config = {
15
15
  level: config?.level ?? LogLevel.INFO,
16
- prefix: config?.prefix ?? "[mesh]",
16
+ prefix: config?.prefix ?? "[realtime]",
17
17
  styling: config?.styling ?? isBrowser,
18
18
  }
19
19
  }