elm-ssr 0.90.0 → 0.90.1
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 +1 -1
- package/src/debugger.ts +95 -59
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elm-ssr",
|
|
3
|
-
"version": "0.90.
|
|
3
|
+
"version": "0.90.1",
|
|
4
4
|
"description": "Elm-first SSR library and framework for Cloudflare Workers (and Bun): file-based routes/islands, backend-neutral effect adapters (KV/D1/Redis/Postgres), background tasks (waitUntil/Queues), SQL-file migrations, CLI scaffold + build.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Michał Majchrzak <michmajchrzak@gmail.com>",
|
package/src/debugger.ts
CHANGED
|
@@ -235,38 +235,51 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
235
235
|
toggleBtn.id = 'elm-ssr-debug-toggle';
|
|
236
236
|
toggleBtn.innerHTML = '⚡ Debug';
|
|
237
237
|
document.body.appendChild(toggleBtn);
|
|
238
|
-
|
|
238
|
+
|
|
239
239
|
// 4. Create Panel DOM
|
|
240
240
|
const panel = document.createElement('div');
|
|
241
241
|
panel.id = 'elm-ssr-debug-panel';
|
|
242
242
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
</div>
|
|
260
|
-
\${sqlSnippet}
|
|
261
|
-
\${paramsSnippet}
|
|
262
|
-
\${detailError}
|
|
243
|
+
const isSqlKind = (kind) => ['query', 'queryOne', 'execute'].includes(kind);
|
|
244
|
+
|
|
245
|
+
const generateEffectItemHtml = (eff) => {
|
|
246
|
+
const isSql = isSqlKind(eff.kind);
|
|
247
|
+
const sqlSnippet = isSql ? \`<pre class="debug-code">\${eff.payload.sql}</pre>\` : '';
|
|
248
|
+
const paramsSnippet = isSql && eff.payload.params && eff.payload.params.length > 0 ?
|
|
249
|
+
\`<div><strong>Params:</strong> \${JSON.stringify(eff.payload.params)}</div>\` : '';
|
|
250
|
+
const outcome = eff.ok ? '<span class="status-ok">SUCCESS</span>' : '<span class="status-error">FAILED</span>';
|
|
251
|
+
const detailError = eff.error ? \`<div class="status-error">\${eff.error}</div>\` : '';
|
|
252
|
+
return \`
|
|
253
|
+
<li class="debug-item">
|
|
254
|
+
<div class="debug-item-meta" style="flex: 1;">
|
|
255
|
+
<div style="display: flex; gap: 8px; align-items: center;">
|
|
256
|
+
<span class="debug-badge badge-primary">\${eff.kind}</span>
|
|
257
|
+
\${outcome}
|
|
258
|
+
<span style="color: #9ca3af; font-size: 0.85em;">\${eff.durationMs.toFixed(1)}ms</span>
|
|
263
259
|
</div>
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
260
|
+
\${sqlSnippet}
|
|
261
|
+
\${paramsSnippet}
|
|
262
|
+
\${detailError}
|
|
263
|
+
</div>
|
|
264
|
+
</li>
|
|
265
|
+
\`;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const getDbHtml = (effects) => {
|
|
269
|
+
const dbEffects = (effects || []).filter(eff => isSqlKind(eff.kind));
|
|
270
|
+
if (dbEffects.length === 0) {
|
|
271
|
+
return '<div style="color: #9ca3af; text-align: center; padding: 20px;">No SQL database queries executed for this request.</div>';
|
|
272
|
+
}
|
|
273
|
+
return \`<ul class="debug-list">\${dbEffects.map(generateEffectItemHtml).join('')}</ul>\`;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const getEffectsHtml = (effects) => {
|
|
277
|
+
const nonDbEffects = (effects || []).filter(eff => !isSqlKind(eff.kind));
|
|
278
|
+
if (nonDbEffects.length === 0) {
|
|
279
|
+
return '<div style="color: #9ca3af; text-align: center; padding: 20px;">No other server-side effects executed for this request.</div>';
|
|
280
|
+
}
|
|
281
|
+
return \`<ul class="debug-list">\${nonDbEffects.map(generateEffectItemHtml).join('')}</ul>\`;
|
|
282
|
+
};
|
|
270
283
|
|
|
271
284
|
panel.innerHTML = \`
|
|
272
285
|
<div class="debug-header">
|
|
@@ -276,6 +289,7 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
276
289
|
<div class="debug-tabs">
|
|
277
290
|
<button class="debug-tab active" data-tab="overview">Overview</button>
|
|
278
291
|
<button class="debug-tab" data-tab="islands">Islands</button>
|
|
292
|
+
<button class="debug-tab" data-tab="database">Database</button>
|
|
279
293
|
<button class="debug-tab" data-tab="effects">Effects</button>
|
|
280
294
|
<button class="debug-tab" data-tab="session">Session</button>
|
|
281
295
|
<button class="debug-tab" data-tab="bridges">Bridges</button>
|
|
@@ -313,11 +327,14 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
313
327
|
</ul>
|
|
314
328
|
</div>
|
|
315
329
|
|
|
330
|
+
<!-- DATABASE TAB -->
|
|
331
|
+
<div class="debug-pane" id="pane-database">
|
|
332
|
+
\${getDbHtml(data.effects)}
|
|
333
|
+
</div>
|
|
334
|
+
|
|
316
335
|
<!-- EFFECTS TAB -->
|
|
317
336
|
<div class="debug-pane" id="pane-effects">
|
|
318
|
-
|
|
319
|
-
\${effectsHtml}
|
|
320
|
-
</ul>
|
|
337
|
+
\${getEffectsHtml(data.effects)}
|
|
321
338
|
</div>
|
|
322
339
|
|
|
323
340
|
<!-- SESSION TAB -->
|
|
@@ -379,6 +396,38 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
379
396
|
highlight.style.display = 'none';
|
|
380
397
|
};
|
|
381
398
|
|
|
399
|
+
// Track island mutations to detect client-side state updates
|
|
400
|
+
const islandMutations = new Map();
|
|
401
|
+
const activeObservers = new Map();
|
|
402
|
+
|
|
403
|
+
const setupMutationObservers = () => {
|
|
404
|
+
for (const obs of activeObservers.values()) {
|
|
405
|
+
obs.disconnect();
|
|
406
|
+
}
|
|
407
|
+
activeObservers.clear();
|
|
408
|
+
|
|
409
|
+
const markers = Array.from(document.getElementsByTagName('elm-ssr-island'));
|
|
410
|
+
markers.forEach(marker => {
|
|
411
|
+
if (!islandMutations.has(marker)) {
|
|
412
|
+
islandMutations.set(marker, { count: 0, lastUpdate: null });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const observer = new MutationObserver(() => {
|
|
416
|
+
const stats = islandMutations.get(marker);
|
|
417
|
+
stats.count += 1;
|
|
418
|
+
stats.lastUpdate = new Date();
|
|
419
|
+
|
|
420
|
+
const activeTab = panel.querySelector('.debug-tab.active')?.getAttribute('data-tab');
|
|
421
|
+
if (activeTab === 'islands') {
|
|
422
|
+
scanIslands();
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
observer.observe(marker, { childList: true, subtree: true, characterData: true, attributes: true });
|
|
427
|
+
activeObservers.set(marker, observer);
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
|
|
382
431
|
const scanIslands = () => {
|
|
383
432
|
const list = panel.querySelector('#debug-islands-list');
|
|
384
433
|
const markers = Array.from(document.getElementsByTagName('elm-ssr-island'));
|
|
@@ -395,6 +444,11 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
395
444
|
const isBooted = marker.getAttribute('data-elmssr-booted') === 'true';
|
|
396
445
|
const id = marker.getAttribute('data-elmssr-id') || 'none';
|
|
397
446
|
|
|
447
|
+
const stats = islandMutations.get(marker) || { count: 0, lastUpdate: null };
|
|
448
|
+
const lastUpdateStr = stats.lastUpdate
|
|
449
|
+
? \`Last active DOM mutation: \${stats.lastUpdate.toLocaleTimeString()}\`
|
|
450
|
+
: 'No client state changes recorded';
|
|
451
|
+
|
|
398
452
|
const li = document.createElement('li');
|
|
399
453
|
li.className = 'debug-item';
|
|
400
454
|
li.style.cursor = 'pointer';
|
|
@@ -406,9 +460,12 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
406
460
|
\${isBooted ? 'BOOTED' : 'INITIALIZING'}
|
|
407
461
|
</span>
|
|
408
462
|
</div>
|
|
409
|
-
<div style="font-size: 0.8em; margin-top: 4px; color: #9ca3af;">
|
|
463
|
+
<div style="font-size: 0.8em; margin-top: 4px; color: #9ca3af; display: flex; justify-content: space-between;">
|
|
410
464
|
<span><strong>ID:</strong> \${id}</span>
|
|
465
|
+
<span style="color: #34d399;"><strong>DOM Mutations:</strong> \${stats.count}</span>
|
|
411
466
|
</div>
|
|
467
|
+
<div style="font-size: 0.75em; color: #9ca3af; margin-top: 2px;">\${lastUpdateStr}</div>
|
|
468
|
+
<div style="font-size: 0.8em; margin-top: 6px; color: #e5e7eb;"><strong>Initial Props:</strong></div>
|
|
412
469
|
<pre class="debug-code" style="font-size: 0.85em; max-height: 80px; overflow-y: auto;">\${JSON.stringify(JSON.parse(props), null, 2)}</pre>
|
|
413
470
|
</div>
|
|
414
471
|
\`;
|
|
@@ -420,6 +477,7 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
420
477
|
};
|
|
421
478
|
|
|
422
479
|
panel.querySelector('[data-tab="islands"]').addEventListener('click', scanIslands);
|
|
480
|
+
setupMutationObservers();
|
|
423
481
|
|
|
424
482
|
const bridgeLog = panel.querySelector('#debug-bridges-log');
|
|
425
483
|
let firstBridge = true;
|
|
@@ -472,35 +530,11 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
472
530
|
</div>
|
|
473
531
|
\`;
|
|
474
532
|
|
|
533
|
+
const dbPane = panel.querySelector('#pane-database');
|
|
534
|
+
dbPane.innerHTML = getDbHtml(newData.effects);
|
|
535
|
+
|
|
475
536
|
const effectsPane = panel.querySelector('#pane-effects');
|
|
476
|
-
|
|
477
|
-
if (newData.effects && newData.effects.length > 0) {
|
|
478
|
-
newEffectsHtml = newData.effects.map(eff => {
|
|
479
|
-
const isSql = ['query', 'queryOne', 'execute'].includes(eff.kind);
|
|
480
|
-
const sqlSnippet = isSql ? \`<pre class="debug-code">\${eff.payload.sql}</pre>\` : '';
|
|
481
|
-
const paramsSnippet = isSql && eff.payload.params && eff.payload.params.length > 0 ?
|
|
482
|
-
\`<div><strong>Params:</strong> \${JSON.stringify(eff.payload.params)}</div>\` : '';
|
|
483
|
-
const outcome = eff.ok ? '<span class="status-ok">SUCCESS</span>' : '<span class="status-error">FAILED</span>';
|
|
484
|
-
const detailError = eff.error ? \`<div class="status-error">\${eff.error}</div>\` : '';
|
|
485
|
-
return \`
|
|
486
|
-
<li class="debug-item">
|
|
487
|
-
<div class="debug-item-meta" style="flex: 1;">
|
|
488
|
-
<div style="display: flex; gap: 8px; align-items: center;">
|
|
489
|
-
<span class="debug-badge badge-primary">\${eff.kind}</span>
|
|
490
|
-
\${outcome}
|
|
491
|
-
<span style="color: #9ca3af; font-size: 0.85em;">\${eff.durationMs.toFixed(1)}ms</span>
|
|
492
|
-
</div>
|
|
493
|
-
\${sqlSnippet}
|
|
494
|
-
\${paramsSnippet}
|
|
495
|
-
\${detailError}
|
|
496
|
-
</div>
|
|
497
|
-
</li>
|
|
498
|
-
\`;
|
|
499
|
-
}).join('');
|
|
500
|
-
effectsPane.innerHTML = \`<ul class="debug-list">\${newEffectsHtml}</ul>\`;
|
|
501
|
-
} else {
|
|
502
|
-
effectsPane.innerHTML = '<div style="color: #9ca3af; text-align: center; padding: 20px;">No server-side effects executed for this request.</div>';
|
|
503
|
-
}
|
|
537
|
+
effectsPane.innerHTML = getEffectsHtml(newData.effects);
|
|
504
538
|
|
|
505
539
|
const sessionPane = panel.querySelector('#pane-session');
|
|
506
540
|
sessionPane.innerHTML = \`
|
|
@@ -512,6 +546,8 @@ export const injectDebugger = (html: string, data: unknown): string => {
|
|
|
512
546
|
</div>
|
|
513
547
|
\`;
|
|
514
548
|
|
|
549
|
+
setupMutationObservers(); // Observe any newly mounted islands
|
|
550
|
+
|
|
515
551
|
const activeTab = panel.querySelector('.debug-tab.active').getAttribute('data-tab');
|
|
516
552
|
if (activeTab === 'islands') {
|
|
517
553
|
scanIslands();
|