@korajs/devtools 1.0.0-beta.11 → 1.0.0-beta.13
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 +23 -32
- package/dist/{chunk-ZWXYLFF7.js → chunk-LQFL4TWM.js} +53 -2
- package/dist/chunk-LQFL4TWM.js.map +1 -0
- package/dist/extension/panel.cjs +52 -1
- package/dist/extension/panel.cjs.map +1 -1
- package/dist/extension/panel.js +52 -1
- package/dist/extension/panel.js.map +1 -1
- package/dist/index.cjs +62 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/overlay.cjs +52 -1
- package/dist/overlay.cjs.map +1 -1
- package/dist/overlay.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-ZWXYLFF7.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bridge/message-bridge.ts","../src/buffer/event-buffer.ts","../src/instrumenter/instrumenter.ts","../src/extension/port-router.ts"],"sourcesContent":["import type { TimestampedEvent } from '../types'\n\nconst DEFAULT_CHANNEL = 'kora-devtools'\n\n/** Shape of messages posted through the bridge */\ninterface BridgeMessage {\n\tsource: string\n\tpayload: TimestampedEvent\n}\n\n/**\n * Communicates between the page context and a DevTools panel via window.postMessage.\n * All messages are namespaced with a `source` field to avoid collisions with other\n * postMessage consumers on the page.\n *\n * Safe to instantiate in non-browser environments (SSR/Node) — all operations\n * become no-ops when `window` is not available.\n */\nexport class MessageBridge {\n\tprivate readonly channelName: string\n\tprivate readonly listeners: Set<(event: TimestampedEvent) => void> = new Set()\n\tprivate readonly messageHandler: ((event: MessageEvent) => void) | null\n\tprivate destroyed = false\n\n\tconstructor(channelName: string = DEFAULT_CHANNEL) {\n\t\tthis.channelName = channelName\n\n\t\tif (typeof window === 'undefined') {\n\t\t\tthis.messageHandler = null\n\t\t\treturn\n\t\t}\n\n\t\t// Single shared handler that dispatches to all registered callbacks\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\tif (this.destroyed) return\n\t\t\tconst data = event.data as Partial<BridgeMessage> | undefined\n\t\t\tif (!data || data.source !== this.channelName) return\n\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\tlistener(data.payload as TimestampedEvent)\n\t\t\t}\n\t\t}\n\n\t\twindow.addEventListener('message', this.messageHandler)\n\t}\n\n\t/**\n\t * Post a timestamped event through the bridge.\n\t * No-op if window is not available or the bridge has been destroyed.\n\t */\n\tsend(event: TimestampedEvent): void {\n\t\tif (this.destroyed || typeof window === 'undefined') return\n\n\t\tconst message: BridgeMessage = {\n\t\t\tsource: this.channelName,\n\t\t\tpayload: event,\n\t\t}\n\t\twindow.postMessage(message, '*')\n\t}\n\n\t/**\n\t * Register a callback for events received through the bridge.\n\t * Returns an unsubscribe function.\n\t */\n\tonReceive(callback: (event: TimestampedEvent) => void): () => void {\n\t\tif (this.destroyed) return () => {}\n\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t/**\n\t * Remove all listeners and detach from window.\n\t * After calling destroy, all operations become no-ops.\n\t */\n\tdestroy(): void {\n\t\tif (this.destroyed) return\n\t\tthis.destroyed = true\n\t\tthis.listeners.clear()\n\n\t\tif (this.messageHandler && typeof window !== 'undefined') {\n\t\t\twindow.removeEventListener('message', this.messageHandler)\n\t\t}\n\t}\n}\n","import type { KoraEventType } from '@korajs/core'\nimport type { TimestampedEvent } from '../types'\n\nconst DEFAULT_CAPACITY = 10_000\n\n/**\n * Fixed-capacity ring buffer for storing timestamped events.\n * When the buffer is full, the oldest events are evicted to make room for new ones.\n * This prevents unbounded memory growth when events accumulate fast.\n */\nexport class EventBuffer {\n\tprivate readonly _capacity: number\n\tprivate readonly buffer: Array<TimestampedEvent | undefined>\n\t/** Index where the next event will be written */\n\tprivate head = 0\n\t/** Total number of events ever pushed (used to compute readable range) */\n\tprivate _totalPushed = 0\n\n\tconstructor(capacity: number = DEFAULT_CAPACITY) {\n\t\tif (capacity < 1) {\n\t\t\tthrow new Error('EventBuffer capacity must be at least 1')\n\t\t}\n\t\tthis._capacity = capacity\n\t\tthis.buffer = new Array<TimestampedEvent | undefined>(capacity)\n\t}\n\n\t/** Maximum number of events the buffer can hold */\n\tget capacity(): number {\n\t\treturn this._capacity\n\t}\n\n\t/** Current number of events in the buffer */\n\tget size(): number {\n\t\treturn Math.min(this._totalPushed, this._capacity)\n\t}\n\n\t/**\n\t * Append an event to the buffer.\n\t * If the buffer is at capacity, the oldest event is evicted.\n\t */\n\tpush(event: TimestampedEvent): void {\n\t\tthis.buffer[this.head] = event\n\t\tthis.head = (this.head + 1) % this._capacity\n\t\tthis._totalPushed++\n\t}\n\n\t/**\n\t * Returns all events in insertion order (oldest first).\n\t */\n\tgetAll(): readonly TimestampedEvent[] {\n\t\tif (this._totalPushed === 0) return []\n\n\t\tconst result: TimestampedEvent[] = []\n\n\t\tif (this._totalPushed <= this._capacity) {\n\t\t\t// Buffer hasn't wrapped yet — events are 0..head-1\n\t\t\tfor (let i = 0; i < this.head; i++) {\n\t\t\t\tconst event = this.buffer[i]\n\t\t\t\tif (event) result.push(event)\n\t\t\t}\n\t\t} else {\n\t\t\t// Buffer has wrapped — oldest is at head, newest is at head-1\n\t\t\tfor (let i = 0; i < this._capacity; i++) {\n\t\t\t\tconst index = (this.head + i) % this._capacity\n\t\t\t\tconst event = this.buffer[index]\n\t\t\t\tif (event) result.push(event)\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Returns events whose sequential IDs fall within [start, end] (inclusive).\n\t */\n\tgetRange(start: number, end: number): readonly TimestampedEvent[] {\n\t\treturn this.getAll().filter((e) => e.id >= start && e.id <= end)\n\t}\n\n\t/**\n\t * Returns events matching a specific KoraEventType.\n\t */\n\tgetByType(type: KoraEventType): readonly TimestampedEvent[] {\n\t\treturn this.getAll().filter((e) => e.event.type === type)\n\t}\n\n\t/** Remove all events from the buffer */\n\tclear(): void {\n\t\tthis.buffer.fill(undefined)\n\t\tthis.head = 0\n\t\tthis._totalPushed = 0\n\t}\n}\n","import type { KoraEvent, KoraEventEmitter, KoraEventType } from '@korajs/core'\nimport { MessageBridge } from '../bridge/message-bridge'\nimport { EventBuffer } from '../buffer/event-buffer'\nimport type { DevtoolsConfig, TimestampedEvent } from '../types'\n\nconst DEFAULT_BUFFER_SIZE = 10_000\nconst DEFAULT_CHANNEL_NAME = 'kora-devtools'\n\n/** All event types the instrumenter subscribes to */\nexport const ALL_EVENT_TYPES: readonly KoraEventType[] = [\n\t'operation:created',\n\t'operation:applied',\n\t'merge:started',\n\t'merge:completed',\n\t'merge:conflict',\n\t'constraint:violated',\n\t'sync:connected',\n\t'sync:disconnected',\n\t'sync:schema-mismatch',\n\t'sync:auth-failed',\n\t'sync:suspended',\n\t'sync:apply-failed',\n\t'sync:apply-blocked',\n\t'sync:apply-retrying',\n\t'sync:apply-recovered',\n\t'sync:apply-abandoned',\n\t'sync:sent',\n\t'sync:received',\n\t'sync:acknowledged',\n\t'sync:diagnostics',\n\t'sync:bandwidth',\n\t'sync:initial-sync-progress',\n\t'store:persistence-error',\n\t'store:quota-exceeded',\n\t'store:storage-fallback',\n\t'store:opfs-unavailable',\n\t'store:db-name-collision',\n\t'replay:completed',\n\t'query:subscribed',\n\t'query:invalidated',\n\t'query:executed',\n\t'connection:quality',\n] as const\n\n/**\n * Core orchestrator for Kora DevTools instrumentation.\n *\n * Attaches to a KoraEventEmitter, records all emitted events into a ring buffer\n * with sequential IDs and reception timestamps, and optionally forwards them\n * through a MessageBridge for consumption by a DevTools panel.\n *\n * @example\n * ```typescript\n * const instrumenter = new Instrumenter(app.emitter, { bufferSize: 5000 })\n * const buffer = instrumenter.getBuffer()\n * // ... later\n * instrumenter.destroy()\n * ```\n */\nexport class Instrumenter {\n\tprivate readonly buffer: EventBuffer\n\tprivate readonly bridge: MessageBridge | null\n\tprivate readonly unsubscribers: Array<() => void> = []\n\tprivate nextId = 1\n\tprivate paused = false\n\tprivate destroyed = false\n\n\tconstructor(\n\t\tprivate readonly emitter: KoraEventEmitter,\n\t\tconfig?: DevtoolsConfig,\n\t) {\n\t\tconst bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE\n\t\tconst bridgeEnabled = config?.bridgeEnabled ?? true\n\t\tconst channelName = config?.channelName ?? DEFAULT_CHANNEL_NAME\n\n\t\tthis.buffer = new EventBuffer(bufferSize)\n\t\tthis.bridge = bridgeEnabled ? new MessageBridge(channelName) : null\n\n\t\tthis.attachListeners()\n\t}\n\n\t/** Access the underlying event buffer */\n\tgetBuffer(): EventBuffer {\n\t\treturn this.buffer\n\t}\n\n\t/** Access the message bridge, or null if bridge is disabled */\n\tgetBridge(): MessageBridge | null {\n\t\treturn this.bridge\n\t}\n\n\t/** Temporarily stop recording events. Events emitted while paused are dropped. */\n\tpause(): void {\n\t\tthis.paused = true\n\t}\n\n\t/** Resume recording events after a pause. */\n\tresume(): void {\n\t\tthis.paused = false\n\t}\n\n\t/** Whether the instrumenter is currently paused */\n\tisPaused(): boolean {\n\t\treturn this.paused\n\t}\n\n\t/**\n\t * Detach all listeners from the emitter and destroy the bridge.\n\t * After calling destroy, the instrumenter is inert.\n\t */\n\tdestroy(): void {\n\t\tif (this.destroyed) return\n\t\tthis.destroyed = true\n\n\t\tfor (const unsub of this.unsubscribers) {\n\t\t\tunsub()\n\t\t}\n\t\tthis.unsubscribers.length = 0\n\n\t\tthis.bridge?.destroy()\n\t}\n\n\tprivate attachListeners(): void {\n\t\tfor (const eventType of ALL_EVENT_TYPES) {\n\t\t\tconst unsub = this.emitter.on(eventType, (event: KoraEvent) => {\n\t\t\t\tthis.handleEvent(event)\n\t\t\t})\n\t\t\tthis.unsubscribers.push(unsub)\n\t\t}\n\t}\n\n\tprivate handleEvent(event: KoraEvent): void {\n\t\tif (this.paused || this.destroyed) return\n\n\t\tconst timestamped: TimestampedEvent = {\n\t\t\tid: this.nextId++,\n\t\t\tevent,\n\t\t\treceivedAt: Date.now(),\n\t\t}\n\n\t\tthis.buffer.push(timestamped)\n\t\tthis.bridge?.send(timestamped)\n\t}\n}\n","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":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/bridge/message-bridge.ts","../src/buffer/event-buffer.ts","../src/instrumenter/instrumenter.ts","../src/extension/port-router.ts"],"sourcesContent":["import type { TimestampedEvent } from '../types'\n\nconst DEFAULT_CHANNEL = 'kora-devtools'\n\n/** Shape of messages posted through the bridge */\ninterface BridgeMessage {\n\tsource: string\n\tpayload: TimestampedEvent\n}\n\n/**\n * Communicates between the page context and a DevTools panel via window.postMessage.\n * All messages are namespaced with a `source` field to avoid collisions with other\n * postMessage consumers on the page.\n *\n * Safe to instantiate in non-browser environments (SSR/Node) — all operations\n * become no-ops when `window` is not available.\n */\nexport class MessageBridge {\n\tprivate readonly channelName: string\n\tprivate readonly listeners: Set<(event: TimestampedEvent) => void> = new Set()\n\tprivate readonly messageHandler: ((event: MessageEvent) => void) | null\n\tprivate destroyed = false\n\n\tconstructor(channelName: string = DEFAULT_CHANNEL) {\n\t\tthis.channelName = channelName\n\n\t\tif (typeof window === 'undefined') {\n\t\t\tthis.messageHandler = null\n\t\t\treturn\n\t\t}\n\n\t\t// Single shared handler that dispatches to all registered callbacks\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\tif (this.destroyed) return\n\t\t\tconst data = event.data as Partial<BridgeMessage> | undefined\n\t\t\tif (!data || data.source !== this.channelName) return\n\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\tlistener(data.payload as TimestampedEvent)\n\t\t\t}\n\t\t}\n\n\t\twindow.addEventListener('message', this.messageHandler)\n\t}\n\n\t/**\n\t * Post a timestamped event through the bridge.\n\t * No-op if window is not available or the bridge has been destroyed.\n\t */\n\tsend(event: TimestampedEvent): void {\n\t\tif (this.destroyed || typeof window === 'undefined') return\n\n\t\tconst message: BridgeMessage = {\n\t\t\tsource: this.channelName,\n\t\t\tpayload: event,\n\t\t}\n\t\twindow.postMessage(message, '*')\n\t}\n\n\t/**\n\t * Register a callback for events received through the bridge.\n\t * Returns an unsubscribe function.\n\t */\n\tonReceive(callback: (event: TimestampedEvent) => void): () => void {\n\t\tif (this.destroyed) return () => {}\n\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t/**\n\t * Remove all listeners and detach from window.\n\t * After calling destroy, all operations become no-ops.\n\t */\n\tdestroy(): void {\n\t\tif (this.destroyed) return\n\t\tthis.destroyed = true\n\t\tthis.listeners.clear()\n\n\t\tif (this.messageHandler && typeof window !== 'undefined') {\n\t\t\twindow.removeEventListener('message', this.messageHandler)\n\t\t}\n\t}\n}\n","import type { KoraEventType } from '@korajs/core'\nimport type { TimestampedEvent } from '../types'\n\nconst DEFAULT_CAPACITY = 10_000\n\n/**\n * Fixed-capacity ring buffer for storing timestamped events.\n * When the buffer is full, the oldest events are evicted to make room for new ones.\n * This prevents unbounded memory growth when events accumulate fast.\n */\nexport class EventBuffer {\n\tprivate readonly _capacity: number\n\tprivate readonly buffer: Array<TimestampedEvent | undefined>\n\t/** Index where the next event will be written */\n\tprivate head = 0\n\t/** Total number of events ever pushed (used to compute readable range) */\n\tprivate _totalPushed = 0\n\n\tconstructor(capacity: number = DEFAULT_CAPACITY) {\n\t\tif (capacity < 1) {\n\t\t\tthrow new Error('EventBuffer capacity must be at least 1')\n\t\t}\n\t\tthis._capacity = capacity\n\t\tthis.buffer = new Array<TimestampedEvent | undefined>(capacity)\n\t}\n\n\t/** Maximum number of events the buffer can hold */\n\tget capacity(): number {\n\t\treturn this._capacity\n\t}\n\n\t/** Current number of events in the buffer */\n\tget size(): number {\n\t\treturn Math.min(this._totalPushed, this._capacity)\n\t}\n\n\t/**\n\t * Append an event to the buffer.\n\t * If the buffer is at capacity, the oldest event is evicted.\n\t */\n\tpush(event: TimestampedEvent): void {\n\t\tthis.buffer[this.head] = event\n\t\tthis.head = (this.head + 1) % this._capacity\n\t\tthis._totalPushed++\n\t}\n\n\t/**\n\t * Returns all events in insertion order (oldest first).\n\t */\n\tgetAll(): readonly TimestampedEvent[] {\n\t\tif (this._totalPushed === 0) return []\n\n\t\tconst result: TimestampedEvent[] = []\n\n\t\tif (this._totalPushed <= this._capacity) {\n\t\t\t// Buffer hasn't wrapped yet — events are 0..head-1\n\t\t\tfor (let i = 0; i < this.head; i++) {\n\t\t\t\tconst event = this.buffer[i]\n\t\t\t\tif (event) result.push(event)\n\t\t\t}\n\t\t} else {\n\t\t\t// Buffer has wrapped — oldest is at head, newest is at head-1\n\t\t\tfor (let i = 0; i < this._capacity; i++) {\n\t\t\t\tconst index = (this.head + i) % this._capacity\n\t\t\t\tconst event = this.buffer[index]\n\t\t\t\tif (event) result.push(event)\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Returns events whose sequential IDs fall within [start, end] (inclusive).\n\t */\n\tgetRange(start: number, end: number): readonly TimestampedEvent[] {\n\t\treturn this.getAll().filter((e) => e.id >= start && e.id <= end)\n\t}\n\n\t/**\n\t * Returns events matching a specific KoraEventType.\n\t */\n\tgetByType(type: KoraEventType): readonly TimestampedEvent[] {\n\t\treturn this.getAll().filter((e) => e.event.type === type)\n\t}\n\n\t/** Remove all events from the buffer */\n\tclear(): void {\n\t\tthis.buffer.fill(undefined)\n\t\tthis.head = 0\n\t\tthis._totalPushed = 0\n\t}\n}\n","import type { KoraEvent, KoraEventEmitter, KoraEventType } from '@korajs/core'\nimport { MessageBridge } from '../bridge/message-bridge'\nimport { EventBuffer } from '../buffer/event-buffer'\nimport type { DevtoolsConfig, TimestampedEvent } from '../types'\n\nconst DEFAULT_BUFFER_SIZE = 10_000\nconst DEFAULT_CHANNEL_NAME = 'kora-devtools'\n\n/** All event types the instrumenter subscribes to */\nexport const ALL_EVENT_TYPES: readonly KoraEventType[] = [\n\t'operation:created',\n\t'operation:applied',\n\t'merge:started',\n\t'merge:completed',\n\t'merge:conflict',\n\t'constraint:violated',\n\t'sync:connected',\n\t'sync:disconnected',\n\t'sync:schema-mismatch',\n\t'sync:auth-failed',\n\t'sync:suspended',\n\t'sync:apply-failed',\n\t'sync:apply-blocked',\n\t'sync:apply-retrying',\n\t'sync:apply-recovered',\n\t'sync:apply-abandoned',\n\t'sync:sent',\n\t'sync:received',\n\t'sync:acknowledged',\n\t'sync:diagnostics',\n\t'sync:bandwidth',\n\t'sync:initial-sync-progress',\n\t'sync:durability-degraded',\n\t'sync:durability-restored',\n\t'store:persistence-error',\n\t'store:quota-exceeded',\n\t'store:log-integrity',\n\t'store:rematerialized',\n\t'store:storage-fallback',\n\t'store:opfs-unavailable',\n\t'store:durability-lost',\n\t'store:db-name-collision',\n\t'store:storage-blocked',\n\t'store:storage-migrated',\n\t'store:schema-ahead',\n\t'replay:completed',\n\t'query:subscribed',\n\t'query:invalidated',\n\t'query:executed',\n\t'query:error',\n\t'storage:persistence',\n\t'connection:quality',\n] as const\n\n/**\n * Core orchestrator for Kora DevTools instrumentation.\n *\n * Attaches to a KoraEventEmitter, records all emitted events into a ring buffer\n * with sequential IDs and reception timestamps, and optionally forwards them\n * through a MessageBridge for consumption by a DevTools panel.\n *\n * @example\n * ```typescript\n * const instrumenter = new Instrumenter(app.emitter, { bufferSize: 5000 })\n * const buffer = instrumenter.getBuffer()\n * // ... later\n * instrumenter.destroy()\n * ```\n */\nexport class Instrumenter {\n\tprivate readonly buffer: EventBuffer\n\tprivate readonly bridge: MessageBridge | null\n\tprivate readonly unsubscribers: Array<() => void> = []\n\tprivate nextId = 1\n\tprivate paused = false\n\tprivate destroyed = false\n\n\tconstructor(\n\t\tprivate readonly emitter: KoraEventEmitter,\n\t\tconfig?: DevtoolsConfig,\n\t) {\n\t\tconst bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE\n\t\tconst bridgeEnabled = config?.bridgeEnabled ?? true\n\t\tconst channelName = config?.channelName ?? DEFAULT_CHANNEL_NAME\n\n\t\tthis.buffer = new EventBuffer(bufferSize)\n\t\tthis.bridge = bridgeEnabled ? new MessageBridge(channelName) : null\n\n\t\tthis.attachListeners()\n\t}\n\n\t/** Access the underlying event buffer */\n\tgetBuffer(): EventBuffer {\n\t\treturn this.buffer\n\t}\n\n\t/** Access the message bridge, or null if bridge is disabled */\n\tgetBridge(): MessageBridge | null {\n\t\treturn this.bridge\n\t}\n\n\t/** Temporarily stop recording events. Events emitted while paused are dropped. */\n\tpause(): void {\n\t\tthis.paused = true\n\t}\n\n\t/** Resume recording events after a pause. */\n\tresume(): void {\n\t\tthis.paused = false\n\t}\n\n\t/** Whether the instrumenter is currently paused */\n\tisPaused(): boolean {\n\t\treturn this.paused\n\t}\n\n\t/**\n\t * Detach all listeners from the emitter and destroy the bridge.\n\t * After calling destroy, the instrumenter is inert.\n\t */\n\tdestroy(): void {\n\t\tif (this.destroyed) return\n\t\tthis.destroyed = true\n\n\t\tfor (const unsub of this.unsubscribers) {\n\t\t\tunsub()\n\t\t}\n\t\tthis.unsubscribers.length = 0\n\n\t\tthis.bridge?.destroy()\n\t}\n\n\tprivate attachListeners(): void {\n\t\tfor (const eventType of ALL_EVENT_TYPES) {\n\t\t\tconst unsub = this.emitter.on(eventType, (event: KoraEvent) => {\n\t\t\t\tthis.handleEvent(event)\n\t\t\t})\n\t\t\tthis.unsubscribers.push(unsub)\n\t\t}\n\t}\n\n\tprivate handleEvent(event: KoraEvent): void {\n\t\tif (this.paused || this.destroyed) return\n\n\t\tconst timestamped: TimestampedEvent = {\n\t\t\tid: this.nextId++,\n\t\t\tevent,\n\t\t\treceivedAt: Date.now(),\n\t\t}\n\n\t\tthis.buffer.push(timestamped)\n\t\tthis.bridge?.send(timestamped)\n\t}\n}\n","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":"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","names":[]}
|
package/dist/overlay.cjs
CHANGED
|
@@ -52,8 +52,14 @@ var EVENT_TYPE_CATEGORIES = {
|
|
|
52
52
|
"sync:auth-failed": "sync",
|
|
53
53
|
"sync:suspended": "sync",
|
|
54
54
|
"sync:clock-skew": "sync",
|
|
55
|
+
"encryption:status": "sync",
|
|
56
|
+
"sync:node-id-rotated": "sync",
|
|
57
|
+
"sync:local-node": "sync",
|
|
55
58
|
"sync:clock-rebase": "sync",
|
|
56
59
|
"sync:schema-mismatch": "sync",
|
|
60
|
+
"sync:protocol-deprecated": "sync",
|
|
61
|
+
"sync:unverified-legacy-operation": "sync",
|
|
62
|
+
"sync:forged-duplicate": "sync",
|
|
57
63
|
"sync:apply-failed": "sync",
|
|
58
64
|
"sync:apply-blocked": "sync",
|
|
59
65
|
"sync:apply-retrying": "sync",
|
|
@@ -63,23 +69,34 @@ var EVENT_TYPE_CATEGORIES = {
|
|
|
63
69
|
"sync:sent": "sync",
|
|
64
70
|
"sync:received": "sync",
|
|
65
71
|
"sync:acknowledged": "sync",
|
|
72
|
+
"sync:scope-retracted": "sync",
|
|
66
73
|
"query:subscribed": "query",
|
|
67
74
|
"query:invalidated": "query",
|
|
68
75
|
"query:executed": "query",
|
|
76
|
+
"query:error": "query",
|
|
77
|
+
"storage:persistence": "connection",
|
|
69
78
|
"connection:quality": "connection",
|
|
70
79
|
"sync:diagnostics": "sync",
|
|
71
80
|
"sync:bandwidth": "sync",
|
|
72
81
|
"sync:initial-sync-progress": "sync",
|
|
73
82
|
"sync:delivery-gap": "sync",
|
|
74
83
|
"sync:delivery-stalled": "sync",
|
|
84
|
+
"sync:durability-degraded": "connection",
|
|
85
|
+
"sync:durability-restored": "connection",
|
|
75
86
|
"awareness:updated": "sync",
|
|
76
87
|
"state-machine:transition": "operation",
|
|
77
88
|
"state-machine:rejected": "operation",
|
|
78
89
|
"store:persistence-error": "connection",
|
|
79
90
|
"store:quota-exceeded": "connection",
|
|
91
|
+
"store:log-integrity": "operation",
|
|
92
|
+
"store:rematerialized": "operation",
|
|
80
93
|
"store:storage-fallback": "connection",
|
|
81
94
|
"store:opfs-unavailable": "connection",
|
|
95
|
+
"store:durability-lost": "connection",
|
|
82
96
|
"store:db-name-collision": "connection",
|
|
97
|
+
"store:storage-blocked": "connection",
|
|
98
|
+
"store:storage-migrated": "connection",
|
|
99
|
+
"store:schema-ahead": "connection",
|
|
83
100
|
"replay:completed": "operation"
|
|
84
101
|
};
|
|
85
102
|
function eventTypeToCategory(type) {
|
|
@@ -347,12 +364,24 @@ function timelineLabel(event) {
|
|
|
347
364
|
return "sync auth failed";
|
|
348
365
|
case "sync:suspended":
|
|
349
366
|
return `sync suspended (${event.reason})`;
|
|
367
|
+
case "encryption:status":
|
|
368
|
+
return `encryption ${event.status.state}${event.status.code ? ` (${event.status.code})` : ""}`;
|
|
350
369
|
case "sync:clock-skew":
|
|
351
370
|
return `clock skew ${Math.round(event.skewMs / 1e3)}s (${event.severity})`;
|
|
371
|
+
case "sync:node-id-rotated":
|
|
372
|
+
return `node id rotated (${event.reenqueuedCount} ops re-queued)`;
|
|
373
|
+
case "sync:local-node":
|
|
374
|
+
return `local node ${event.nodeId} ${event.action}`;
|
|
352
375
|
case "sync:clock-rebase":
|
|
353
376
|
return `clock rebase ${event.rebasedCount} ops (${Math.round(event.maxSkewMs / 1e3)}s ahead)`;
|
|
354
377
|
case "sync:schema-mismatch":
|
|
355
378
|
return `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`;
|
|
379
|
+
case "sync:protocol-deprecated":
|
|
380
|
+
return `protocol v${event.clientProtocolVersion} deprecated (node ${event.nodeId})`;
|
|
381
|
+
case "sync:unverified-legacy-operation":
|
|
382
|
+
return `unverified legacy op ${event.operationId} (node ${event.nodeId})`;
|
|
383
|
+
case "sync:forged-duplicate":
|
|
384
|
+
return `forged duplicate refused ${event.operationId} (node ${event.nodeId})`;
|
|
356
385
|
case "sync:apply-failed":
|
|
357
386
|
return `sync apply failed ${event.operationId}`;
|
|
358
387
|
case "sync:apply-blocked":
|
|
@@ -371,12 +400,18 @@ function timelineLabel(event) {
|
|
|
371
400
|
return `sync received ${event.batchSize}`;
|
|
372
401
|
case "sync:acknowledged":
|
|
373
402
|
return `sync ack ${event.sequenceNumber}`;
|
|
403
|
+
case "sync:scope-retracted":
|
|
404
|
+
return `scope retracted ${event.collection}/${event.recordId}`;
|
|
374
405
|
case "query:subscribed":
|
|
375
406
|
return `query subscribed ${event.collection}`;
|
|
376
407
|
case "query:invalidated":
|
|
377
408
|
return `query invalidated ${event.queryId}`;
|
|
378
409
|
case "query:executed":
|
|
379
410
|
return `query executed ${event.queryId}`;
|
|
411
|
+
case "query:error":
|
|
412
|
+
return `query error ${event.collection} (${event.phase}): ${event.message}`;
|
|
413
|
+
case "storage:persistence":
|
|
414
|
+
return `storage persistence ${event.state}: ${event.persisted ? "persisted" : "best-effort"}`;
|
|
380
415
|
case "connection:quality":
|
|
381
416
|
return `connection ${event.quality}`;
|
|
382
417
|
case "sync:diagnostics":
|
|
@@ -388,7 +423,11 @@ function timelineLabel(event) {
|
|
|
388
423
|
case "sync:delivery-gap":
|
|
389
424
|
return `delivery gap expected ${event.expectedBase} received ${event.receivedBase}`;
|
|
390
425
|
case "sync:delivery-stalled":
|
|
391
|
-
return `delivery
|
|
426
|
+
return `delivery unacknowledged ${event.watermark} \u2192 ${event.outstandingMaxDeliverySequence} (${event.repeatCount} repeats)`;
|
|
427
|
+
case "sync:durability-degraded":
|
|
428
|
+
return `local durability degraded after ${event.failedAttempts} failed barriers: ${event.message}`;
|
|
429
|
+
case "sync:durability-restored":
|
|
430
|
+
return "local durability restored";
|
|
392
431
|
case "awareness:updated":
|
|
393
432
|
return `awareness ${event.states.size} peers`;
|
|
394
433
|
case "state-machine:transition":
|
|
@@ -399,12 +438,24 @@ function timelineLabel(event) {
|
|
|
399
438
|
return `store persistence error ${event.message}`;
|
|
400
439
|
case "store:quota-exceeded":
|
|
401
440
|
return "store quota exceeded";
|
|
441
|
+
case "store:log-integrity":
|
|
442
|
+
return `log integrity: ${event.repaired} repaired, ${event.quarantined} quarantined`;
|
|
443
|
+
case "store:rematerialized":
|
|
444
|
+
return `re-materialized ${event.records} records (${event.mode}), ${event.changedRows} changed`;
|
|
402
445
|
case "store:storage-fallback":
|
|
403
446
|
return `store fallback ${event.from} \u2192 ${event.to}`;
|
|
404
447
|
case "store:opfs-unavailable":
|
|
405
448
|
return `store OPFS unavailable (${event.reason}), running in memory`;
|
|
449
|
+
case "store:durability-lost":
|
|
450
|
+
return `store durability lost (${event.reason} during ${event.phase}), writes refused`;
|
|
406
451
|
case "store:db-name-collision":
|
|
407
452
|
return `store db-name collision ${event.dbName}`;
|
|
453
|
+
case "store:storage-blocked":
|
|
454
|
+
return `store ${event.resource} ${event.state === "waiting" ? "blocked by another holder" : "unblocked"}`;
|
|
455
|
+
case "store:storage-migrated":
|
|
456
|
+
return `store migrated ${event.from} \u2192 ${event.to}`;
|
|
457
|
+
case "store:schema-ahead":
|
|
458
|
+
return `store refused: database at schema v${event.storedVersion}, code at v${event.codeVersion}`;
|
|
408
459
|
case "replay:completed":
|
|
409
460
|
return `replay ${event.operationsApplied} ops`;
|
|
410
461
|
}
|