@korajs/devtools 0.1.3 → 0.3.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/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -266,6 +276,7 @@ var EVENT_TYPE_CATEGORIES = {
266
276
  "constraint:violated": "merge",
267
277
  "sync:connected": "sync",
268
278
  "sync:disconnected": "sync",
279
+ "sync:auth-failed": "sync",
269
280
  "sync:sent": "sync",
270
281
  "sync:received": "sync",
271
282
  "sync:acknowledged": "sync",
@@ -407,7 +418,7 @@ function buildPanelModel(events) {
407
418
  dependsOn: extractCausalDependencies(entry.event)
408
419
  }));
409
420
  const conflicts = events.flatMap((entry) => {
410
- if (entry.event.type !== "merge:completed" && entry.event.type !== "merge:conflict") {
421
+ if (entry.event.type !== "merge:completed" && entry.event.type !== "merge:conflict" && entry.event.type !== "constraint:violated") {
411
422
  return [];
412
423
  }
413
424
  const trace = entry.event.trace;
@@ -518,6 +529,8 @@ function timelineLabel(event) {
518
529
  return `sync connected ${event.nodeId}`;
519
530
  case "sync:disconnected":
520
531
  return `sync disconnected`;
532
+ case "sync:auth-failed":
533
+ return `sync auth failed`;
521
534
  case "sync:sent":
522
535
  return `sync sent ${event.batchSize}`;
523
536
  case "sync:received":
@@ -558,40 +571,462 @@ function extractOperation(event) {
558
571
  }
559
572
 
560
573
  // src/ui/panel.ts
561
- function renderDevtoolsPanel(target, events) {
562
- const model = buildPanelModel(events);
563
- target.innerHTML = [
564
- '<section data-panel="timeline"><h2>Sync Timeline</h2>',
565
- `<p>Total events: ${model.timeline.length}</p>`,
566
- "<ul>",
567
- ...model.timeline.slice(-20).map(
568
- (item) => `<li><span style="color:${item.color}">${item.type}</span> \xB7 ${escapeHtml(item.label)}</li>`
569
- ),
570
- "</ul></section>",
571
- '<section data-panel="conflicts"><h2>Conflict Inspector</h2>',
572
- `<p>Conflicts: ${model.conflicts.length}</p>`,
573
- "<ul>",
574
- ...model.conflicts.slice(-20).map(
575
- (item) => `<li>${escapeHtml(item.collection)}.${escapeHtml(item.field)} \xB7 ${escapeHtml(item.strategy)} \xB7 tier ${item.tier}</li>`
576
- ),
577
- "</ul></section>",
578
- '<section data-panel="operations"><h2>Operation Log</h2>',
579
- `<p>Operations: ${model.operations.length}</p>`,
580
- "<ul>",
581
- ...model.operations.slice(-20).map(
582
- (item) => `<li>${escapeHtml(item.opType)} ${escapeHtml(item.collection)}/${escapeHtml(item.recordId)} (${escapeHtml(item.operationId)})</li>`
583
- ),
584
- "</ul></section>",
585
- '<section data-panel="network"><h2>Network Status</h2>',
586
- `<p>Connected: ${model.network.connected ? "yes" : "no"}</p>`,
587
- `<p>Pending ACKs: ${model.network.pendingAcks}</p>`,
588
- `<p>Sent ops: ${model.network.sentOps}</p>`,
589
- `<p>Received ops: ${model.network.receivedOps}</p>`,
590
- "</section>"
591
- ].join("");
574
+ var import_preact = require("preact");
575
+ var import_hooks = require("preact/hooks");
576
+ var import_htm = __toESM(require("htm"), 1);
577
+
578
+ // src/ui/components.ts
579
+ function formatTime(timestamp) {
580
+ const d = new Date(timestamp);
581
+ const h2 = String(d.getHours()).padStart(2, "0");
582
+ const m = String(d.getMinutes()).padStart(2, "0");
583
+ const s = String(d.getSeconds()).padStart(2, "0");
584
+ const ms = String(d.getMilliseconds()).padStart(3, "0");
585
+ return `${h2}:${m}:${s}.${ms}`;
586
+ }
587
+ function truncate(str, max) {
588
+ if (str.length <= max) return str;
589
+ return str.slice(0, max - 1) + "\u2026";
590
+ }
591
+ function formatValue(value) {
592
+ if (value === null || value === void 0) return "null";
593
+ if (typeof value === "string") return `"${value}"`;
594
+ if (typeof value === "object") return JSON.stringify(value, null, 2);
595
+ return String(value);
596
+ }
597
+
598
+ // src/ui/panel.ts
599
+ var html = import_htm.default.bind(import_preact.h);
600
+ var ALL_CATEGORIES = ["operation", "merge", "sync", "query", "connection"];
601
+ function DevToolsPanel({ events }) {
602
+ const [activeTab, setActiveTab] = (0, import_hooks.useState)("timeline");
603
+ const [search, setSearch] = (0, import_hooks.useState)("");
604
+ const [categories, setCategories] = (0, import_hooks.useState)(
605
+ () => new Set(ALL_CATEGORIES)
606
+ );
607
+ const [expanded, setExpanded] = (0, import_hooks.useState)(() => /* @__PURE__ */ new Set());
608
+ const [paused, setPaused] = (0, import_hooks.useState)(false);
609
+ const toggleCategory = (0, import_hooks.useCallback)(
610
+ (cat) => {
611
+ setCategories((prev) => {
612
+ const next = new Set(prev);
613
+ if (next.has(cat)) next.delete(cat);
614
+ else next.add(cat);
615
+ return next;
616
+ });
617
+ },
618
+ []
619
+ );
620
+ const toggleExpand = (0, import_hooks.useCallback)(
621
+ (id) => {
622
+ setExpanded((prev) => {
623
+ const next = new Set(prev);
624
+ if (next.has(id)) next.delete(id);
625
+ else next.add(id);
626
+ return next;
627
+ });
628
+ },
629
+ []
630
+ );
631
+ const filtered = (0, import_hooks.useMemo)(
632
+ () => filterEvents(events, { categories: [...categories] }),
633
+ [events, categories]
634
+ );
635
+ const model = (0, import_hooks.useMemo)(() => buildPanelModel(filtered), [filtered]);
636
+ const stats = (0, import_hooks.useMemo)(() => computeStatistics(events), [events]);
637
+ return html`
638
+ <div class="kora-devtools">
639
+ <${Toolbar}
640
+ activeTab=${activeTab}
641
+ onTabChange=${setActiveTab}
642
+ totalEvents=${events.length}
643
+ conflicts=${stats.mergeConflicts}
644
+ search=${search}
645
+ onSearchChange=${setSearch}
646
+ paused=${paused}
647
+ onPauseToggle=${() => setPaused((p) => !p)}
648
+ onClear=${() => setExpanded(/* @__PURE__ */ new Set())}
649
+ categories=${categories}
650
+ onToggleCategory=${toggleCategory}
651
+ />
652
+ <div class="kora-content">
653
+ ${activeTab === "timeline" && html`<${TimelinePanel}
654
+ items=${model.timeline}
655
+ search=${search}
656
+ expanded=${expanded}
657
+ onToggle=${toggleExpand}
658
+ />`}
659
+ ${activeTab === "conflicts" && html`<${ConflictsPanel}
660
+ items=${model.conflicts}
661
+ search=${search}
662
+ expanded=${expanded}
663
+ onToggle=${toggleExpand}
664
+ />`}
665
+ ${activeTab === "operations" && html`<${OperationsPanel}
666
+ items=${model.operations}
667
+ search=${search}
668
+ expanded=${expanded}
669
+ onToggle=${toggleExpand}
670
+ />`}
671
+ ${activeTab === "network" && html`<${NetworkPanel} network=${model.network} events=${events} />`}
672
+ </div>
673
+ </div>
674
+ `;
675
+ }
676
+ function Toolbar({
677
+ activeTab,
678
+ onTabChange,
679
+ totalEvents,
680
+ conflicts,
681
+ search,
682
+ onSearchChange,
683
+ paused,
684
+ onPauseToggle,
685
+ onClear,
686
+ categories,
687
+ onToggleCategory
688
+ }) {
689
+ const tabs = [
690
+ { id: "timeline", label: "Timeline", badge: totalEvents },
691
+ { id: "conflicts", label: "Conflicts", badge: conflicts },
692
+ { id: "operations", label: "Operations" },
693
+ { id: "network", label: "Network" }
694
+ ];
695
+ return html`
696
+ <div class="kora-toolbar">
697
+ <div class="kora-tabs">
698
+ ${tabs.map(
699
+ (tab) => html`
700
+ <button
701
+ class="kora-tab${activeTab === tab.id ? " active" : ""}"
702
+ onClick=${() => onTabChange(tab.id)}
703
+ >
704
+ ${tab.label}
705
+ ${tab.badge != null && tab.badge > 0 ? html`<span class="kora-badge">${tab.badge}</span>` : null}
706
+ </button>
707
+ `
708
+ )}
709
+ </div>
710
+ <div class="kora-controls">
711
+ <input
712
+ type="text"
713
+ class="kora-search"
714
+ placeholder="Filter..."
715
+ value=${search}
716
+ onInput=${(e) => onSearchChange(e.target.value)}
717
+ />
718
+ <button class="kora-btn" onClick=${onPauseToggle}>
719
+ ${paused ? "Resume" : "Pause"}
720
+ </button>
721
+ <button class="kora-btn" onClick=${onClear}>Clear</button>
722
+ ${ALL_CATEGORIES.map(
723
+ (cat) => html`
724
+ <button
725
+ class="kora-cat-toggle${categories.has(cat) ? " active" : ""}"
726
+ onClick=${() => onToggleCategory(cat)}
727
+ >
728
+ ${cat}
729
+ </button>
730
+ `
731
+ )}
732
+ </div>
733
+ </div>
734
+ `;
735
+ }
736
+ function TimelinePanel({
737
+ items,
738
+ search,
739
+ expanded,
740
+ onToggle
741
+ }) {
742
+ const lowerSearch = search.toLowerCase();
743
+ const filtered = lowerSearch ? items.filter(
744
+ (i) => i.label.toLowerCase().includes(lowerSearch) || i.type.toLowerCase().includes(lowerSearch)
745
+ ) : items;
746
+ const visible = filtered.slice(-200).reverse();
747
+ if (visible.length === 0) {
748
+ return html`<div class="kora-panel"><div class="kora-empty">No events recorded yet.</div></div>`;
749
+ }
750
+ return html`
751
+ <div class="kora-panel kora-timeline">
752
+ <div class="kora-summary">Showing ${visible.length} of ${items.length} events</div>
753
+ <div class="kora-event-list">
754
+ ${visible.map(
755
+ (item) => html`
756
+ <div
757
+ key=${item.id}
758
+ class="kora-row${expanded.has(item.id) ? " expanded" : ""}"
759
+ onClick=${() => onToggle(item.id)}
760
+ >
761
+ <span class="kora-time">${formatTime(item.receivedAt)}</span>
762
+ <span class="kora-dot" style="background:${item.color}"></span>
763
+ <span class="kora-type">${item.type}</span>
764
+ <span class="kora-label">${item.label}</span>
765
+ ${item.dependsOn.length > 0 ? html`<span class="kora-deps">deps: ${item.dependsOn.length}</span>` : null}
766
+ </div>
767
+ ${expanded.has(item.id) ? html`
768
+ <div class="kora-detail">
769
+ <div>
770
+ <span class="kora-detail-label">Event ID:</span> ${item.id}
771
+ </div>
772
+ <div>
773
+ <span class="kora-detail-label">Received:</span>
774
+ ${new Date(item.receivedAt).toISOString()}
775
+ </div>
776
+ ${item.dependsOn.length > 0 ? html`<div>
777
+ <span class="kora-detail-label">Causal deps:</span>
778
+ ${item.dependsOn.join(", ")}
779
+ </div>` : null}
780
+ </div>
781
+ ` : null}
782
+ `
783
+ )}
784
+ </div>
785
+ </div>
786
+ `;
787
+ }
788
+ function ConflictsPanel({
789
+ items,
790
+ search,
791
+ expanded,
792
+ onToggle
793
+ }) {
794
+ const lowerSearch = search.toLowerCase();
795
+ const filtered = lowerSearch ? items.filter(
796
+ (i) => i.collection.toLowerCase().includes(lowerSearch) || i.field.toLowerCase().includes(lowerSearch) || i.strategy.toLowerCase().includes(lowerSearch)
797
+ ) : items;
798
+ const visible = filtered.slice(-100).reverse();
799
+ if (visible.length === 0) {
800
+ return html`<div class="kora-panel"><div class="kora-empty">No merge conflicts detected.</div></div>`;
801
+ }
802
+ return html`
803
+ <div class="kora-panel kora-conflicts">
804
+ <div class="kora-summary">
805
+ ${items.length} conflict${items.length !== 1 ? "s" : ""}
806
+ </div>
807
+ <table class="kora-table">
808
+ <thead>
809
+ <tr>
810
+ <th>Time</th>
811
+ <th>Collection</th>
812
+ <th>Field</th>
813
+ <th>Strategy</th>
814
+ <th>Tier</th>
815
+ <th>Result</th>
816
+ </tr>
817
+ </thead>
818
+ <tbody>
819
+ ${visible.map(
820
+ (item) => html`
821
+ <tr
822
+ key=${item.id}
823
+ class="kora-conflict-row${item.constraintViolated ? " violated" : ""}${expanded.has(item.id) ? " expanded" : ""}"
824
+ onClick=${() => onToggle(item.id)}
825
+ >
826
+ <td>${formatTime(item.timestamp)}</td>
827
+ <td>${item.collection}</td>
828
+ <td class="kora-mono">${item.field}</td>
829
+ <td>
830
+ <span class="kora-strategy tier-${item.tier}">${item.strategy}</span>
831
+ </td>
832
+ <td>${item.tier}</td>
833
+ <td class="kora-mono">${truncate(String(item.output), 30)}</td>
834
+ </tr>
835
+ ${expanded.has(item.id) ? html`
836
+ <tr class="kora-detail-row">
837
+ <td colspan="6">
838
+ <div class="kora-conflict-detail">
839
+ <div class="kora-comparison">
840
+ <div class="kora-value-box">
841
+ <div class="kora-value-label">Input A (local)</div>
842
+ <pre class="kora-value-code">
843
+ ${formatValue(item.inputA)}</pre
844
+ >
845
+ </div>
846
+ <div class="kora-value-box">
847
+ <div class="kora-value-label">Input B (remote)</div>
848
+ <pre class="kora-value-code">
849
+ ${formatValue(item.inputB)}</pre
850
+ >
851
+ </div>
852
+ <div class="kora-value-box result">
853
+ <div class="kora-value-label">Result</div>
854
+ <pre class="kora-value-code">
855
+ ${formatValue(item.output)}</pre
856
+ >
857
+ </div>
858
+ </div>
859
+ ${item.constraintViolated ? html`<div class="kora-constraint-warning">
860
+ Constraint violated: ${item.constraintViolated}
861
+ </div>` : null}
862
+ </div>
863
+ </td>
864
+ </tr>
865
+ ` : null}
866
+ `
867
+ )}
868
+ </tbody>
869
+ </table>
870
+ </div>
871
+ `;
592
872
  }
593
- function escapeHtml(value) {
594
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
873
+ function OperationsPanel({
874
+ items,
875
+ search,
876
+ expanded,
877
+ onToggle
878
+ }) {
879
+ const lowerSearch = search.toLowerCase();
880
+ const filtered = lowerSearch ? items.filter(
881
+ (i) => i.collection.toLowerCase().includes(lowerSearch) || i.recordId.toLowerCase().includes(lowerSearch) || i.operationId.toLowerCase().includes(lowerSearch) || i.opType.toLowerCase().includes(lowerSearch)
882
+ ) : items;
883
+ const visible = filtered.slice(-200).reverse();
884
+ if (visible.length === 0) {
885
+ return html`<div class="kora-panel"><div class="kora-empty">No operations recorded yet.</div></div>`;
886
+ }
887
+ return html`
888
+ <div class="kora-panel kora-operations">
889
+ <div class="kora-summary">
890
+ Showing ${visible.length} of ${items.length} operations
891
+ </div>
892
+ <div class="kora-op-list">
893
+ ${visible.map(
894
+ (item) => html`
895
+ <div
896
+ key=${item.id}
897
+ class="kora-op-row${expanded.has(item.id) ? " expanded" : ""}"
898
+ onClick=${() => onToggle(item.id)}
899
+ >
900
+ <span class="kora-time">${formatTime(item.timestamp)}</span>
901
+ <span class="kora-op-type op-${item.opType}">${item.opType}</span>
902
+ <span class="kora-op-collection">${item.collection}</span>
903
+ <span class="kora-op-record kora-mono"
904
+ >${truncate(item.recordId, 12)}</span
905
+ >
906
+ <span class="kora-op-node">node:${truncate(item.nodeId, 8)}</span>
907
+ <span class="kora-op-seq">#${item.sequenceNumber}</span>
908
+ </div>
909
+ ${expanded.has(item.id) ? html`
910
+ <div class="kora-op-detail">
911
+ <div>
912
+ <span class="kora-detail-label">Operation ID:</span>
913
+ <span class="kora-mono">${item.operationId}</span>
914
+ </div>
915
+ <div>
916
+ <span class="kora-detail-label">Node:</span>
917
+ <span class="kora-mono">${item.nodeId}</span>
918
+ </div>
919
+ <div>
920
+ <span class="kora-detail-label">Sequence:</span>
921
+ ${item.sequenceNumber}
922
+ </div>
923
+ ${item.causalDeps.length > 0 ? html`<div>
924
+ <span class="kora-detail-label">Causal deps:</span>
925
+ <span class="kora-mono"
926
+ >${item.causalDeps.join(", ")}</span
927
+ >
928
+ </div>` : null}
929
+ ${item.data ? html`<div>
930
+ <div class="kora-detail-label">Data:</div>
931
+ <pre class="kora-value-code">
932
+ ${JSON.stringify(item.data, null, 2)}</pre
933
+ >
934
+ </div>` : null}
935
+ </div>
936
+ ` : null}
937
+ `
938
+ )}
939
+ </div>
940
+ </div>
941
+ `;
942
+ }
943
+ function NetworkPanel({
944
+ network,
945
+ events
946
+ }) {
947
+ const statusClass = network.connected ? "connected" : "disconnected";
948
+ const statusText = network.connected ? network.quality ? `Connected (${network.quality})` : "Connected" : "Disconnected";
949
+ const syncEvents = events.filter((e) => {
950
+ const cat = eventTypeToCategory(e.event.type);
951
+ return cat === "sync" || cat === "connection";
952
+ }).slice(-50).reverse();
953
+ return html`
954
+ <div class="kora-panel kora-network">
955
+ <div class="kora-network-status">
956
+ <div class="kora-status-indicator ${statusClass}">
957
+ <span class="kora-status-dot"></span>
958
+ <span class="kora-status-text">${statusText}</span>
959
+ </div>
960
+ </div>
961
+
962
+ <div class="kora-stats-grid">
963
+ <${StatCard} label="Sent" value=${String(network.sentOps)} unit="ops" />
964
+ <${StatCard} label="Received" value=${String(network.receivedOps)} unit="ops" />
965
+ <${StatCard} label="Pending" value=${String(network.pendingAcks)} unit="acks" />
966
+ <${StatCard}
967
+ label="Last Sync"
968
+ value=${network.lastSyncAt ? formatTime(network.lastSyncAt) : "never"}
969
+ />
970
+ </div>
971
+
972
+ ${network.versionVector.length > 0 ? html`
973
+ <div class="kora-vv-section">
974
+ <h3>Version Vector</h3>
975
+ <table class="kora-table kora-vv-table">
976
+ <thead>
977
+ <tr>
978
+ <th>Node</th>
979
+ <th>Sequence</th>
980
+ </tr>
981
+ </thead>
982
+ <tbody>
983
+ ${network.versionVector.map(
984
+ (entry) => html`
985
+ <tr key=${entry.nodeId}>
986
+ <td class="kora-mono">${truncate(entry.nodeId, 16)}</td>
987
+ <td>${entry.sequenceNumber}</td>
988
+ </tr>
989
+ `
990
+ )}
991
+ </tbody>
992
+ </table>
993
+ </div>
994
+ ` : null}
995
+
996
+ ${syncEvents.length > 0 ? html`
997
+ <div class="kora-sync-log">
998
+ <h3>Recent Sync Activity</h3>
999
+ <div class="kora-event-list">
1000
+ ${syncEvents.map(
1001
+ (entry) => html`
1002
+ <div key=${entry.id} class="kora-row compact">
1003
+ <span class="kora-time"
1004
+ >${formatTime(entry.receivedAt)}</span
1005
+ >
1006
+ <span class="kora-type">${entry.event.type}</span>
1007
+ </div>
1008
+ `
1009
+ )}
1010
+ </div>
1011
+ </div>
1012
+ ` : null}
1013
+ </div>
1014
+ `;
1015
+ }
1016
+ function StatCard({
1017
+ label,
1018
+ value,
1019
+ unit
1020
+ }) {
1021
+ return html`
1022
+ <div class="kora-stat-card">
1023
+ <div class="kora-stat-value">${value}</div>
1024
+ <div class="kora-stat-label">${label}${unit ? ` (${unit})` : ""}</div>
1025
+ </div>
1026
+ `;
1027
+ }
1028
+ function renderDevtoolsPanel(target, events) {
1029
+ (0, import_preact.render)(html`<${DevToolsPanel} events=${events} />`, target);
595
1030
  }
596
1031
 
597
1032
  // src/extension/port-router.ts