@prsm/realtime 1.0.1 → 1.0.3

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
  import { createServer as createHttpServer } from "node:http"
2
2
  import { randomUUID } from "node:crypto"
3
3
  import { WebSocketServer } from "ws"
4
- import { LogLevel, Status, serverLogger, parseCommand } from "../shared/index.js"
4
+ import { LogLevel, configureLogLevel, Status, serverLogger, parseCommand } from "../shared/index.js"
5
5
  import { Connection } from "./connection.js"
6
6
  import { PUB_SUB_CHANNEL_PREFIX } from "./utils/constants.js"
7
7
  import { ConnectionManager } from "./managers/connections.js"
@@ -54,7 +54,7 @@ export class RealtimeServer {
54
54
  enablePresenceExpirationEvents: opts.enablePresenceExpirationEvents ?? true,
55
55
  }
56
56
 
57
- serverLogger.configure({ level: this.serverOptions.logLevel, styling: false })
57
+ configureLogLevel(this.serverOptions.logLevel)
58
58
 
59
59
  this.redisManager = new RedisManager()
60
60
  this.redisManager.initialize(opts.redis, (err) => this._emitError(err))
@@ -159,7 +159,7 @@ export class RealtimeServer {
159
159
 
160
160
  enableGracefulShutdown() {
161
161
  const handler = () => {
162
- serverLogger.info("Received shutdown signal, closing...")
162
+ serverLogger.info("received shutdown signal, closing")
163
163
  this.close().then(() => process.exit(0))
164
164
  }
165
165
  process.on("SIGTERM", handler)
@@ -177,7 +177,7 @@ export class RealtimeServer {
177
177
  }
178
178
 
179
179
  _emitError(err) {
180
- serverLogger.error(`Error: ${err}`)
180
+ serverLogger.error("error", { err })
181
181
  for (const handler of this._errorHandlers) handler(err)
182
182
  }
183
183
 
@@ -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))) {
@@ -702,17 +702,17 @@ export class RealtimeServer {
702
702
  this.recordSubscriptionManager.addSubscription(recordId, connectionId, mode)
703
703
  return { success: true, record, version }
704
704
  } catch (e) {
705
- serverLogger.error(`Failed to subscribe to record ${recordId}:`, e)
705
+ serverLogger.error("failed to subscribe to record", { recordId, err: e })
706
706
  return { success: false }
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)
@@ -742,18 +742,18 @@ export class RealtimeServer {
742
742
  statesMap.forEach((state, connectionId) => { states[connectionId] = state })
743
743
  return { success: true, present, states }
744
744
  } catch (e) {
745
- serverLogger.error(`Failed to subscribe to presence for room ${roomName}:`, e)
745
+ serverLogger.error("failed to subscribe to presence for room", { roomName, err: e })
746
746
  return { success: false, present: [] }
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
@@ -764,12 +764,12 @@ export class RealtimeServer {
764
764
  await this.presenceManager.publishPresenceState(connectionId, roomName, state, expireAfter, silent)
765
765
  return true
766
766
  } catch (e) {
767
- serverLogger.error(`Failed to publish presence state for room ${roomName}:`, e)
767
+ serverLogger.error("failed to publish presence state for room", { roomName, err: e })
768
768
  return false
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))) {
@@ -779,12 +779,12 @@ export class RealtimeServer {
779
779
  await this.presenceManager.clearPresenceState(connectionId, roomName)
780
780
  return true
781
781
  } catch (e) {
782
- serverLogger.error(`Failed to clear presence state for room ${roomName}:`, e)
782
+ serverLogger.error("failed to clear presence state for room", { roomName, err: e })
783
783
  return false
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: [] }
@@ -796,12 +796,12 @@ export class RealtimeServer {
796
796
  statesMap.forEach((state, connectionId) => { states[connectionId] = state })
797
797
  return { success: true, present, states }
798
798
  } catch (e) {
799
- serverLogger.error(`Failed to get presence state for room ${roomName}:`, e)
799
+ serverLogger.error("failed to get presence state for room", { roomName, err: e })
800
800
  return { success: false, present: [] }
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))) {
@@ -812,19 +812,19 @@ export class RealtimeServer {
812
812
  const recordsWithId = records.map((record) => ({ id: record.id, record }))
813
813
  return { success: true, ids, records: recordsWithId, version }
814
814
  } catch (e) {
815
- serverLogger.error(`Failed to subscribe to collection ${collectionId}:`, e)
815
+ serverLogger.error("failed to subscribe to collection", { collectionId, err: e })
816
816
  return { success: false, ids: [], records: [], version: 0 }
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
  })
824
824
  }
825
825
 
826
826
  async cleanupConnection(connection) {
827
- serverLogger.info("Cleaning up connection:", connection.id)
827
+ serverLogger.info("cleaning up connection", { connectionId: connection.id })
828
828
  connection.stopIntervals()
829
829
  try {
830
830
  await this.presenceManager.cleanupConnection(connection)
@@ -858,7 +858,7 @@ export class RealtimeServer {
858
858
 
859
859
  if (this.persistenceManager) {
860
860
  try { await this.persistenceManager.shutdown() }
861
- catch (err) { serverLogger.error("Error shutting down persistence manager:", err) }
861
+ catch (err) { serverLogger.error("error shutting down persistence manager", { err }) }
862
862
  }
863
863
 
864
864
  await this.channelManager.cleanupAllSubscriptions()
@@ -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:"
@@ -1,5 +1,5 @@
1
1
  export { CodeError } from "./errors.js"
2
- export { LogLevel, Logger, clientLogger, serverLogger, logger } from "./logger.js"
2
+ export { LogLevel, configureLogLevel, clientLogger, serverLogger, logger } from "./logger.js"
3
3
  export { deepMerge, isObject } from "./merge.js"
4
4
  export { parseCommand, stringifyCommand } from "./message.js"
5
5
  export { Status } from "./status.js"
@@ -1,4 +1,5 @@
1
- /** @enum {number} */
1
+ import log from "@prsm/log"
2
+
2
3
  export const LogLevel = {
3
4
  NONE: 0,
4
5
  ERROR: 1,
@@ -7,47 +8,13 @@ export const LogLevel = {
7
8
  DEBUG: 4,
8
9
  }
9
10
 
10
- const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"
11
-
12
- export class Logger {
13
- constructor(config) {
14
- this.config = {
15
- level: config?.level ?? LogLevel.INFO,
16
- prefix: config?.prefix ?? "[mesh]",
17
- styling: config?.styling ?? isBrowser,
18
- }
19
- }
20
-
21
- configure(config) {
22
- this.config = { ...this.config, ...config }
23
- }
24
-
25
- info(...args) {
26
- if (this.config.level >= LogLevel.INFO) this._log("log", ...args)
27
- }
28
-
29
- warn(...args) {
30
- if (this.config.level >= LogLevel.WARN) this._log("warn", ...args)
31
- }
32
-
33
- error(...args) {
34
- if (this.config.level >= LogLevel.ERROR) this._log("error", ...args)
35
- }
36
-
37
- debug(...args) {
38
- if (this.config.level >= LogLevel.DEBUG) this._log("debug", ...args)
39
- }
11
+ const levelMap = { 0: "none", 1: "error", 2: "warn", 3: "info", 4: "debug" }
40
12
 
41
- _log(method, ...args) {
42
- if (this.config.styling && isBrowser) {
43
- const style = "background: #000; color: #FFA07A; padding: 2px 4px; border-radius: 2px;"
44
- console[method](`%c${this.config.prefix}%c`, style, "", ...args)
45
- } else {
46
- console[method](this.config.prefix, ...args)
47
- }
48
- }
13
+ export function configureLogLevel(level) {
14
+ const str = typeof level === "number" ? levelMap[level] ?? "info" : level
15
+ log.configure({ level: str })
49
16
  }
50
17
 
51
- export const clientLogger = new Logger({ level: LogLevel.ERROR, styling: true })
52
- export const serverLogger = new Logger({ level: LogLevel.ERROR, styling: false })
53
- export const logger = isBrowser ? clientLogger : serverLogger
18
+ export const serverLogger = log.child({ sys: "server" })
19
+ export const clientLogger = log.child({ sys: "client" })
20
+ export const logger = log