specnav-core 0.2.0 → 0.2.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/CHANGELOG.md +6 -0
- package/dist/adaptive.js +4 -1
- package/dist/adaptive.js.map +1 -1
- package/dist/{adaptive.cjs → adaptive.mjs} +3 -6
- package/dist/adaptive.mjs.map +1 -0
- package/dist/cache.js +4 -1
- package/dist/cache.js.map +1 -1
- package/dist/{cache.cjs → cache.mjs} +3 -6
- package/dist/cache.mjs.map +1 -0
- package/dist/graph.js +5 -1
- package/dist/graph.js.map +1 -1
- package/dist/{graph.cjs → graph.mjs} +3 -7
- package/dist/graph.mjs.map +1 -0
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/{index.cjs → index.mjs} +3 -20
- package/dist/index.mjs.map +1 -0
- package/dist/morpher.js +5 -1
- package/dist/morpher.js.map +1 -1
- package/dist/{morpher.cjs → morpher.mjs} +3 -7
- package/dist/morpher.mjs.map +1 -0
- package/dist/performance.js +4 -1
- package/dist/performance.js.map +1 -1
- package/dist/{performance.cjs → performance.mjs} +3 -6
- package/dist/performance.mjs.map +1 -0
- package/dist/speculator.js +4 -1
- package/dist/speculator.js.map +1 -1
- package/dist/{speculator.cjs → speculator.mjs} +3 -6
- package/dist/speculator.mjs.map +1 -0
- package/dist/trajectory.js +4 -1
- package/dist/trajectory.js.map +1 -1
- package/dist/{trajectory.cjs → trajectory.mjs} +3 -6
- package/dist/trajectory.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/adaptive.cjs.map +0 -1
- package/dist/cache.cjs.map +0 -1
- package/dist/graph.cjs.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/morpher.cjs.map +0 -1
- package/dist/performance.cjs.map +0 -1
- package/dist/speculator.cjs.map +0 -1
- package/dist/trajectory.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
package/dist/adaptive.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
// src/adaptive.ts
|
|
2
4
|
var DEFAULT_CONFIG = {
|
|
3
5
|
batteryThreshold: 0.2,
|
|
@@ -100,6 +102,7 @@ function createAdaptiveMode(config) {
|
|
|
100
102
|
return new AdaptiveMode(config);
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
exports.AdaptiveMode = AdaptiveMode;
|
|
106
|
+
exports.createAdaptiveMode = createAdaptiveMode;
|
|
104
107
|
//# sourceMappingURL=adaptive.js.map
|
|
105
108
|
//# sourceMappingURL=adaptive.js.map
|
package/dist/adaptive.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adaptive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/adaptive.ts"],"names":[],"mappings":"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","file":"adaptive.js","sourcesContent":["import type { AdaptiveConfig, Strategy } from \"./types\";\n\nconst DEFAULT_CONFIG: AdaptiveConfig = {\n batteryThreshold: 0.2,\n connectionTypes: {\n slow: [\"2g\", \"3g\", \"slow-2g\"],\n fast: [\"4g\"],\n },\n respectSaveData: true,\n respectReducedMotion: true,\n};\n\nexport class AdaptiveMode {\n private config: AdaptiveConfig;\n private battery: any = null;\n private connection: any = null;\n private initPromise: Promise<void>;\n\n constructor(config: Partial<AdaptiveConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.initPromise = this.initialize();\n }\n\n async waitForInit(): Promise<void> {\n await this.initPromise;\n }\n\n private async initialize(): Promise<void> {\n if (typeof navigator === \"undefined\") return;\n\n // Battery API\n if (\"getBattery\" in navigator) {\n try {\n this.battery = await (navigator as any).getBattery();\n } catch {\n // Not available\n }\n }\n\n // Network Information API\n if (\"connection\" in navigator || \"mozConnection\" in navigator || \"webkitConnection\" in navigator) {\n this.connection =\n (navigator as any).connection ||\n (navigator as any).mozConnection ||\n (navigator as any).webkitConnection;\n }\n }\n\n getStrategy(baseStrategy: Strategy): Strategy {\n if (baseStrategy === \"off\") return \"off\";\n if (baseStrategy !== \"auto\") return baseStrategy;\n\n // Check Save-Data\n if (this.config.respectSaveData && this.hasSaveData()) {\n return \"off\";\n }\n\n // Check battery\n if (this.isLowBattery()) {\n return \"conservative\";\n }\n\n // Check connection\n if (this.isSlowConnection()) {\n return \"conservative\";\n }\n\n return \"aggressive\";\n }\n\n shouldPrefetch(): boolean {\n if (this.config.respectSaveData && this.hasSaveData()) {\n return false;\n }\n\n if (this.isLowBattery()) {\n return false;\n }\n\n return true;\n }\n\n shouldSpeculate(): boolean {\n if (!this.shouldPrefetch()) {\n return false;\n }\n\n if (this.isSlowConnection()) {\n return false;\n }\n\n return true;\n }\n\n shouldUseTransitions(): boolean {\n if (!this.config.respectReducedMotion) return true;\n\n if (typeof window === \"undefined\") return true;\n\n return !window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n }\n\n private hasSaveData(): boolean {\n if (typeof navigator === \"undefined\") return false;\n\n return (\n (navigator as any).connection?.saveData === true ||\n (navigator as any).mozConnection?.saveData === true ||\n (navigator as any).webkitConnection?.saveData === true\n );\n }\n\n private isLowBattery(): boolean {\n if (!this.battery) return false;\n\n return (\n !this.battery.charging &&\n this.battery.level < this.config.batteryThreshold\n );\n }\n\n private isSlowConnection(): boolean {\n if (!this.connection) return false;\n\n const effectiveType = this.connection.effectiveType;\n if (!effectiveType) return false;\n\n return this.config.connectionTypes.slow.includes(effectiveType);\n }\n\n isFastConnection(): boolean {\n if (!this.connection) return true; // Assume fast if unknown\n\n const effectiveType = this.connection.effectiveType;\n if (!effectiveType) return true;\n\n return this.config.connectionTypes.fast.includes(effectiveType);\n }\n\n getBatteryLevel(): number | null {\n return this.battery?.level ?? null;\n }\n\n getConnectionType(): string | null {\n return this.connection?.effectiveType ?? null;\n }\n}\n\nexport function createAdaptiveMode(\n config?: Partial<AdaptiveConfig>\n): AdaptiveMode {\n return new AdaptiveMode(config);\n}\n"]}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// src/adaptive.ts
|
|
4
2
|
var DEFAULT_CONFIG = {
|
|
5
3
|
batteryThreshold: 0.2,
|
|
@@ -102,7 +100,6 @@ function createAdaptiveMode(config) {
|
|
|
102
100
|
return new AdaptiveMode(config);
|
|
103
101
|
}
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//# sourceMappingURL=adaptive.
|
|
108
|
-
//# sourceMappingURL=adaptive.cjs.map
|
|
103
|
+
export { AdaptiveMode, createAdaptiveMode };
|
|
104
|
+
//# sourceMappingURL=adaptive.mjs.map
|
|
105
|
+
//# sourceMappingURL=adaptive.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adaptive.ts"],"names":[],"mappings":"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","file":"adaptive.mjs","sourcesContent":["import type { AdaptiveConfig, Strategy } from \"./types\";\n\nconst DEFAULT_CONFIG: AdaptiveConfig = {\n batteryThreshold: 0.2,\n connectionTypes: {\n slow: [\"2g\", \"3g\", \"slow-2g\"],\n fast: [\"4g\"],\n },\n respectSaveData: true,\n respectReducedMotion: true,\n};\n\nexport class AdaptiveMode {\n private config: AdaptiveConfig;\n private battery: any = null;\n private connection: any = null;\n private initPromise: Promise<void>;\n\n constructor(config: Partial<AdaptiveConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.initPromise = this.initialize();\n }\n\n async waitForInit(): Promise<void> {\n await this.initPromise;\n }\n\n private async initialize(): Promise<void> {\n if (typeof navigator === \"undefined\") return;\n\n // Battery API\n if (\"getBattery\" in navigator) {\n try {\n this.battery = await (navigator as any).getBattery();\n } catch {\n // Not available\n }\n }\n\n // Network Information API\n if (\"connection\" in navigator || \"mozConnection\" in navigator || \"webkitConnection\" in navigator) {\n this.connection =\n (navigator as any).connection ||\n (navigator as any).mozConnection ||\n (navigator as any).webkitConnection;\n }\n }\n\n getStrategy(baseStrategy: Strategy): Strategy {\n if (baseStrategy === \"off\") return \"off\";\n if (baseStrategy !== \"auto\") return baseStrategy;\n\n // Check Save-Data\n if (this.config.respectSaveData && this.hasSaveData()) {\n return \"off\";\n }\n\n // Check battery\n if (this.isLowBattery()) {\n return \"conservative\";\n }\n\n // Check connection\n if (this.isSlowConnection()) {\n return \"conservative\";\n }\n\n return \"aggressive\";\n }\n\n shouldPrefetch(): boolean {\n if (this.config.respectSaveData && this.hasSaveData()) {\n return false;\n }\n\n if (this.isLowBattery()) {\n return false;\n }\n\n return true;\n }\n\n shouldSpeculate(): boolean {\n if (!this.shouldPrefetch()) {\n return false;\n }\n\n if (this.isSlowConnection()) {\n return false;\n }\n\n return true;\n }\n\n shouldUseTransitions(): boolean {\n if (!this.config.respectReducedMotion) return true;\n\n if (typeof window === \"undefined\") return true;\n\n return !window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n }\n\n private hasSaveData(): boolean {\n if (typeof navigator === \"undefined\") return false;\n\n return (\n (navigator as any).connection?.saveData === true ||\n (navigator as any).mozConnection?.saveData === true ||\n (navigator as any).webkitConnection?.saveData === true\n );\n }\n\n private isLowBattery(): boolean {\n if (!this.battery) return false;\n\n return (\n !this.battery.charging &&\n this.battery.level < this.config.batteryThreshold\n );\n }\n\n private isSlowConnection(): boolean {\n if (!this.connection) return false;\n\n const effectiveType = this.connection.effectiveType;\n if (!effectiveType) return false;\n\n return this.config.connectionTypes.slow.includes(effectiveType);\n }\n\n isFastConnection(): boolean {\n if (!this.connection) return true; // Assume fast if unknown\n\n const effectiveType = this.connection.effectiveType;\n if (!effectiveType) return true;\n\n return this.config.connectionTypes.fast.includes(effectiveType);\n }\n\n getBatteryLevel(): number | null {\n return this.battery?.level ?? null;\n }\n\n getConnectionType(): string | null {\n return this.connection?.effectiveType ?? null;\n }\n}\n\nexport function createAdaptiveMode(\n config?: Partial<AdaptiveConfig>\n): AdaptiveMode {\n return new AdaptiveMode(config);\n}\n"]}
|
package/dist/cache.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
// src/cache.ts
|
|
2
4
|
var DEFAULT_CONFIG = {
|
|
3
5
|
memory: {
|
|
@@ -216,6 +218,7 @@ function createCacheManager(config, callbacks) {
|
|
|
216
218
|
return new CacheManager(config, callbacks);
|
|
217
219
|
}
|
|
218
220
|
|
|
219
|
-
|
|
221
|
+
exports.CacheManager = CacheManager;
|
|
222
|
+
exports.createCacheManager = createCacheManager;
|
|
220
223
|
//# sourceMappingURL=cache.js.map
|
|
221
224
|
//# sourceMappingURL=cache.js.map
|
package/dist/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.ts"],"names":[],"mappings":"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","file":"cache.js","sourcesContent":["import type { CacheConfig, CachedPage, CacheLayer } from \"./types\";\n\nconst DEFAULT_CONFIG: CacheConfig = {\n memory: {\n enabled: true,\n maxPages: 10,\n },\n serviceWorker: {\n enabled: true,\n ttl: 300,\n cacheName: \"specnav-v1\",\n crossTabSync: true,\n broadcastChannel: \"specnav-sync\",\n },\n edge: {\n enabled: true,\n maxAge: 60,\n staleWhileRevalidate: 300,\n },\n exclude: [],\n};\n\nexport class CacheManager {\n private config: CacheConfig;\n private memoryCache: Map<string, CachedPage> = new Map();\n private accessOrder: string[] = [];\n private broadcastChannel?: BroadcastChannel;\n private onCacheHit?: (href: string, layer: CacheLayer) => void;\n\n constructor(\n config: Partial<CacheConfig> = {},\n callbacks?: { onCacheHit?: (href: string, layer: CacheLayer) => void }\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.onCacheHit = callbacks?.onCacheHit;\n\n if (\n this.config.serviceWorker.enabled &&\n this.config.serviceWorker.crossTabSync &&\n typeof BroadcastChannel !== \"undefined\"\n ) {\n this.broadcastChannel = new BroadcastChannel(\n this.config.serviceWorker.broadcastChannel\n );\n this.broadcastChannel.onmessage = this.handleBroadcast;\n }\n }\n\n async get(href: string): Promise<string | null> {\n if (this.isExcluded(href)) return null;\n\n // L1: Memory cache\n const memoryHit = this.getFromMemory(href);\n if (memoryHit) {\n this.onCacheHit?.(href, 1);\n return memoryHit;\n }\n\n // L2: Service Worker cache\n if (this.config.serviceWorker.enabled) {\n const swHit = await this.getFromServiceWorker(href);\n if (swHit) {\n this.onCacheHit?.(href, 2);\n this.setInMemory(href, swHit);\n return swHit;\n }\n }\n\n return null;\n }\n\n async set(href: string, html: string): Promise<void> {\n if (this.isExcluded(href)) return;\n\n // Sanitize href to prevent cache poisoning\n const sanitizedHref = this.sanitizeHref(href);\n if (!sanitizedHref) return;\n\n // Validate HTML doesn't contain dangerous inline scripts\n if (this.containsDangerousContent(html)) {\n console.warn(`Refusing to cache potentially dangerous content from ${sanitizedHref}`);\n return;\n }\n\n const page: CachedPage = {\n href: sanitizedHref,\n html,\n timestamp: Date.now(),\n ttl: this.config.serviceWorker.ttl,\n };\n\n // L1: Memory\n if (this.config.memory.enabled) {\n this.setInMemory(sanitizedHref, html);\n }\n\n // L2: Service Worker\n if (this.config.serviceWorker.enabled) {\n await this.setInServiceWorker(page);\n this.broadcastSet(sanitizedHref);\n }\n }\n\n private containsDangerousContent(html: string): boolean {\n // Check for inline event handlers and javascript: URLs\n const dangerousPatterns = [\n /on\\w+\\s*=\\s*[\"'][^\"']*[\"']/i, // Inline event handlers\n /javascript:/i, // javascript: URLs\n ];\n\n // Check for inline scripts, but exclude safe types\n const scriptRegex = /<script([^>]*)>([\\s\\S]*?)<\\/script>/gi;\n let match;\n while ((match = scriptRegex.exec(html)) !== null) {\n const attrs = match[1] || \"\";\n \n // Allow scripts with src attribute\n if (/src\\s*=/.test(attrs)) continue;\n \n // Allow safe script types (JSON data, templates)\n if (/type\\s*=\\s*[\"']?(application\\/json|application\\/ld\\+json|text\\/template)[\"']?/i.test(attrs)) continue;\n \n // Allow Next.js data script\n if (/id\\s*=\\s*[\"']__NEXT_DATA__[\"']/i.test(attrs)) continue;\n \n // If we get here, it's an inline script without safe type\n return true;\n }\n\n return dangerousPatterns.some(pattern => pattern.test(html));\n }\n\n private sanitizeHref(href: string): string | null {\n try {\n const url = new URL(href, window.location.origin);\n // Only allow same-origin URLs\n if (url.origin !== window.location.origin) return null;\n return url.pathname + url.search + url.hash;\n } catch {\n return null;\n }\n }\n\n clear(href?: string): void {\n if (href) {\n this.memoryCache.delete(href);\n this.accessOrder = this.accessOrder.filter((h) => h !== href);\n this.clearFromServiceWorker(href);\n } else {\n this.memoryCache.clear();\n this.accessOrder = [];\n this.clearAllServiceWorker();\n }\n }\n\n getSize(): number {\n return this.memoryCache.size;\n }\n\n destroy(): void {\n this.memoryCache.clear();\n this.accessOrder = [];\n this.broadcastChannel?.close();\n }\n\n private getFromMemory(href: string): string | null {\n const page = this.memoryCache.get(href);\n if (!page) return null;\n\n // Update LRU order\n this.accessOrder = this.accessOrder.filter((h) => h !== href);\n this.accessOrder.push(href);\n\n return page.html;\n }\n\n private setInMemory(href: string, html: string): void {\n // Prevent memory exhaustion - limit individual page size\n const maxPageSize = 5 * 1024 * 1024; // 5MB per page\n if (html.length > maxPageSize) {\n console.warn(`Page ${href} exceeds max size, not caching`);\n return;\n }\n\n this.memoryCache.set(href, {\n href,\n html,\n timestamp: Date.now(),\n ttl: this.config.serviceWorker.ttl,\n });\n\n // Deduplicate before adding to accessOrder\n const existingIndex = this.accessOrder.indexOf(href);\n if (existingIndex !== -1) {\n this.accessOrder.splice(existingIndex, 1);\n }\n this.accessOrder.push(href);\n\n // LRU eviction\n if (this.memoryCache.size > this.config.memory.maxPages) {\n const oldest = this.accessOrder.shift();\n if (oldest) this.memoryCache.delete(oldest);\n }\n }\n\n private async getFromServiceWorker(href: string): Promise<string | null> {\n if (typeof caches === \"undefined\") return null;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n const response = await cache.match(href);\n\n if (!response) return null;\n\n const data = await response.json();\n const page = data as CachedPage;\n\n // Check TTL\n const age = Date.now() - page.timestamp;\n if (age > page.ttl * 1000) {\n await cache.delete(href);\n return null;\n }\n\n return page.html;\n } catch {\n return null;\n }\n }\n\n private async setInServiceWorker(page: CachedPage): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n const response = new Response(JSON.stringify(page), {\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": `max-age=${this.config.edge.maxAge}, stale-while-revalidate=${this.config.edge.staleWhileRevalidate}`,\n },\n });\n await cache.put(page.href, response);\n } catch {\n // Silently fail\n }\n }\n\n private async clearFromServiceWorker(href: string): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n await cache.delete(href);\n } catch {\n // Silently fail\n }\n }\n\n private async clearAllServiceWorker(): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n await caches.delete(this.config.serviceWorker.cacheName);\n } catch {\n // Silently fail\n }\n }\n\n private isExcluded(href: string): boolean {\n return this.config.exclude.some((pattern) => {\n if (typeof pattern === \"string\") {\n return pattern.includes(\"*\")\n ? new RegExp(pattern.replace(/\\*/g, \".*\")).test(href)\n : href === pattern;\n }\n return pattern.test(href);\n });\n }\n\n private broadcastSet(href: string): void {\n this.broadcastChannel?.postMessage({ type: \"set\", href });\n }\n\n private handleBroadcast = (event: MessageEvent): void => {\n if (event.data.type === \"set\") {\n // Invalidate memory cache to force fetch from SW\n this.memoryCache.delete(event.data.href);\n }\n };\n}\n\nexport function createCacheManager(\n config?: Partial<CacheConfig>,\n callbacks?: { onCacheHit?: (href: string, layer: CacheLayer) => void }\n): CacheManager {\n return new CacheManager(config, callbacks);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts"],"names":[],"mappings":"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","file":"cache.js","sourcesContent":["import type { CacheConfig, CachedPage, CacheLayer } from \"./types\";\n\nconst DEFAULT_CONFIG: CacheConfig = {\n memory: {\n enabled: true,\n maxPages: 10,\n },\n serviceWorker: {\n enabled: true,\n ttl: 300,\n cacheName: \"specnav-v1\",\n crossTabSync: true,\n broadcastChannel: \"specnav-sync\",\n },\n edge: {\n enabled: true,\n maxAge: 60,\n staleWhileRevalidate: 300,\n },\n exclude: [],\n};\n\nexport class CacheManager {\n private config: CacheConfig;\n private memoryCache: Map<string, CachedPage> = new Map();\n private accessOrder: string[] = [];\n private broadcastChannel?: BroadcastChannel;\n private onCacheHit?: (href: string, layer: CacheLayer) => void;\n\n constructor(\n config: Partial<CacheConfig> = {},\n callbacks?: { onCacheHit?: (href: string, layer: CacheLayer) => void }\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.onCacheHit = callbacks?.onCacheHit;\n\n if (\n this.config.serviceWorker.enabled &&\n this.config.serviceWorker.crossTabSync &&\n typeof BroadcastChannel !== \"undefined\"\n ) {\n this.broadcastChannel = new BroadcastChannel(\n this.config.serviceWorker.broadcastChannel\n );\n this.broadcastChannel.onmessage = this.handleBroadcast;\n }\n }\n\n async get(href: string): Promise<string | null> {\n if (this.isExcluded(href)) return null;\n\n // L1: Memory cache\n const memoryHit = this.getFromMemory(href);\n if (memoryHit) {\n this.onCacheHit?.(href, 1);\n return memoryHit;\n }\n\n // L2: Service Worker cache\n if (this.config.serviceWorker.enabled) {\n const swHit = await this.getFromServiceWorker(href);\n if (swHit) {\n this.onCacheHit?.(href, 2);\n this.setInMemory(href, swHit);\n return swHit;\n }\n }\n\n return null;\n }\n\n async set(href: string, html: string): Promise<void> {\n if (this.isExcluded(href)) return;\n\n // Sanitize href to prevent cache poisoning\n const sanitizedHref = this.sanitizeHref(href);\n if (!sanitizedHref) return;\n\n // Validate HTML doesn't contain dangerous inline scripts\n if (this.containsDangerousContent(html)) {\n console.warn(`Refusing to cache potentially dangerous content from ${sanitizedHref}`);\n return;\n }\n\n const page: CachedPage = {\n href: sanitizedHref,\n html,\n timestamp: Date.now(),\n ttl: this.config.serviceWorker.ttl,\n };\n\n // L1: Memory\n if (this.config.memory.enabled) {\n this.setInMemory(sanitizedHref, html);\n }\n\n // L2: Service Worker\n if (this.config.serviceWorker.enabled) {\n await this.setInServiceWorker(page);\n this.broadcastSet(sanitizedHref);\n }\n }\n\n private containsDangerousContent(html: string): boolean {\n // Check for inline event handlers and javascript: URLs\n const dangerousPatterns = [\n /on\\w+\\s*=\\s*[\"'][^\"']*[\"']/i, // Inline event handlers\n /javascript:/i, // javascript: URLs\n ];\n\n // Check for inline scripts, but exclude safe types\n const scriptRegex = /<script([^>]*)>([\\s\\S]*?)<\\/script>/gi;\n let match;\n while ((match = scriptRegex.exec(html)) !== null) {\n const attrs = match[1] || \"\";\n \n // Allow scripts with src attribute\n if (/src\\s*=/.test(attrs)) continue;\n \n // Allow safe script types (JSON data, templates)\n if (/type\\s*=\\s*[\"']?(application\\/json|application\\/ld\\+json|text\\/template)[\"']?/i.test(attrs)) continue;\n \n // Allow Next.js data script\n if (/id\\s*=\\s*[\"']__NEXT_DATA__[\"']/i.test(attrs)) continue;\n \n // If we get here, it's an inline script without safe type\n return true;\n }\n\n return dangerousPatterns.some(pattern => pattern.test(html));\n }\n\n private sanitizeHref(href: string): string | null {\n try {\n const url = new URL(href, window.location.origin);\n // Only allow same-origin URLs\n if (url.origin !== window.location.origin) return null;\n return url.pathname + url.search + url.hash;\n } catch {\n return null;\n }\n }\n\n clear(href?: string): void {\n if (href) {\n this.memoryCache.delete(href);\n this.accessOrder = this.accessOrder.filter((h) => h !== href);\n this.clearFromServiceWorker(href);\n } else {\n this.memoryCache.clear();\n this.accessOrder = [];\n this.clearAllServiceWorker();\n }\n }\n\n getSize(): number {\n return this.memoryCache.size;\n }\n\n destroy(): void {\n this.memoryCache.clear();\n this.accessOrder = [];\n this.broadcastChannel?.close();\n }\n\n private getFromMemory(href: string): string | null {\n const page = this.memoryCache.get(href);\n if (!page) return null;\n\n // Update LRU order\n this.accessOrder = this.accessOrder.filter((h) => h !== href);\n this.accessOrder.push(href);\n\n return page.html;\n }\n\n private setInMemory(href: string, html: string): void {\n // Prevent memory exhaustion - limit individual page size\n const maxPageSize = 5 * 1024 * 1024; // 5MB per page\n if (html.length > maxPageSize) {\n console.warn(`Page ${href} exceeds max size, not caching`);\n return;\n }\n\n this.memoryCache.set(href, {\n href,\n html,\n timestamp: Date.now(),\n ttl: this.config.serviceWorker.ttl,\n });\n\n // Deduplicate before adding to accessOrder\n const existingIndex = this.accessOrder.indexOf(href);\n if (existingIndex !== -1) {\n this.accessOrder.splice(existingIndex, 1);\n }\n this.accessOrder.push(href);\n\n // LRU eviction\n if (this.memoryCache.size > this.config.memory.maxPages) {\n const oldest = this.accessOrder.shift();\n if (oldest) this.memoryCache.delete(oldest);\n }\n }\n\n private async getFromServiceWorker(href: string): Promise<string | null> {\n if (typeof caches === \"undefined\") return null;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n const response = await cache.match(href);\n\n if (!response) return null;\n\n const data = await response.json();\n const page = data as CachedPage;\n\n // Check TTL\n const age = Date.now() - page.timestamp;\n if (age > page.ttl * 1000) {\n await cache.delete(href);\n return null;\n }\n\n return page.html;\n } catch {\n return null;\n }\n }\n\n private async setInServiceWorker(page: CachedPage): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n const response = new Response(JSON.stringify(page), {\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": `max-age=${this.config.edge.maxAge}, stale-while-revalidate=${this.config.edge.staleWhileRevalidate}`,\n },\n });\n await cache.put(page.href, response);\n } catch {\n // Silently fail\n }\n }\n\n private async clearFromServiceWorker(href: string): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n await cache.delete(href);\n } catch {\n // Silently fail\n }\n }\n\n private async clearAllServiceWorker(): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n await caches.delete(this.config.serviceWorker.cacheName);\n } catch {\n // Silently fail\n }\n }\n\n private isExcluded(href: string): boolean {\n return this.config.exclude.some((pattern) => {\n if (typeof pattern === \"string\") {\n return pattern.includes(\"*\")\n ? new RegExp(pattern.replace(/\\*/g, \".*\")).test(href)\n : href === pattern;\n }\n return pattern.test(href);\n });\n }\n\n private broadcastSet(href: string): void {\n this.broadcastChannel?.postMessage({ type: \"set\", href });\n }\n\n private handleBroadcast = (event: MessageEvent): void => {\n if (event.data.type === \"set\") {\n // Invalidate memory cache to force fetch from SW\n this.memoryCache.delete(event.data.href);\n }\n };\n}\n\nexport function createCacheManager(\n config?: Partial<CacheConfig>,\n callbacks?: { onCacheHit?: (href: string, layer: CacheLayer) => void }\n): CacheManager {\n return new CacheManager(config, callbacks);\n}\n"]}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// src/cache.ts
|
|
4
2
|
var DEFAULT_CONFIG = {
|
|
5
3
|
memory: {
|
|
@@ -218,7 +216,6 @@ function createCacheManager(config, callbacks) {
|
|
|
218
216
|
return new CacheManager(config, callbacks);
|
|
219
217
|
}
|
|
220
218
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//# sourceMappingURL=cache.
|
|
224
|
-
//# sourceMappingURL=cache.cjs.map
|
|
219
|
+
export { CacheManager, createCacheManager };
|
|
220
|
+
//# sourceMappingURL=cache.mjs.map
|
|
221
|
+
//# sourceMappingURL=cache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts"],"names":[],"mappings":"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","file":"cache.mjs","sourcesContent":["import type { CacheConfig, CachedPage, CacheLayer } from \"./types\";\n\nconst DEFAULT_CONFIG: CacheConfig = {\n memory: {\n enabled: true,\n maxPages: 10,\n },\n serviceWorker: {\n enabled: true,\n ttl: 300,\n cacheName: \"specnav-v1\",\n crossTabSync: true,\n broadcastChannel: \"specnav-sync\",\n },\n edge: {\n enabled: true,\n maxAge: 60,\n staleWhileRevalidate: 300,\n },\n exclude: [],\n};\n\nexport class CacheManager {\n private config: CacheConfig;\n private memoryCache: Map<string, CachedPage> = new Map();\n private accessOrder: string[] = [];\n private broadcastChannel?: BroadcastChannel;\n private onCacheHit?: (href: string, layer: CacheLayer) => void;\n\n constructor(\n config: Partial<CacheConfig> = {},\n callbacks?: { onCacheHit?: (href: string, layer: CacheLayer) => void }\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.onCacheHit = callbacks?.onCacheHit;\n\n if (\n this.config.serviceWorker.enabled &&\n this.config.serviceWorker.crossTabSync &&\n typeof BroadcastChannel !== \"undefined\"\n ) {\n this.broadcastChannel = new BroadcastChannel(\n this.config.serviceWorker.broadcastChannel\n );\n this.broadcastChannel.onmessage = this.handleBroadcast;\n }\n }\n\n async get(href: string): Promise<string | null> {\n if (this.isExcluded(href)) return null;\n\n // L1: Memory cache\n const memoryHit = this.getFromMemory(href);\n if (memoryHit) {\n this.onCacheHit?.(href, 1);\n return memoryHit;\n }\n\n // L2: Service Worker cache\n if (this.config.serviceWorker.enabled) {\n const swHit = await this.getFromServiceWorker(href);\n if (swHit) {\n this.onCacheHit?.(href, 2);\n this.setInMemory(href, swHit);\n return swHit;\n }\n }\n\n return null;\n }\n\n async set(href: string, html: string): Promise<void> {\n if (this.isExcluded(href)) return;\n\n // Sanitize href to prevent cache poisoning\n const sanitizedHref = this.sanitizeHref(href);\n if (!sanitizedHref) return;\n\n // Validate HTML doesn't contain dangerous inline scripts\n if (this.containsDangerousContent(html)) {\n console.warn(`Refusing to cache potentially dangerous content from ${sanitizedHref}`);\n return;\n }\n\n const page: CachedPage = {\n href: sanitizedHref,\n html,\n timestamp: Date.now(),\n ttl: this.config.serviceWorker.ttl,\n };\n\n // L1: Memory\n if (this.config.memory.enabled) {\n this.setInMemory(sanitizedHref, html);\n }\n\n // L2: Service Worker\n if (this.config.serviceWorker.enabled) {\n await this.setInServiceWorker(page);\n this.broadcastSet(sanitizedHref);\n }\n }\n\n private containsDangerousContent(html: string): boolean {\n // Check for inline event handlers and javascript: URLs\n const dangerousPatterns = [\n /on\\w+\\s*=\\s*[\"'][^\"']*[\"']/i, // Inline event handlers\n /javascript:/i, // javascript: URLs\n ];\n\n // Check for inline scripts, but exclude safe types\n const scriptRegex = /<script([^>]*)>([\\s\\S]*?)<\\/script>/gi;\n let match;\n while ((match = scriptRegex.exec(html)) !== null) {\n const attrs = match[1] || \"\";\n \n // Allow scripts with src attribute\n if (/src\\s*=/.test(attrs)) continue;\n \n // Allow safe script types (JSON data, templates)\n if (/type\\s*=\\s*[\"']?(application\\/json|application\\/ld\\+json|text\\/template)[\"']?/i.test(attrs)) continue;\n \n // Allow Next.js data script\n if (/id\\s*=\\s*[\"']__NEXT_DATA__[\"']/i.test(attrs)) continue;\n \n // If we get here, it's an inline script without safe type\n return true;\n }\n\n return dangerousPatterns.some(pattern => pattern.test(html));\n }\n\n private sanitizeHref(href: string): string | null {\n try {\n const url = new URL(href, window.location.origin);\n // Only allow same-origin URLs\n if (url.origin !== window.location.origin) return null;\n return url.pathname + url.search + url.hash;\n } catch {\n return null;\n }\n }\n\n clear(href?: string): void {\n if (href) {\n this.memoryCache.delete(href);\n this.accessOrder = this.accessOrder.filter((h) => h !== href);\n this.clearFromServiceWorker(href);\n } else {\n this.memoryCache.clear();\n this.accessOrder = [];\n this.clearAllServiceWorker();\n }\n }\n\n getSize(): number {\n return this.memoryCache.size;\n }\n\n destroy(): void {\n this.memoryCache.clear();\n this.accessOrder = [];\n this.broadcastChannel?.close();\n }\n\n private getFromMemory(href: string): string | null {\n const page = this.memoryCache.get(href);\n if (!page) return null;\n\n // Update LRU order\n this.accessOrder = this.accessOrder.filter((h) => h !== href);\n this.accessOrder.push(href);\n\n return page.html;\n }\n\n private setInMemory(href: string, html: string): void {\n // Prevent memory exhaustion - limit individual page size\n const maxPageSize = 5 * 1024 * 1024; // 5MB per page\n if (html.length > maxPageSize) {\n console.warn(`Page ${href} exceeds max size, not caching`);\n return;\n }\n\n this.memoryCache.set(href, {\n href,\n html,\n timestamp: Date.now(),\n ttl: this.config.serviceWorker.ttl,\n });\n\n // Deduplicate before adding to accessOrder\n const existingIndex = this.accessOrder.indexOf(href);\n if (existingIndex !== -1) {\n this.accessOrder.splice(existingIndex, 1);\n }\n this.accessOrder.push(href);\n\n // LRU eviction\n if (this.memoryCache.size > this.config.memory.maxPages) {\n const oldest = this.accessOrder.shift();\n if (oldest) this.memoryCache.delete(oldest);\n }\n }\n\n private async getFromServiceWorker(href: string): Promise<string | null> {\n if (typeof caches === \"undefined\") return null;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n const response = await cache.match(href);\n\n if (!response) return null;\n\n const data = await response.json();\n const page = data as CachedPage;\n\n // Check TTL\n const age = Date.now() - page.timestamp;\n if (age > page.ttl * 1000) {\n await cache.delete(href);\n return null;\n }\n\n return page.html;\n } catch {\n return null;\n }\n }\n\n private async setInServiceWorker(page: CachedPage): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n const response = new Response(JSON.stringify(page), {\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": `max-age=${this.config.edge.maxAge}, stale-while-revalidate=${this.config.edge.staleWhileRevalidate}`,\n },\n });\n await cache.put(page.href, response);\n } catch {\n // Silently fail\n }\n }\n\n private async clearFromServiceWorker(href: string): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n const cache = await caches.open(this.config.serviceWorker.cacheName);\n await cache.delete(href);\n } catch {\n // Silently fail\n }\n }\n\n private async clearAllServiceWorker(): Promise<void> {\n if (typeof caches === \"undefined\") return;\n\n try {\n await caches.delete(this.config.serviceWorker.cacheName);\n } catch {\n // Silently fail\n }\n }\n\n private isExcluded(href: string): boolean {\n return this.config.exclude.some((pattern) => {\n if (typeof pattern === \"string\") {\n return pattern.includes(\"*\")\n ? new RegExp(pattern.replace(/\\*/g, \".*\")).test(href)\n : href === pattern;\n }\n return pattern.test(href);\n });\n }\n\n private broadcastSet(href: string): void {\n this.broadcastChannel?.postMessage({ type: \"set\", href });\n }\n\n private handleBroadcast = (event: MessageEvent): void => {\n if (event.data.type === \"set\") {\n // Invalidate memory cache to force fetch from SW\n this.memoryCache.delete(event.data.href);\n }\n };\n}\n\nexport function createCacheManager(\n config?: Partial<CacheConfig>,\n callbacks?: { onCacheHit?: (href: string, layer: CacheLayer) => void }\n): CacheManager {\n return new CacheManager(config, callbacks);\n}\n"]}
|
package/dist/graph.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
// src/graph.ts
|
|
2
4
|
var DEFAULT_CONFIG = {
|
|
3
5
|
enabled: true,
|
|
@@ -123,6 +125,8 @@ function getNavigationGraph(storageKey = "specnav-graph") {
|
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
|
|
128
|
+
exports.NavigationGraphLearner = NavigationGraphLearner;
|
|
129
|
+
exports.createNavigationGraphLearner = createNavigationGraphLearner;
|
|
130
|
+
exports.getNavigationGraph = getNavigationGraph;
|
|
127
131
|
//# sourceMappingURL=graph.js.map
|
|
128
132
|
//# sourceMappingURL=graph.js.map
|
package/dist/graph.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/graph.ts"],"names":[],"mappings":"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","file":"graph.js","sourcesContent":["import type { GraphConfig, NavigationGraph } from \"./types\";\n\nconst DEFAULT_CONFIG: GraphConfig = {\n enabled: true,\n minConfidence: 0.6,\n sessionWeight: 0.7,\n storageKey: \"specnav-graph\",\n maxNodes: 50,\n};\n\nexport class NavigationGraphLearner {\n private config: GraphConfig;\n private graph: NavigationGraph = {};\n private sessionGraph: NavigationGraph = {};\n\n constructor(config: Partial<GraphConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n if (this.config.enabled) {\n this.loadGraph();\n }\n }\n\n recordNavigation(fromHref: string, toHref: string): void {\n if (!this.config.enabled) return;\n\n // Update session graph\n if (!this.sessionGraph[fromHref]) {\n this.sessionGraph[fromHref] = {};\n }\n const current = this.sessionGraph[fromHref]![toHref] ?? 0;\n this.sessionGraph[fromHref]![toHref] = current + 1;\n\n // Update persistent graph\n if (!this.graph[fromHref]) {\n this.graph[fromHref] = {};\n }\n const persistentCurrent = this.graph[fromHref]![toHref] ?? 0;\n this.graph[fromHref]![toHref] = persistentCurrent + 1;\n\n this.saveGraph();\n }\n\n getPredictions(fromHref: string): string[] {\n if (!this.config.enabled) return [];\n\n const sessionEdges = this.sessionGraph[fromHref] ?? {};\n const persistentEdges = this.graph[fromHref] ?? {};\n\n // Merge session and persistent with weighting\n const merged: Record<string, number> = {};\n\n Object.entries(persistentEdges).forEach(([href, count]) => {\n merged[href] = count * (1 - this.config.sessionWeight);\n });\n\n Object.entries(sessionEdges).forEach(([href, count]) => {\n merged[href] = (merged[href] ?? 0) + count * this.config.sessionWeight;\n });\n\n // Calculate total for normalization\n const total = Object.values(merged).reduce((sum, val) => sum + val, 0);\n if (total === 0) return [];\n\n // Convert to confidence scores and filter\n const predictions = Object.entries(merged)\n .map(([href, count]) => ({\n href,\n confidence: count / total,\n }))\n .filter((p) => p.confidence >= this.config.minConfidence)\n .sort((a, b) => b.confidence - a.confidence)\n .map((p) => p.href);\n\n return predictions;\n }\n\n getGraph(): NavigationGraph {\n return { ...this.graph };\n }\n\n clearGraph(): void {\n this.graph = {};\n this.sessionGraph = {};\n this.saveGraph();\n }\n\n private loadGraph(): void {\n if (typeof localStorage === \"undefined\") return;\n\n try {\n const stored = localStorage.getItem(this.config.storageKey);\n if (stored) {\n this.graph = JSON.parse(stored);\n this.pruneGraph();\n }\n } catch {\n // Silently fail\n }\n }\n\n private saveGraph(): void {\n if (typeof localStorage === \"undefined\") return;\n\n try {\n this.pruneGraph();\n const serialized = JSON.stringify(this.graph);\n \n // Check size before saving (localStorage typically has 5-10MB limit)\n if (serialized.length > 4 * 1024 * 1024) { // 4MB safety limit\n console.warn(\"Navigation graph too large, pruning more aggressively\");\n // Prune to half the max nodes (use local variable)\n const reducedMaxNodes = Math.floor(this.config.maxNodes / 2);\n const originalMaxNodes = this.config.maxNodes;\n this.config.maxNodes = reducedMaxNodes;\n this.pruneGraph();\n this.config.maxNodes = originalMaxNodes; // Restore original\n }\n \n localStorage.setItem(this.config.storageKey, JSON.stringify(this.graph));\n } catch (error) {\n // Handle quota exceeded error\n if (error instanceof Error && error.name === \"QuotaExceededError\") {\n console.warn(\"localStorage quota exceeded, clearing navigation graph\");\n this.clearGraph();\n }\n }\n }\n\n private pruneGraph(): void {\n const nodes = Object.keys(this.graph);\n if (nodes.length <= this.config.maxNodes) return;\n\n // Calculate total transitions per node\n const nodeCounts = nodes.map((node) => ({\n node,\n count: Object.values(this.graph[node]!).reduce(\n (sum, val) => sum + val,\n 0\n ),\n }));\n\n // Sort by count and keep top maxNodes\n nodeCounts.sort((a, b) => b.count - a.count);\n const toKeep = new Set(\n nodeCounts.slice(0, this.config.maxNodes).map((n) => n.node)\n );\n\n // Remove nodes not in top set\n Object.keys(this.graph).forEach((node) => {\n if (!toKeep.has(node)) {\n delete this.graph[node];\n }\n });\n }\n}\n\nexport function createNavigationGraphLearner(\n config?: Partial<GraphConfig>\n): NavigationGraphLearner {\n return new NavigationGraphLearner(config);\n}\n\nexport function getNavigationGraph(\n storageKey = \"specnav-graph\"\n): NavigationGraph {\n if (typeof localStorage === \"undefined\") return {};\n\n try {\n const stored = localStorage.getItem(storageKey);\n return stored ? JSON.parse(stored) : {};\n } catch {\n return {};\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/graph.ts"],"names":[],"mappings":"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","file":"graph.js","sourcesContent":["import type { GraphConfig, NavigationGraph } from \"./types\";\n\nconst DEFAULT_CONFIG: GraphConfig = {\n enabled: true,\n minConfidence: 0.6,\n sessionWeight: 0.7,\n storageKey: \"specnav-graph\",\n maxNodes: 50,\n};\n\nexport class NavigationGraphLearner {\n private config: GraphConfig;\n private graph: NavigationGraph = {};\n private sessionGraph: NavigationGraph = {};\n\n constructor(config: Partial<GraphConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n if (this.config.enabled) {\n this.loadGraph();\n }\n }\n\n recordNavigation(fromHref: string, toHref: string): void {\n if (!this.config.enabled) return;\n\n // Update session graph\n if (!this.sessionGraph[fromHref]) {\n this.sessionGraph[fromHref] = {};\n }\n const current = this.sessionGraph[fromHref]![toHref] ?? 0;\n this.sessionGraph[fromHref]![toHref] = current + 1;\n\n // Update persistent graph\n if (!this.graph[fromHref]) {\n this.graph[fromHref] = {};\n }\n const persistentCurrent = this.graph[fromHref]![toHref] ?? 0;\n this.graph[fromHref]![toHref] = persistentCurrent + 1;\n\n this.saveGraph();\n }\n\n getPredictions(fromHref: string): string[] {\n if (!this.config.enabled) return [];\n\n const sessionEdges = this.sessionGraph[fromHref] ?? {};\n const persistentEdges = this.graph[fromHref] ?? {};\n\n // Merge session and persistent with weighting\n const merged: Record<string, number> = {};\n\n Object.entries(persistentEdges).forEach(([href, count]) => {\n merged[href] = count * (1 - this.config.sessionWeight);\n });\n\n Object.entries(sessionEdges).forEach(([href, count]) => {\n merged[href] = (merged[href] ?? 0) + count * this.config.sessionWeight;\n });\n\n // Calculate total for normalization\n const total = Object.values(merged).reduce((sum, val) => sum + val, 0);\n if (total === 0) return [];\n\n // Convert to confidence scores and filter\n const predictions = Object.entries(merged)\n .map(([href, count]) => ({\n href,\n confidence: count / total,\n }))\n .filter((p) => p.confidence >= this.config.minConfidence)\n .sort((a, b) => b.confidence - a.confidence)\n .map((p) => p.href);\n\n return predictions;\n }\n\n getGraph(): NavigationGraph {\n return { ...this.graph };\n }\n\n clearGraph(): void {\n this.graph = {};\n this.sessionGraph = {};\n this.saveGraph();\n }\n\n private loadGraph(): void {\n if (typeof localStorage === \"undefined\") return;\n\n try {\n const stored = localStorage.getItem(this.config.storageKey);\n if (stored) {\n this.graph = JSON.parse(stored);\n this.pruneGraph();\n }\n } catch {\n // Silently fail\n }\n }\n\n private saveGraph(): void {\n if (typeof localStorage === \"undefined\") return;\n\n try {\n this.pruneGraph();\n const serialized = JSON.stringify(this.graph);\n \n // Check size before saving (localStorage typically has 5-10MB limit)\n if (serialized.length > 4 * 1024 * 1024) { // 4MB safety limit\n console.warn(\"Navigation graph too large, pruning more aggressively\");\n // Prune to half the max nodes (use local variable)\n const reducedMaxNodes = Math.floor(this.config.maxNodes / 2);\n const originalMaxNodes = this.config.maxNodes;\n this.config.maxNodes = reducedMaxNodes;\n this.pruneGraph();\n this.config.maxNodes = originalMaxNodes; // Restore original\n }\n \n localStorage.setItem(this.config.storageKey, JSON.stringify(this.graph));\n } catch (error) {\n // Handle quota exceeded error\n if (error instanceof Error && error.name === \"QuotaExceededError\") {\n console.warn(\"localStorage quota exceeded, clearing navigation graph\");\n this.clearGraph();\n }\n }\n }\n\n private pruneGraph(): void {\n const nodes = Object.keys(this.graph);\n if (nodes.length <= this.config.maxNodes) return;\n\n // Calculate total transitions per node\n const nodeCounts = nodes.map((node) => ({\n node,\n count: Object.values(this.graph[node]!).reduce(\n (sum, val) => sum + val,\n 0\n ),\n }));\n\n // Sort by count and keep top maxNodes\n nodeCounts.sort((a, b) => b.count - a.count);\n const toKeep = new Set(\n nodeCounts.slice(0, this.config.maxNodes).map((n) => n.node)\n );\n\n // Remove nodes not in top set\n Object.keys(this.graph).forEach((node) => {\n if (!toKeep.has(node)) {\n delete this.graph[node];\n }\n });\n }\n}\n\nexport function createNavigationGraphLearner(\n config?: Partial<GraphConfig>\n): NavigationGraphLearner {\n return new NavigationGraphLearner(config);\n}\n\nexport function getNavigationGraph(\n storageKey = \"specnav-graph\"\n): NavigationGraph {\n if (typeof localStorage === \"undefined\") return {};\n\n try {\n const stored = localStorage.getItem(storageKey);\n return stored ? JSON.parse(stored) : {};\n } catch {\n return {};\n }\n}\n"]}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// src/graph.ts
|
|
4
2
|
var DEFAULT_CONFIG = {
|
|
5
3
|
enabled: true,
|
|
@@ -125,8 +123,6 @@ function getNavigationGraph(storageKey = "specnav-graph") {
|
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//# sourceMappingURL=graph.cjs.map
|
|
132
|
-
//# sourceMappingURL=graph.cjs.map
|
|
126
|
+
export { NavigationGraphLearner, createNavigationGraphLearner, getNavigationGraph };
|
|
127
|
+
//# sourceMappingURL=graph.mjs.map
|
|
128
|
+
//# sourceMappingURL=graph.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graph.ts"],"names":[],"mappings":"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","file":"graph.mjs","sourcesContent":["import type { GraphConfig, NavigationGraph } from \"./types\";\n\nconst DEFAULT_CONFIG: GraphConfig = {\n enabled: true,\n minConfidence: 0.6,\n sessionWeight: 0.7,\n storageKey: \"specnav-graph\",\n maxNodes: 50,\n};\n\nexport class NavigationGraphLearner {\n private config: GraphConfig;\n private graph: NavigationGraph = {};\n private sessionGraph: NavigationGraph = {};\n\n constructor(config: Partial<GraphConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n if (this.config.enabled) {\n this.loadGraph();\n }\n }\n\n recordNavigation(fromHref: string, toHref: string): void {\n if (!this.config.enabled) return;\n\n // Update session graph\n if (!this.sessionGraph[fromHref]) {\n this.sessionGraph[fromHref] = {};\n }\n const current = this.sessionGraph[fromHref]![toHref] ?? 0;\n this.sessionGraph[fromHref]![toHref] = current + 1;\n\n // Update persistent graph\n if (!this.graph[fromHref]) {\n this.graph[fromHref] = {};\n }\n const persistentCurrent = this.graph[fromHref]![toHref] ?? 0;\n this.graph[fromHref]![toHref] = persistentCurrent + 1;\n\n this.saveGraph();\n }\n\n getPredictions(fromHref: string): string[] {\n if (!this.config.enabled) return [];\n\n const sessionEdges = this.sessionGraph[fromHref] ?? {};\n const persistentEdges = this.graph[fromHref] ?? {};\n\n // Merge session and persistent with weighting\n const merged: Record<string, number> = {};\n\n Object.entries(persistentEdges).forEach(([href, count]) => {\n merged[href] = count * (1 - this.config.sessionWeight);\n });\n\n Object.entries(sessionEdges).forEach(([href, count]) => {\n merged[href] = (merged[href] ?? 0) + count * this.config.sessionWeight;\n });\n\n // Calculate total for normalization\n const total = Object.values(merged).reduce((sum, val) => sum + val, 0);\n if (total === 0) return [];\n\n // Convert to confidence scores and filter\n const predictions = Object.entries(merged)\n .map(([href, count]) => ({\n href,\n confidence: count / total,\n }))\n .filter((p) => p.confidence >= this.config.minConfidence)\n .sort((a, b) => b.confidence - a.confidence)\n .map((p) => p.href);\n\n return predictions;\n }\n\n getGraph(): NavigationGraph {\n return { ...this.graph };\n }\n\n clearGraph(): void {\n this.graph = {};\n this.sessionGraph = {};\n this.saveGraph();\n }\n\n private loadGraph(): void {\n if (typeof localStorage === \"undefined\") return;\n\n try {\n const stored = localStorage.getItem(this.config.storageKey);\n if (stored) {\n this.graph = JSON.parse(stored);\n this.pruneGraph();\n }\n } catch {\n // Silently fail\n }\n }\n\n private saveGraph(): void {\n if (typeof localStorage === \"undefined\") return;\n\n try {\n this.pruneGraph();\n const serialized = JSON.stringify(this.graph);\n \n // Check size before saving (localStorage typically has 5-10MB limit)\n if (serialized.length > 4 * 1024 * 1024) { // 4MB safety limit\n console.warn(\"Navigation graph too large, pruning more aggressively\");\n // Prune to half the max nodes (use local variable)\n const reducedMaxNodes = Math.floor(this.config.maxNodes / 2);\n const originalMaxNodes = this.config.maxNodes;\n this.config.maxNodes = reducedMaxNodes;\n this.pruneGraph();\n this.config.maxNodes = originalMaxNodes; // Restore original\n }\n \n localStorage.setItem(this.config.storageKey, JSON.stringify(this.graph));\n } catch (error) {\n // Handle quota exceeded error\n if (error instanceof Error && error.name === \"QuotaExceededError\") {\n console.warn(\"localStorage quota exceeded, clearing navigation graph\");\n this.clearGraph();\n }\n }\n }\n\n private pruneGraph(): void {\n const nodes = Object.keys(this.graph);\n if (nodes.length <= this.config.maxNodes) return;\n\n // Calculate total transitions per node\n const nodeCounts = nodes.map((node) => ({\n node,\n count: Object.values(this.graph[node]!).reduce(\n (sum, val) => sum + val,\n 0\n ),\n }));\n\n // Sort by count and keep top maxNodes\n nodeCounts.sort((a, b) => b.count - a.count);\n const toKeep = new Set(\n nodeCounts.slice(0, this.config.maxNodes).map((n) => n.node)\n );\n\n // Remove nodes not in top set\n Object.keys(this.graph).forEach((node) => {\n if (!toKeep.has(node)) {\n delete this.graph[node];\n }\n });\n }\n}\n\nexport function createNavigationGraphLearner(\n config?: Partial<GraphConfig>\n): NavigationGraphLearner {\n return new NavigationGraphLearner(config);\n}\n\nexport function getNavigationGraph(\n storageKey = \"specnav-graph\"\n): NavigationGraph {\n if (typeof localStorage === \"undefined\") return {};\n\n try {\n const stored = localStorage.getItem(storageKey);\n return stored ? JSON.parse(stored) : {};\n } catch {\n return {};\n }\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
// src/trajectory.ts
|
|
2
4
|
var DEFAULT_CONFIG = {
|
|
3
5
|
lookaheadMs: 120,
|
|
@@ -821,6 +823,21 @@ var PerformanceMonitor = class {
|
|
|
821
823
|
};
|
|
822
824
|
var performanceMonitor = new PerformanceMonitor();
|
|
823
825
|
|
|
824
|
-
|
|
826
|
+
exports.AdaptiveMode = AdaptiveMode;
|
|
827
|
+
exports.CacheManager = CacheManager;
|
|
828
|
+
exports.DOMmorpher = DOMmorpher;
|
|
829
|
+
exports.NavigationGraphLearner = NavigationGraphLearner;
|
|
830
|
+
exports.PerformanceMonitor = PerformanceMonitor;
|
|
831
|
+
exports.SpeculativeRenderer = SpeculativeRenderer;
|
|
832
|
+
exports.TrajectoryEngine = TrajectoryEngine;
|
|
833
|
+
exports.createAdaptiveMode = createAdaptiveMode;
|
|
834
|
+
exports.createCacheManager = createCacheManager;
|
|
835
|
+
exports.createMorpher = createMorpher;
|
|
836
|
+
exports.createNavigationGraphLearner = createNavigationGraphLearner;
|
|
837
|
+
exports.createSpeculativeRenderer = createSpeculativeRenderer;
|
|
838
|
+
exports.createTrajectoryEngine = createTrajectoryEngine;
|
|
839
|
+
exports.getNavigationGraph = getNavigationGraph;
|
|
840
|
+
exports.morph = morph;
|
|
841
|
+
exports.performanceMonitor = performanceMonitor;
|
|
825
842
|
//# sourceMappingURL=index.js.map
|
|
826
843
|
//# sourceMappingURL=index.js.map
|