@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
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>mesh devtools</title>
|
|
7
|
-
<style>
|
|
8
|
-
body { margin: 0; background: #0a0a0a; }
|
|
9
|
-
</style>
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-w2FI7RvC.js"></script>
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./assets/index-CGm1NqOQ.css">
|
|
12
|
-
</head>
|
|
13
|
-
<body>
|
|
14
|
-
<div id="app"></div>
|
|
15
|
-
</body>
|
|
16
|
-
</html>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>mesh devtools</title>
|
|
7
|
-
<style>
|
|
8
|
-
body { margin: 0; background: #0a0a0a; }
|
|
9
|
-
</style>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<div id="app"></div>
|
|
13
|
-
<script type="module" src="/src/main.js"></script>
|
|
14
|
-
</body>
|
|
15
|
-
</html>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@prsm/realtime-devtools-client",
|
|
3
|
-
"private": true,
|
|
4
|
-
"type": "module",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "vite",
|
|
7
|
-
"build": "vite build"
|
|
8
|
-
},
|
|
9
|
-
"dependencies": {
|
|
10
|
-
"vue": "^3.5.13",
|
|
11
|
-
"shiki": "^3.4.2"
|
|
12
|
-
},
|
|
13
|
-
"devDependencies": {
|
|
14
|
-
"@vitejs/plugin-vue": "^5.2.4",
|
|
15
|
-
"vite": "^6.3.5"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="app">
|
|
3
|
-
<div class="top-bar">
|
|
4
|
-
<div class="top-bar-left">
|
|
5
|
-
<span class="logo">mesh <span>devtools</span></span>
|
|
6
|
-
</div>
|
|
7
|
-
<div class="top-bar-right">
|
|
8
|
-
<span class="pulse" :class="{ disconnected: !!error }"></span>
|
|
9
|
-
<span class="instance-id" v-if="state">instance {{ state.instanceId?.slice(0, 8) }}</span>
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
<div class="tab-bar">
|
|
14
|
-
<div
|
|
15
|
-
v-for="tab in tabs"
|
|
16
|
-
:key="tab.id"
|
|
17
|
-
class="tab"
|
|
18
|
-
:class="{ active: activeTab === tab.id }"
|
|
19
|
-
@click="activeTab = tab.id"
|
|
20
|
-
>
|
|
21
|
-
{{ tab.label }}<span class="count" v-if="tab.count !== undefined">{{ tab.count }}</span>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
<div class="main">
|
|
26
|
-
<div class="sidebar">
|
|
27
|
-
<ConnectionPicker
|
|
28
|
-
:connections="state?.connections || []"
|
|
29
|
-
:selected-id="selectedConnectionId"
|
|
30
|
-
@select="selectConnection"
|
|
31
|
-
/>
|
|
32
|
-
<div class="sidebar-section" v-if="state?.exposed">
|
|
33
|
-
<div class="sidebar-header" style="cursor: pointer; user-select: none;" @click="showExposed = !showExposed">
|
|
34
|
-
<span>registered</span>
|
|
35
|
-
<span style="float: right; font-size: 9px;">{{ showExposed ? '−' : '+' }}</span>
|
|
36
|
-
</div>
|
|
37
|
-
<template v-if="showExposed">
|
|
38
|
-
<div
|
|
39
|
-
v-for="(patterns, type) in nonEmptyExposed"
|
|
40
|
-
:key="type"
|
|
41
|
-
class="exposed-row"
|
|
42
|
-
>
|
|
43
|
-
<span class="exposed-label">{{ formatExposedLabel(type) }}</span>
|
|
44
|
-
<span class="tag" v-for="p in patterns" :key="p">{{ p }}</span>
|
|
45
|
-
</div>
|
|
46
|
-
</template>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<div class="content">
|
|
51
|
-
<RoomsView
|
|
52
|
-
v-if="activeTab === 'rooms'"
|
|
53
|
-
:rooms="filteredRooms"
|
|
54
|
-
@navigate="navigateToConnection"
|
|
55
|
-
/>
|
|
56
|
-
<ChannelsView
|
|
57
|
-
v-if="activeTab === 'channels'"
|
|
58
|
-
:channels="filteredChannels"
|
|
59
|
-
@navigate="navigateToConnection"
|
|
60
|
-
/>
|
|
61
|
-
<CollectionsView
|
|
62
|
-
v-if="activeTab === 'collections'"
|
|
63
|
-
:collections="filteredCollections"
|
|
64
|
-
:fetch-records="fetchCollectionRecords"
|
|
65
|
-
@navigate="navigateToConnection"
|
|
66
|
-
/>
|
|
67
|
-
<RecordsView
|
|
68
|
-
v-if="activeTab === 'records'"
|
|
69
|
-
:records="filteredRecords"
|
|
70
|
-
@navigate="navigateToConnection"
|
|
71
|
-
/>
|
|
72
|
-
<MetadataView
|
|
73
|
-
v-if="activeTab === 'metadata'"
|
|
74
|
-
:detail="connectionDetail"
|
|
75
|
-
:connections="state?.connections || []"
|
|
76
|
-
/>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
</template>
|
|
81
|
-
|
|
82
|
-
<script setup>
|
|
83
|
-
import { ref, computed } from 'vue'
|
|
84
|
-
import { useApi } from './composables/useApi.js'
|
|
85
|
-
import ConnectionPicker from './components/ConnectionPicker.vue'
|
|
86
|
-
import RoomsView from './views/RoomsView.vue'
|
|
87
|
-
import ChannelsView from './views/ChannelsView.vue'
|
|
88
|
-
import CollectionsView from './views/CollectionsView.vue'
|
|
89
|
-
import RecordsView from './views/RecordsView.vue'
|
|
90
|
-
import MetadataView from './views/MetadataView.vue'
|
|
91
|
-
|
|
92
|
-
const activeTab = ref('rooms')
|
|
93
|
-
const showExposed = ref(true)
|
|
94
|
-
|
|
95
|
-
const exposedLabels = {
|
|
96
|
-
channels: 'channels',
|
|
97
|
-
records: 'records',
|
|
98
|
-
writableRecords: 'writable',
|
|
99
|
-
collections: 'collections',
|
|
100
|
-
presence: 'presence',
|
|
101
|
-
commands: 'commands'
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function formatExposedLabel(key) {
|
|
105
|
-
return exposedLabels[key] || key
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function navigateToConnection(connId) {
|
|
109
|
-
selectConnection(connId)
|
|
110
|
-
activeTab.value = 'metadata'
|
|
111
|
-
}
|
|
112
|
-
const {
|
|
113
|
-
state,
|
|
114
|
-
connectionDetail,
|
|
115
|
-
selectedConnectionId,
|
|
116
|
-
error,
|
|
117
|
-
selectConnection,
|
|
118
|
-
fetchCollectionRecords
|
|
119
|
-
} = useApi()
|
|
120
|
-
|
|
121
|
-
const filteredRooms = computed(() => {
|
|
122
|
-
if (!state.value?.rooms) return []
|
|
123
|
-
if (!selectedConnectionId.value) return state.value.rooms
|
|
124
|
-
return state.value.rooms.filter(r => r.members.includes(selectedConnectionId.value))
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
const filteredChannels = computed(() => {
|
|
128
|
-
if (!state.value?.channels) return {}
|
|
129
|
-
if (!selectedConnectionId.value) return state.value.channels
|
|
130
|
-
const out = {}
|
|
131
|
-
for (const [ch, subs] of Object.entries(state.value.channels)) {
|
|
132
|
-
if (subs.includes(selectedConnectionId.value)) out[ch] = subs
|
|
133
|
-
}
|
|
134
|
-
return out
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
const filteredCollections = computed(() => {
|
|
138
|
-
if (!state.value?.collections) return {}
|
|
139
|
-
if (!selectedConnectionId.value) return state.value.collections
|
|
140
|
-
const out = {}
|
|
141
|
-
for (const [id, info] of Object.entries(state.value.collections)) {
|
|
142
|
-
if (info.subscribers[selectedConnectionId.value]) out[id] = info
|
|
143
|
-
}
|
|
144
|
-
return out
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
const filteredRecords = computed(() => {
|
|
148
|
-
if (!state.value?.records) return {}
|
|
149
|
-
if (!selectedConnectionId.value) return state.value.records
|
|
150
|
-
const out = {}
|
|
151
|
-
for (const [id, info] of Object.entries(state.value.records)) {
|
|
152
|
-
if (info.subscribers[selectedConnectionId.value]) out[id] = info
|
|
153
|
-
}
|
|
154
|
-
return out
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
const nonEmptyExposed = computed(() => {
|
|
158
|
-
if (!state.value?.exposed) return {}
|
|
159
|
-
const out = {}
|
|
160
|
-
for (const [k, v] of Object.entries(state.value.exposed)) {
|
|
161
|
-
if (v.length) out[k] = v
|
|
162
|
-
}
|
|
163
|
-
return out
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
const tabs = computed(() => [
|
|
167
|
-
{ id: 'rooms', label: 'rooms', count: filteredRooms.value.length },
|
|
168
|
-
{ id: 'channels', label: 'channels', count: Object.keys(filteredChannels.value).length },
|
|
169
|
-
{ id: 'collections', label: 'collections', count: Object.keys(filteredCollections.value).length },
|
|
170
|
-
{ id: 'records', label: 'records', count: Object.keys(filteredRecords.value).length },
|
|
171
|
-
{ id: 'metadata', label: 'metadata' }
|
|
172
|
-
])
|
|
173
|
-
</script>
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="sidebar-section">
|
|
3
|
-
<div class="sidebar-header">connections</div>
|
|
4
|
-
<div
|
|
5
|
-
class="sidebar-item"
|
|
6
|
-
:class="{ active: !selectedId }"
|
|
7
|
-
@click="$emit('select', null)"
|
|
8
|
-
>
|
|
9
|
-
<span class="label">all connections</span>
|
|
10
|
-
<span class="badge" v-if="connections.length">{{ connections.length }}</span>
|
|
11
|
-
</div>
|
|
12
|
-
<div
|
|
13
|
-
v-for="conn in connections"
|
|
14
|
-
:key="conn.id"
|
|
15
|
-
class="sidebar-item"
|
|
16
|
-
:class="{ active: selectedId === conn.id }"
|
|
17
|
-
@click="$emit('select', conn.id)"
|
|
18
|
-
>
|
|
19
|
-
<span class="label" :title="conn.id">{{ conn.id.slice(0, 8) }}</span>
|
|
20
|
-
<span class="meta" v-if="conn.latency !== null">{{ conn.latency }}ms</span>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
</template>
|
|
24
|
-
|
|
25
|
-
<script setup>
|
|
26
|
-
defineProps({
|
|
27
|
-
connections: { type: Array, default: () => [] },
|
|
28
|
-
selectedId: { type: String, default: null }
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
defineEmits(['select'])
|
|
32
|
-
|
|
33
|
-
function label(conn) {
|
|
34
|
-
if (conn.metadata?.name) return conn.metadata.name
|
|
35
|
-
if (conn.metadata?.username) return conn.metadata.username
|
|
36
|
-
return conn.id.slice(0, 8)
|
|
37
|
-
}
|
|
38
|
-
</script>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="json-view" v-html="html"></div>
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script setup>
|
|
6
|
-
import { computed } from 'vue'
|
|
7
|
-
import { useHighlight } from '../composables/useHighlight.js'
|
|
8
|
-
|
|
9
|
-
const props = defineProps({ data: { default: null } })
|
|
10
|
-
const { highlight } = useHighlight()
|
|
11
|
-
|
|
12
|
-
const html = computed(() => {
|
|
13
|
-
if (props.data === null || props.data === undefined) {
|
|
14
|
-
return '<span style="color: var(--syn-null)">null</span>'
|
|
15
|
-
}
|
|
16
|
-
return highlight(props.data)
|
|
17
|
-
})
|
|
18
|
-
</script>
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { ref, onMounted, onUnmounted } from 'vue'
|
|
2
|
-
|
|
3
|
-
export function useApi(pollInterval = 1000) {
|
|
4
|
-
const state = ref(null)
|
|
5
|
-
const connectionDetail = ref(null)
|
|
6
|
-
const selectedConnectionId = ref(null)
|
|
7
|
-
const error = ref(null)
|
|
8
|
-
let timer = null
|
|
9
|
-
|
|
10
|
-
const basePath = import.meta.env.DEV ? '' : '.'
|
|
11
|
-
|
|
12
|
-
async function fetchState() {
|
|
13
|
-
try {
|
|
14
|
-
const res = await fetch(`${basePath}/api/state`)
|
|
15
|
-
if (!res.ok) throw new Error(`${res.status}`)
|
|
16
|
-
state.value = await res.json()
|
|
17
|
-
error.value = null
|
|
18
|
-
} catch (e) {
|
|
19
|
-
error.value = e.message
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function fetchConnectionDetail(id) {
|
|
24
|
-
try {
|
|
25
|
-
const res = await fetch(`${basePath}/api/connection/${id}`)
|
|
26
|
-
if (!res.ok) throw new Error(`${res.status}`)
|
|
27
|
-
connectionDetail.value = await res.json()
|
|
28
|
-
} catch {}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function fetchCollectionRecords(collId, connId) {
|
|
32
|
-
try {
|
|
33
|
-
const res = await fetch(`${basePath}/api/collection/${encodeURIComponent(collId)}/records?connId=${encodeURIComponent(connId)}`)
|
|
34
|
-
if (!res.ok) throw new Error(`${res.status}`)
|
|
35
|
-
return await res.json()
|
|
36
|
-
} catch {
|
|
37
|
-
return { recordIds: [], records: [] }
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function selectConnection(id) {
|
|
42
|
-
selectedConnectionId.value = id
|
|
43
|
-
if (id) await fetchConnectionDetail(id)
|
|
44
|
-
else connectionDetail.value = null
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function poll() {
|
|
48
|
-
await fetchState()
|
|
49
|
-
if (selectedConnectionId.value) {
|
|
50
|
-
await fetchConnectionDetail(selectedConnectionId.value)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
onMounted(() => {
|
|
55
|
-
poll()
|
|
56
|
-
timer = setInterval(poll, pollInterval)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
onUnmounted(() => {
|
|
60
|
-
if (timer) clearInterval(timer)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
state,
|
|
65
|
-
connectionDetail,
|
|
66
|
-
selectedConnectionId,
|
|
67
|
-
error,
|
|
68
|
-
selectConnection,
|
|
69
|
-
fetchCollectionRecords
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { ref, onMounted } from 'vue'
|
|
2
|
-
import { createHighlighterCore } from 'shiki/core'
|
|
3
|
-
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
|
|
4
|
-
import json from 'shiki/langs/json.mjs'
|
|
5
|
-
|
|
6
|
-
const highlighterRef = ref(null)
|
|
7
|
-
const ready = ref(false)
|
|
8
|
-
let initPromise = null
|
|
9
|
-
|
|
10
|
-
const theme = {
|
|
11
|
-
name: 'mesh-devtools',
|
|
12
|
-
type: 'dark',
|
|
13
|
-
bg: 'transparent',
|
|
14
|
-
fg: '#d4d4d4',
|
|
15
|
-
settings: [
|
|
16
|
-
{ scope: ['string'], settings: { foreground: '#a5d6a7' } },
|
|
17
|
-
{ scope: ['constant.numeric'], settings: { foreground: '#4dd0e1' } },
|
|
18
|
-
{ scope: ['constant.language.boolean', 'constant.language'], settings: { foreground: '#ce93d8' } },
|
|
19
|
-
{ scope: ['constant.language.null'], settings: { foreground: '#666666' } },
|
|
20
|
-
{ scope: ['support.type.property-name'], settings: { foreground: '#b0b0b0' } },
|
|
21
|
-
{ scope: ['punctuation'], settings: { foreground: '#555555' } },
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function init() {
|
|
26
|
-
if (initPromise) return initPromise
|
|
27
|
-
initPromise = createHighlighterCore({
|
|
28
|
-
themes: [theme],
|
|
29
|
-
langs: [json],
|
|
30
|
-
engine: createJavaScriptRegexEngine()
|
|
31
|
-
}).then(h => {
|
|
32
|
-
highlighterRef.value = h
|
|
33
|
-
ready.value = true
|
|
34
|
-
return h
|
|
35
|
-
})
|
|
36
|
-
return initPromise
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function useHighlight() {
|
|
40
|
-
onMounted(init)
|
|
41
|
-
|
|
42
|
-
function highlight(value) {
|
|
43
|
-
const json = typeof value === 'string' ? value : JSON.stringify(value, null, 2)
|
|
44
|
-
if (!json) return ''
|
|
45
|
-
if (!highlighterRef.value) return escapeHtml(json)
|
|
46
|
-
return highlighterRef.value.codeToHtml(json, {
|
|
47
|
-
lang: 'json',
|
|
48
|
-
theme: 'mesh-devtools'
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return { highlight, ready }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function escapeHtml(str) {
|
|
56
|
-
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
57
|
-
}
|