@prsm/realtime 1.0.0 → 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/README.md +9 -0
- package/package.json +2 -7
- package/src/adapters/postgres.js +3 -3
- package/src/client/client.js +10 -10
- package/src/client/connection.js +1 -1
- package/src/client/subscriptions/channels.js +3 -3
- package/src/client/subscriptions/collections.js +2 -2
- package/src/client/subscriptions/presence.js +5 -5
- package/src/client/subscriptions/records.js +3 -3
- package/src/client/subscriptions/rooms.js +3 -3
- package/src/server/managers/channels.js +4 -4
- package/src/server/managers/collections.js +5 -5
- package/src/server/managers/connections.js +3 -3
- package/src/server/managers/instance.js +20 -20
- package/src/server/managers/presence.js +7 -7
- package/src/server/managers/pubsub.js +14 -14
- package/src/server/managers/rooms.js +7 -7
- package/src/server/server.js +26 -26
- package/src/server/utils/constants.js +4 -4
- package/src/shared/logger.js +1 -1
- package/src/devtools/client/dist/assets/index-CGm1NqOQ.css +0 -1
- package/src/devtools/client/dist/assets/index-w2FI7RvC.js +0 -168
- package/src/devtools/client/dist/index.html +0 -16
- package/src/devtools/client/index.html +0 -15
- package/src/devtools/client/package.json +0 -17
- package/src/devtools/client/src/App.vue +0 -173
- package/src/devtools/client/src/components/ConnectionPicker.vue +0 -38
- package/src/devtools/client/src/components/JsonView.vue +0 -18
- package/src/devtools/client/src/composables/useApi.js +0 -71
- package/src/devtools/client/src/composables/useHighlight.js +0 -57
- package/src/devtools/client/src/main.js +0 -5
- package/src/devtools/client/src/style.css +0 -440
- package/src/devtools/client/src/views/ChannelsView.vue +0 -27
- package/src/devtools/client/src/views/CollectionsView.vue +0 -61
- package/src/devtools/client/src/views/MetadataView.vue +0 -108
- package/src/devtools/client/src/views/RecordsView.vue +0 -30
- package/src/devtools/client/src/views/RoomsView.vue +0 -39
- package/src/devtools/client/vite.config.js +0 -17
- package/src/devtools/demo/server.js +0 -144
- package/src/devtools/index.js +0 -186
package/src/server/server.js
CHANGED
|
@@ -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: "
|
|
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("
|
|
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("
|
|
585
|
+
this.exposeCommand("rt/noop", async () => true)
|
|
586
586
|
|
|
587
|
-
this.exposeCommand("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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 = `
|
|
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("
|
|
750
|
+
this.exposeCommand("rt/unsubscribe-presence", async (ctx) => {
|
|
751
751
|
const { roomName } = ctx.payload
|
|
752
|
-
const presenceChannel = `
|
|
752
|
+
const presenceChannel = `rt:presence:updates:${roomName}`
|
|
753
753
|
return this.channelManager.removeSubscription(presenceChannel, ctx.connection)
|
|
754
754
|
})
|
|
755
755
|
|
|
756
|
-
this.exposeCommand("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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 = "
|
|
2
|
-
export const RECORD_PUB_SUB_CHANNEL = "
|
|
3
|
-
export const RECORD_KEY_PREFIX = "
|
|
4
|
-
export const RECORD_VERSION_KEY_PREFIX = "
|
|
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:"
|
package/src/shared/logger.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--bg: #0a0a0a;--bg-surface: #111111;--bg-raised: #1a1a1a;--bg-hover: #222222;--bg-active: #2a2a2a;--border: #2a2a2a;--border-subtle: #1e1e1e;--text: #d4d4d4;--text-bright: #e8e8e8;--text-muted: #555555;--accent: #34d399;--accent-dim: rgba(52, 211, 153, .12);--accent-text: #2dd4a2;--syn-string: #a5d6a7;--syn-number: #4dd0e1;--syn-boolean: #ce93d8;--syn-null: #666666;--syn-key: #b0b0b0;--syn-bracket: #555555}*{box-sizing:border-box;margin:0;padding:0}body{font-family:SF Mono,Fira Code,JetBrains Mono,Cascadia Code,monospace;font-size:12px;line-height:1.5;color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased}::selection{background:var(--accent-dim);color:var(--accent-text)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#3a3a3a}.app{display:flex;flex-direction:column;height:100vh;overflow:hidden}.top-bar{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:40px;border-bottom:1px solid var(--border);background:var(--bg-surface);flex-shrink:0}.top-bar-left{display:flex;align-items:center;gap:12px}.logo{font-size:11px;font-weight:600;letter-spacing:.5px;text-transform:uppercase;color:var(--text-muted)}.logo span{color:var(--accent)}.top-bar-right{display:flex;align-items:center;gap:12px}.pulse{width:6px;height:6px;border-radius:50%;background:var(--accent)}.pulse.disconnected{background:#ef4444}.instance-id{font-size:10px;color:var(--text-muted)}.tab-bar{display:flex;align-items:center;gap:0;border-bottom:1px solid var(--border);background:var(--bg-surface);flex-shrink:0;padding:0 16px}.tab{padding:8px 16px;font-size:11px;color:var(--text-muted);cursor:pointer;border-bottom:2px solid transparent;-webkit-user-select:none;user-select:none}.tab:hover{color:var(--text)}.tab.active{color:var(--accent-text);border-bottom-color:var(--accent)}.tab .count{margin-left:6px;font-size:10px;color:var(--text-muted)}.tab.active .count{color:var(--accent)}.main{display:flex;flex:1;overflow:hidden}.sidebar{width:280px;min-width:280px;border-right:1px solid var(--border);overflow-y:auto;background:var(--bg-surface)}.content{flex:1;overflow-y:auto;padding:16px}.sidebar-section{border-bottom:1px solid var(--border-subtle)}.sidebar-header{padding:8px 12px;font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);background:var(--bg)}.sidebar-item{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;cursor:pointer;border-left:2px solid transparent}.sidebar-item:hover{background:var(--bg-hover)}.sidebar-item.active{background:var(--accent-dim);border-left-color:var(--accent)}.sidebar-item .label{font-size:11px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-item.active .label{color:var(--accent-text)}.sidebar-item .meta{font-size:10px;color:var(--text-muted);flex-shrink:0;margin-left:8px}.badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;font-size:10px;border-radius:9px;background:#1f1f1f;color:#999}.badge.accent{background:var(--accent-dim);color:var(--accent)}.section{margin-bottom:20px}.section-title{font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);margin-bottom:8px}.card{background:var(--bg-surface);border:1px solid var(--border);border-radius:4px;overflow:hidden}.card+.card{margin-top:8px}.card-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--bg);border-bottom:1px solid var(--border-subtle);font-size:11px}.card-header .name{color:var(--text-bright)}.card-body{padding:8px 12px}.kv-row{display:flex;align-items:baseline;padding:2px 0;font-size:11px}.kv-key{color:var(--text-muted);min-width:100px;flex-shrink:0}.kv-value{color:var(--text);word-break:break-all}.member-row{display:flex;align-items:center;justify-content:space-between;padding:4px 12px;font-size:11px;border-bottom:1px solid var(--border-subtle)}.member-row:last-child{border-bottom:none}.member-id{color:var(--text);font-size:11px}.member-presence{font-size:10px;color:var(--accent)}.tag{display:inline-block;padding:1px 6px;font-size:10px;border-radius:3px;background:#1f1f1f;color:#999;margin:1px 2px}.tag.accent{background:var(--accent-dim);color:var(--accent)}.empty{padding:24px;text-align:center;color:var(--text-muted);font-size:11px}.view-hint{font-size:11px;color:var(--text-muted);margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--border-subtle)}.no-presence{font-size:10px;color:#333}.pattern-list{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.json-view{font-size:11px;line-height:1.6;white-space:pre-wrap;word-break:break-all}.json-view .shiki{background:transparent!important;padding:0;margin:0}.json-view .shiki code{font-family:inherit;font-size:inherit}.select{background:var(--bg-raised);border:1px solid var(--border);color:var(--text);font-family:inherit;font-size:11px;padding:4px 8px;border-radius:3px;outline:none;cursor:pointer}.select:focus{border-color:var(--accent)}.conn-link{cursor:pointer;text-decoration:underline;text-decoration-color:var(--border);text-underline-offset:2px}.conn-link:hover{color:var(--accent-text);text-decoration-color:var(--accent)}.exposed-row{display:flex;align-items:center;flex-wrap:wrap;gap:3px;padding:4px 12px;border-bottom:1px solid var(--border-subtle)}.exposed-row:last-child{border-bottom:none}.exposed-label{font-size:10px;color:var(--text-muted);min-width:70px;flex-shrink:0}.inline-json .s{color:var(--syn-string)}.inline-json .n{color:var(--syn-number)}.inline-json .b{color:var(--syn-boolean)}.inline-json .null{color:var(--syn-null)}.inline-json .k{color:var(--syn-key)}.inline-json .p{color:var(--syn-bracket)}
|