@korajs/devtools 0.1.0 → 0.1.3

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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ui/panel-state.ts","../src/ui/panel.ts"],"sourcesContent":["import type { KoraEvent, Operation } from '@korajs/core'\nimport type { TimestampedEvent } from '../types'\n\nexport interface TimelineItem {\n\tid: number\n\ttype: KoraEvent['type']\n\tlabel: string\n\tcolor: string\n\treceivedAt: number\n\tdependsOn: string[]\n}\n\nexport interface ConflictItem {\n\tid: number\n\ttimestamp: number\n\tcollection: string\n\tfield: string\n\tstrategy: string\n\ttier: 1 | 2 | 3\n\tinputA: unknown\n\tinputB: unknown\n\toutput: unknown\n\tconstraintViolated: string | null\n}\n\nexport interface OperationItem {\n\tid: number\n\ttimestamp: number\n\toperationId: string\n\tcollection: string\n\trecordId: string\n\topType: Operation['type']\n\tdata: Record<string, unknown> | null\n\tcausalDeps: string[]\n\tnodeId: string\n\tsequenceNumber: number\n}\n\nexport interface NetworkStatusModel {\n\tconnected: boolean\n\tquality: string | null\n\tpendingAcks: number\n\tlastSyncAt: number | null\n\tsentOps: number\n\treceivedOps: number\n\tversionVector: Array<{ nodeId: string; sequenceNumber: number }>\n}\n\nexport interface DevtoolsPanelModel {\n\ttimeline: TimelineItem[]\n\tconflicts: ConflictItem[]\n\toperations: OperationItem[]\n\tnetwork: NetworkStatusModel\n}\n\nexport function buildPanelModel(events: readonly TimestampedEvent[]): DevtoolsPanelModel {\n\tconst timeline = events.map((entry) => ({\n\t\tid: entry.id,\n\t\ttype: entry.event.type,\n\t\tlabel: timelineLabel(entry.event),\n\t\tcolor: timelineColor(entry.event.type),\n\t\treceivedAt: entry.receivedAt,\n\t\tdependsOn: extractCausalDependencies(entry.event),\n\t}))\n\n\tconst conflicts = events\n\t\t.flatMap((entry) => {\n\t\t\tif (entry.event.type !== 'merge:completed' && entry.event.type !== 'merge:conflict') {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst trace = entry.event.trace\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tid: entry.id,\n\t\t\t\t\ttimestamp: entry.receivedAt,\n\t\t\t\t\tcollection: trace.operationA.collection,\n\t\t\t\t\tfield: trace.field,\n\t\t\t\t\tstrategy: trace.strategy,\n\t\t\t\t\ttier: trace.tier,\n\t\t\t\t\tinputA: trace.inputA,\n\t\t\t\t\tinputB: trace.inputB,\n\t\t\t\t\toutput: trace.output,\n\t\t\t\t\tconstraintViolated: trace.constraintViolated,\n\t\t\t\t},\n\t\t\t]\n\t\t})\n\n\tconst operations = events\n\t\t.map((entry) => {\n\t\t\tconst operation = extractOperation(entry.event)\n\t\t\tif (!operation) return null\n\n\t\t\treturn {\n\t\t\t\tid: entry.id,\n\t\t\t\ttimestamp: entry.receivedAt,\n\t\t\t\toperationId: operation.id,\n\t\t\t\tcollection: operation.collection,\n\t\t\t\trecordId: operation.recordId,\n\t\t\t\topType: operation.type,\n\t\t\t\tdata: operation.data,\n\t\t\t\tcausalDeps: operation.causalDeps,\n\t\t\t\tnodeId: operation.nodeId,\n\t\t\t\tsequenceNumber: operation.sequenceNumber,\n\t\t\t}\n\t\t})\n\t\t.filter((item): item is OperationItem => item !== null)\n\n\tconst network = buildNetworkStatus(events, operations)\n\n\treturn {\n\t\ttimeline,\n\t\tconflicts,\n\t\toperations,\n\t\tnetwork,\n\t}\n}\n\nfunction buildNetworkStatus(\n\tevents: readonly TimestampedEvent[],\n\toperations: readonly OperationItem[],\n): NetworkStatusModel {\n\tlet connected = false\n\tlet quality: string | null = null\n\tlet pendingAcks = 0\n\tlet lastSyncAt: number | null = null\n\tlet sentOps = 0\n\tlet receivedOps = 0\n\n\tfor (const entry of events) {\n\t\tswitch (entry.event.type) {\n\t\t\tcase 'sync:connected':\n\t\t\t\tconnected = true\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:disconnected':\n\t\t\t\tconnected = false\n\t\t\t\tbreak\n\t\t\tcase 'connection:quality':\n\t\t\t\tquality = entry.event.quality\n\t\t\t\tbreak\n\t\t\tcase 'sync:sent':\n\t\t\t\tsentOps += entry.event.operations.length\n\t\t\t\tpendingAcks += entry.event.operations.length\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:received':\n\t\t\t\treceivedOps += entry.event.operations.length\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:acknowledged':\n\t\t\t\tpendingAcks = Math.max(0, pendingAcks - 1)\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\tconst vector = new Map<string, number>()\n\tfor (const operation of operations) {\n\t\tconst current = vector.get(operation.nodeId) ?? 0\n\t\tif (operation.sequenceNumber > current) {\n\t\t\tvector.set(operation.nodeId, operation.sequenceNumber)\n\t\t}\n\t}\n\n\treturn {\n\t\tconnected,\n\t\tquality,\n\t\tpendingAcks,\n\t\tlastSyncAt,\n\t\tsentOps,\n\t\treceivedOps,\n\t\tversionVector: [...vector.entries()]\n\t\t\t.map(([nodeId, sequenceNumber]) => ({ nodeId, sequenceNumber }))\n\t\t\t.sort((left, right) => left.nodeId.localeCompare(right.nodeId)),\n\t}\n}\n\nfunction timelineLabel(event: KoraEvent): string {\n\tswitch (event.type) {\n\t\tcase 'operation:created':\n\t\tcase 'operation:applied':\n\t\t\treturn `${event.operation.type} ${event.operation.collection}/${event.operation.recordId}`\n\t\tcase 'merge:started':\n\t\t\treturn `merge start ${event.operationA.collection}`\n\t\tcase 'merge:completed':\n\t\t\treturn `merge complete ${event.trace.field}`\n\t\tcase 'merge:conflict':\n\t\t\treturn `merge conflict ${event.trace.field}`\n\t\tcase 'constraint:violated':\n\t\t\treturn `constraint ${event.constraint}`\n\t\tcase 'sync:connected':\n\t\t\treturn `sync connected ${event.nodeId}`\n\t\tcase 'sync:disconnected':\n\t\t\treturn `sync disconnected`\n\t\tcase 'sync:sent':\n\t\t\treturn `sync sent ${event.batchSize}`\n\t\tcase 'sync:received':\n\t\t\treturn `sync received ${event.batchSize}`\n\t\tcase 'sync:acknowledged':\n\t\t\treturn `sync ack ${event.sequenceNumber}`\n\t\tcase 'query:subscribed':\n\t\t\treturn `query subscribed ${event.collection}`\n\t\tcase 'query:invalidated':\n\t\t\treturn `query invalidated ${event.queryId}`\n\t\tcase 'query:executed':\n\t\t\treturn `query executed ${event.queryId}`\n\t\tcase 'connection:quality':\n\t\t\treturn `connection ${event.quality}`\n\t}\n}\n\nfunction timelineColor(type: KoraEvent['type']): string {\n\tif (type.startsWith('operation:')) return '#22c55e'\n\tif (type.startsWith('sync:')) return '#a855f7'\n\tif (type.startsWith('merge:') || type.startsWith('constraint:')) return '#f59e0b'\n\tif (type.startsWith('query:')) return '#0ea5e9'\n\treturn '#64748b'\n}\n\nfunction extractCausalDependencies(event: KoraEvent): string[] {\n\tconst operation = extractOperation(event)\n\treturn operation?.causalDeps ?? []\n}\n\nfunction extractOperation(event: KoraEvent): Operation | null {\n\tswitch (event.type) {\n\t\tcase 'operation:created':\n\t\tcase 'operation:applied':\n\t\t\treturn event.operation\n\t\tcase 'query:invalidated':\n\t\t\treturn event.trigger\n\t\tdefault:\n\t\t\treturn null\n\t}\n}\n","import type { TimestampedEvent } from '../types'\nimport { buildPanelModel } from './panel-state'\n\nexport function renderDevtoolsPanel(target: HTMLElement, events: readonly TimestampedEvent[]): void {\n\tconst model = buildPanelModel(events)\n\n\ttarget.innerHTML = [\n\t\t'<section data-panel=\"timeline\"><h2>Sync Timeline</h2>',\n\t\t`<p>Total events: ${model.timeline.length}</p>`,\n\t\t'<ul>',\n\t\t...model.timeline.slice(-20).map(\n\t\t\t(item) =>\n\t\t\t\t`<li><span style=\"color:${item.color}\">${item.type}</span> · ${escapeHtml(item.label)}</li>`,\n\t\t),\n\t\t'</ul></section>',\n\t\t'<section data-panel=\"conflicts\"><h2>Conflict Inspector</h2>',\n\t\t`<p>Conflicts: ${model.conflicts.length}</p>`,\n\t\t'<ul>',\n\t\t...model.conflicts\n\t\t\t.slice(-20)\n\t\t\t.map(\n\t\t\t\t(item) =>\n\t\t\t\t\t`<li>${escapeHtml(item.collection)}.${escapeHtml(item.field)} · ${escapeHtml(item.strategy)} · tier ${item.tier}</li>`,\n\t\t\t),\n\t\t'</ul></section>',\n\t\t'<section data-panel=\"operations\"><h2>Operation Log</h2>',\n\t\t`<p>Operations: ${model.operations.length}</p>`,\n\t\t'<ul>',\n\t\t...model.operations.slice(-20).map(\n\t\t\t(item) =>\n\t\t\t\t`<li>${escapeHtml(item.opType)} ${escapeHtml(item.collection)}/${escapeHtml(item.recordId)} (${escapeHtml(item.operationId)})</li>`,\n\t\t),\n\t\t'</ul></section>',\n\t\t'<section data-panel=\"network\"><h2>Network Status</h2>',\n\t\t`<p>Connected: ${model.network.connected ? 'yes' : 'no'}</p>`,\n\t\t`<p>Pending ACKs: ${model.network.pendingAcks}</p>`,\n\t\t`<p>Sent ops: ${model.network.sentOps}</p>`,\n\t\t`<p>Received ops: ${model.network.receivedOps}</p>`,\n\t\t'</section>',\n\t].join('')\n}\n\nfunction escapeHtml(value: string): string {\n\treturn value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')\n}\n"],"mappings":"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","names":[]}
@@ -1,58 +0,0 @@
1
- // src/extension/port-router.ts
2
- var PortRouter = class {
3
- panelClients = /* @__PURE__ */ new Map();
4
- contentClients = /* @__PURE__ */ new Map();
5
- handleConnection(port) {
6
- if (port.name === "kora-panel") {
7
- this.attachPanel(port);
8
- return;
9
- }
10
- if (port.name === "kora-content") {
11
- this.attachContent(port);
12
- }
13
- }
14
- attachPanel(port) {
15
- port.onMessage.addListener((message) => {
16
- if (!isPanelInitMessage(message)) return;
17
- this.panelClients.set(message.tabId, { tabId: message.tabId, port });
18
- });
19
- port.onDisconnect.addListener(() => {
20
- for (const [tabId, client] of this.panelClients) {
21
- if (client.port === port) {
22
- this.panelClients.delete(tabId);
23
- }
24
- }
25
- });
26
- }
27
- attachContent(port) {
28
- const tabId = port.sender?.tab?.id;
29
- if (typeof tabId !== "number") {
30
- return;
31
- }
32
- this.contentClients.set(tabId, port);
33
- port.onMessage.addListener((message) => {
34
- if (!isContentEventMessage(message)) return;
35
- const panel = this.panelClients.get(tabId);
36
- if (!panel) return;
37
- panel.port.postMessage({ type: "kora-event", payload: message.payload });
38
- });
39
- port.onDisconnect.addListener(() => {
40
- this.contentClients.delete(tabId);
41
- });
42
- }
43
- };
44
- function isPanelInitMessage(value) {
45
- if (typeof value !== "object" || value === null) return false;
46
- const record = value;
47
- return record.type === "panel-init" && typeof record.tabId === "number";
48
- }
49
- function isContentEventMessage(value) {
50
- if (typeof value !== "object" || value === null) return false;
51
- const record = value;
52
- return record.type === "kora-event" && "payload" in record;
53
- }
54
-
55
- export {
56
- PortRouter
57
- };
58
- //# sourceMappingURL=chunk-JH2X4T4Z.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/extension/port-router.ts"],"sourcesContent":["export interface ExtensionPort {\n\tname: string\n\tonMessage: {\n\t\taddListener(callback: (message: unknown) => void): void\n\t}\n\tonDisconnect: {\n\t\taddListener(callback: () => void): void\n\t}\n\tpostMessage(message: unknown): void\n\tsender?: { tab?: { id?: number } }\n}\n\ninterface PanelClient {\n\tport: ExtensionPort\n\ttabId: number\n}\n\n/**\n * Routes content-script events to the matching DevTools panel by tab.\n */\nexport class PortRouter {\n\tprivate readonly panelClients = new Map<number, PanelClient>()\n\tprivate readonly contentClients = new Map<number, ExtensionPort>()\n\n\thandleConnection(port: ExtensionPort): void {\n\t\tif (port.name === 'kora-panel') {\n\t\t\tthis.attachPanel(port)\n\t\t\treturn\n\t\t}\n\n\t\tif (port.name === 'kora-content') {\n\t\t\tthis.attachContent(port)\n\t\t}\n\t}\n\n\tprivate attachPanel(port: ExtensionPort): void {\n\t\tport.onMessage.addListener((message) => {\n\t\t\tif (!isPanelInitMessage(message)) return\n\n\t\t\tthis.panelClients.set(message.tabId, { tabId: message.tabId, port })\n\t\t})\n\n\t\tport.onDisconnect.addListener(() => {\n\t\t\tfor (const [tabId, client] of this.panelClients) {\n\t\t\t\tif (client.port === port) {\n\t\t\t\t\tthis.panelClients.delete(tabId)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate attachContent(port: ExtensionPort): void {\n\t\tconst tabId = port.sender?.tab?.id\n\t\tif (typeof tabId !== 'number') {\n\t\t\treturn\n\t\t}\n\n\t\tthis.contentClients.set(tabId, port)\n\n\t\tport.onMessage.addListener((message) => {\n\t\t\tif (!isContentEventMessage(message)) return\n\n\t\t\tconst panel = this.panelClients.get(tabId)\n\t\t\tif (!panel) return\n\n\t\t\tpanel.port.postMessage({ type: 'kora-event', payload: message.payload })\n\t\t})\n\n\t\tport.onDisconnect.addListener(() => {\n\t\t\tthis.contentClients.delete(tabId)\n\t\t})\n\t}\n}\n\nfunction isPanelInitMessage(value: unknown): value is { type: 'panel-init'; tabId: number } {\n\tif (typeof value !== 'object' || value === null) return false\n\tconst record = value as Record<string, unknown>\n\treturn record.type === 'panel-init' && typeof record.tabId === 'number'\n}\n\nfunction isContentEventMessage(value: unknown): value is { type: 'kora-event'; payload: unknown } {\n\tif (typeof value !== 'object' || value === null) return false\n\tconst record = value as Record<string, unknown>\n\treturn record.type === 'kora-event' && 'payload' in record\n}\n"],"mappings":";AAoBO,IAAM,aAAN,MAAiB;AAAA,EACN,eAAe,oBAAI,IAAyB;AAAA,EAC5C,iBAAiB,oBAAI,IAA2B;AAAA,EAEjE,iBAAiB,MAA2B;AAC3C,QAAI,KAAK,SAAS,cAAc;AAC/B,WAAK,YAAY,IAAI;AACrB;AAAA,IACD;AAEA,QAAI,KAAK,SAAS,gBAAgB;AACjC,WAAK,cAAc,IAAI;AAAA,IACxB;AAAA,EACD;AAAA,EAEQ,YAAY,MAA2B;AAC9C,SAAK,UAAU,YAAY,CAAC,YAAY;AACvC,UAAI,CAAC,mBAAmB,OAAO,EAAG;AAElC,WAAK,aAAa,IAAI,QAAQ,OAAO,EAAE,OAAO,QAAQ,OAAO,KAAK,CAAC;AAAA,IACpE,CAAC;AAED,SAAK,aAAa,YAAY,MAAM;AACnC,iBAAW,CAAC,OAAO,MAAM,KAAK,KAAK,cAAc;AAChD,YAAI,OAAO,SAAS,MAAM;AACzB,eAAK,aAAa,OAAO,KAAK;AAAA,QAC/B;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EAEQ,cAAc,MAA2B;AAChD,UAAM,QAAQ,KAAK,QAAQ,KAAK;AAChC,QAAI,OAAO,UAAU,UAAU;AAC9B;AAAA,IACD;AAEA,SAAK,eAAe,IAAI,OAAO,IAAI;AAEnC,SAAK,UAAU,YAAY,CAAC,YAAY;AACvC,UAAI,CAAC,sBAAsB,OAAO,EAAG;AAErC,YAAM,QAAQ,KAAK,aAAa,IAAI,KAAK;AACzC,UAAI,CAAC,MAAO;AAEZ,YAAM,KAAK,YAAY,EAAE,MAAM,cAAc,SAAS,QAAQ,QAAQ,CAAC;AAAA,IACxE,CAAC;AAED,SAAK,aAAa,YAAY,MAAM;AACnC,WAAK,eAAe,OAAO,KAAK;AAAA,IACjC,CAAC;AAAA,EACF;AACD;AAEA,SAAS,mBAAmB,OAAgE;AAC3F,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,SAAS;AACf,SAAO,OAAO,SAAS,gBAAgB,OAAO,OAAO,UAAU;AAChE;AAEA,SAAS,sBAAsB,OAAmE;AACjG,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,SAAS;AACf,SAAO,OAAO,SAAS,gBAAgB,aAAa;AACrD;","names":[]}