svelte-realtime 0.1.4

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/devtools.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** Side-effect-only module: injects the devtools overlay into the page. No exports. */
2
+ export {};
package/devtools.js ADDED
@@ -0,0 +1,214 @@
1
+ // @ts-check
2
+ // DevTools overlay for svelte-realtime (dev-mode only)
3
+ // Injected by the Vite plugin via transformIndexHtml
4
+
5
+ /** @type {HTMLElement | null} */
6
+ let panel = null;
7
+ let visible = false;
8
+ let activeTab = 'rpcs';
9
+
10
+ /** @type {ReturnType<typeof setInterval> | null} */
11
+ let refreshInterval = null;
12
+
13
+ function toggle() {
14
+ visible = !visible;
15
+ if (panel) panel.style.display = visible ? 'block' : 'none';
16
+ if (visible && !refreshInterval) {
17
+ const content = panel?.querySelector('div:last-child');
18
+ if (content) render(/** @type {HTMLElement} */ (content));
19
+ refreshInterval = setInterval(() => {
20
+ const content = panel?.querySelector('div:last-child');
21
+ if (content) render(/** @type {HTMLElement} */ (content));
22
+ }, 1000);
23
+ } else if (!visible && refreshInterval) {
24
+ clearInterval(refreshInterval);
25
+ refreshInterval = null;
26
+ }
27
+ }
28
+
29
+ function init() {
30
+ if (panel) return;
31
+
32
+ document.addEventListener('keydown', (e) => {
33
+ if (e.ctrlKey && e.shiftKey && e.key === 'L') {
34
+ e.preventDefault();
35
+ toggle();
36
+ }
37
+ });
38
+
39
+ panel = document.createElement('div');
40
+ panel.id = 'svelte-realtime-devtools';
41
+ panel.style.cssText = `
42
+ display: none; position: fixed; bottom: 10px; right: 10px; width: 420px; max-height: 400px;
43
+ background: #1a1a2e; color: #e0e0e0; border: 1px solid #333; border-radius: 8px;
44
+ font-family: monospace; font-size: 12px; z-index: 99999; overflow: hidden;
45
+ box-shadow: 0 4px 20px rgba(0,0,0,0.5);
46
+ `;
47
+
48
+ const header = document.createElement('div');
49
+ header.style.cssText = `
50
+ padding: 6px 10px; background: #16213e; display: flex; justify-content: space-between;
51
+ align-items: center; cursor: move; user-select: none; border-bottom: 1px solid #333;
52
+ `;
53
+ header.innerHTML = '<span style="font-weight:bold;color:#e94560">svelte-realtime</span>';
54
+
55
+ const closeBtn = document.createElement('button');
56
+ closeBtn.textContent = 'x';
57
+ closeBtn.style.cssText = 'background:none;border:none;color:#888;cursor:pointer;font-size:14px;padding:0 4px;';
58
+ closeBtn.onclick = toggle;
59
+ header.appendChild(closeBtn);
60
+
61
+ // Drag support
62
+ let dragging = false, dx = 0, dy = 0;
63
+ header.onmousedown = (e) => {
64
+ dragging = true;
65
+ dx = e.clientX - panel.offsetLeft;
66
+ dy = e.clientY - panel.offsetTop;
67
+ };
68
+ document.addEventListener('mousemove', (e) => {
69
+ if (!dragging) return;
70
+ panel.style.left = (e.clientX - dx) + 'px';
71
+ panel.style.top = (e.clientY - dy) + 'px';
72
+ panel.style.right = 'auto';
73
+ panel.style.bottom = 'auto';
74
+ });
75
+ document.addEventListener('mouseup', () => { dragging = false; });
76
+
77
+ const tabs = document.createElement('div');
78
+ tabs.style.cssText = 'display:flex;border-bottom:1px solid #333;';
79
+
80
+ const content = document.createElement('div');
81
+ content.style.cssText = 'padding:8px;overflow-y:auto;max-height:320px;';
82
+
83
+ const tabNames = ['rpcs', 'streams', 'connection'];
84
+ for (const name of tabNames) {
85
+ const btn = document.createElement('button');
86
+ btn.textContent = name;
87
+ btn.style.cssText = `
88
+ flex:1;padding:5px;background:none;border:none;color:#888;cursor:pointer;
89
+ font-family:monospace;font-size:11px;border-bottom:2px solid transparent;
90
+ `;
91
+ btn.onclick = () => {
92
+ activeTab = name;
93
+ _lastRenderedHtml = '';
94
+ for (const b of tabs.children) {
95
+ /** @type {HTMLElement} */ (b).style.borderBottomColor = 'transparent';
96
+ /** @type {HTMLElement} */ (b).style.color = '#888';
97
+ }
98
+ btn.style.borderBottomColor = '#e94560';
99
+ btn.style.color = '#e0e0e0';
100
+ render(content);
101
+ };
102
+ if (name === activeTab) {
103
+ btn.style.borderBottomColor = '#e94560';
104
+ btn.style.color = '#e0e0e0';
105
+ }
106
+ tabs.appendChild(btn);
107
+ }
108
+
109
+ panel.appendChild(header);
110
+ panel.appendChild(tabs);
111
+ panel.appendChild(content);
112
+ document.body.appendChild(panel);
113
+
114
+ // Refresh managed by toggle() -- no idle timer when hidden
115
+ }
116
+
117
+ /** @param {HTMLElement} el */
118
+ function render(el) {
119
+ /** @type {any} */
120
+ let devtools = null;
121
+ try {
122
+ // Dynamic import to avoid SSR issues
123
+ devtools = window.__svelte_realtime_devtools;
124
+ } catch { return; }
125
+ if (!devtools) {
126
+ el.textContent = 'No devtools data available';
127
+ return;
128
+ }
129
+
130
+ if (activeTab === 'rpcs') {
131
+ renderRpcs(el, devtools);
132
+ } else if (activeTab === 'streams') {
133
+ renderStreams(el, devtools);
134
+ } else if (activeTab === 'connection') {
135
+ renderConnection(el, devtools);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Escape a string for safe insertion into HTML.
141
+ * @param {string} s
142
+ * @returns {string}
143
+ */
144
+ function esc(s) {
145
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
146
+ }
147
+
148
+ /** @type {string} */
149
+ let _lastRenderedHtml = '';
150
+
151
+ /** @param {HTMLElement} el @param {any} dt */
152
+ function renderRpcs(el, dt) {
153
+ const pending = dt.pending ? Array.from(dt.pending.values()) : [];
154
+ const history = dt.history || [];
155
+
156
+ let html = '';
157
+ if (pending.length > 0) {
158
+ html += '<div style="color:#ffc107;margin-bottom:6px">Pending (' + esc(String(pending.length)) + ')</div>';
159
+ for (const p of pending) {
160
+ const elapsed = Date.now() - p.startTime;
161
+ html += `<div style="padding:2px 0;color:#aaa">${esc(p.path)} <span style="color:#666">${esc(String(elapsed))}ms</span></div>`;
162
+ }
163
+ html += '<hr style="border-color:#333;margin:6px 0">';
164
+ }
165
+
166
+ html += '<div style="margin-bottom:4px">History (' + esc(String(history.length)) + ')</div>';
167
+ for (let i = history.length - 1; i >= Math.max(0, history.length - 20); i--) {
168
+ const h = history[i];
169
+ const color = h.ok ? '#4caf50' : '#f44336';
170
+ html += `<div style="padding:2px 0"><span style="color:${color}">${h.ok ? 'OK' : 'ERR'}</span> ${esc(h.path)} <span style="color:#666">${esc(String(h.duration))}ms</span></div>`;
171
+ }
172
+ if (history.length === 0) html += '<div style="color:#666">No RPC calls yet</div>';
173
+
174
+ if (html !== _lastRenderedHtml) {
175
+ _lastRenderedHtml = html;
176
+ el.innerHTML = html;
177
+ }
178
+ }
179
+
180
+ /** @param {HTMLElement} el @param {any} dt */
181
+ function renderStreams(el, dt) {
182
+ const streams = dt.streams ? Array.from(dt.streams.values()) : [];
183
+ let html = '<div style="margin-bottom:4px">Active (' + esc(String(streams.length)) + ')</div>';
184
+ for (const s of streams) {
185
+ html += `<div style="padding:2px 0">${esc(s.path)} <span style="color:#666">topic:${esc(s.topic || '?')} subs:${esc(String(s.subCount))}</span></div>`;
186
+ }
187
+ if (streams.length === 0) html += '<div style="color:#666">No active streams</div>';
188
+
189
+ if (html !== _lastRenderedHtml) {
190
+ _lastRenderedHtml = html;
191
+ el.innerHTML = html;
192
+ }
193
+ }
194
+
195
+ /** @param {HTMLElement} el @param {any} dt */
196
+ function renderConnection(el, dt) {
197
+ const pending = dt.pending ? dt.pending.size : 0;
198
+ const html = `
199
+ <div style="padding:2px 0">Pending RPCs: ${esc(String(pending))}</div>
200
+ <div style="padding:2px 0">History entries: ${esc(String((dt.history || []).length))}</div>
201
+ <div style="padding:2px 0">Active streams: ${esc(String(dt.streams ? dt.streams.size : 0))}</div>
202
+ <div style="padding:4px 0;color:#666;font-size:11px">Press Ctrl+Shift+L to toggle</div>
203
+ `;
204
+
205
+ if (html !== _lastRenderedHtml) {
206
+ _lastRenderedHtml = html;
207
+ el.innerHTML = html;
208
+ }
209
+ }
210
+
211
+ // Auto-init when loaded
212
+ if (typeof window !== 'undefined') {
213
+ init();
214
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "svelte-realtime",
3
+ "version": "0.1.4",
4
+ "description": "Realtime RPC and reactive subscriptions for SvelteKit, built on svelte-adapter-uws",
5
+ "author": "Kevin Radziszewski",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/lanteanio/svelte-realtime.git"
10
+ },
11
+ "homepage": "https://github.com/lanteanio/svelte-realtime",
12
+ "bugs": "https://github.com/lanteanio/svelte-realtime/issues",
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./server.d.ts",
17
+ "default": "./server.js"
18
+ },
19
+ "./server": {
20
+ "types": "./server.d.ts",
21
+ "default": "./server.js"
22
+ },
23
+ "./client": {
24
+ "types": "./client.d.ts",
25
+ "default": "./client.js"
26
+ },
27
+ "./vite": {
28
+ "types": "./vite.d.ts",
29
+ "default": "./vite.js"
30
+ },
31
+ "./devtools": {
32
+ "types": "./devtools.d.ts",
33
+ "default": "./devtools.js"
34
+ },
35
+ "./test": {
36
+ "types": "./test.d.ts",
37
+ "default": "./test.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "server.js",
42
+ "server.d.ts",
43
+ "client.js",
44
+ "client.d.ts",
45
+ "vite.js",
46
+ "vite.d.ts",
47
+ "test.js",
48
+ "test.d.ts",
49
+ "devtools.js",
50
+ "devtools.d.ts",
51
+ "LICENSE",
52
+ "README.md"
53
+ ],
54
+ "scripts": {
55
+ "test": "vitest run",
56
+ "test:watch": "vitest"
57
+ },
58
+ "engines": {
59
+ "node": ">=20.0.0"
60
+ },
61
+ "peerDependencies": {
62
+ "svelte-adapter-uws": ">=0.2.0",
63
+ "svelte": "^4.0.0 || ^5.0.0",
64
+ "@sveltejs/kit": "^2.0.0"
65
+ },
66
+ "devDependencies": {
67
+ "vitest": "^4.0.18"
68
+ },
69
+ "keywords": [
70
+ "svelte",
71
+ "sveltekit",
72
+ "websocket",
73
+ "rpc",
74
+ "realtime",
75
+ "pubsub",
76
+ "uwebsockets",
77
+ "streams",
78
+ "live"
79
+ ]
80
+ }