@trops/dash-core 0.1.165 → 0.1.167
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/electron/index.js +223 -12
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +6 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -4512,7 +4512,7 @@ var settingsController_1 = settingsController$1;
|
|
|
4512
4512
|
* window.mainApi.myService.getData({ ...pc, cache: true, forceRefresh: true }); // bypass
|
|
4513
4513
|
*/
|
|
4514
4514
|
|
|
4515
|
-
const cache = new Map(); // key → { data, timestamp, ttl }
|
|
4515
|
+
const cache$1 = new Map(); // key → { data, timestamp, ttl }
|
|
4516
4516
|
const inflight = new Map(); // key → Promise
|
|
4517
4517
|
|
|
4518
4518
|
function stableHash(obj) {
|
|
@@ -4528,13 +4528,13 @@ const responseCache$1 = {
|
|
|
4528
4528
|
async get(key, fetcher, options = {}) {
|
|
4529
4529
|
const { ttl = 30000, forceRefresh = false } = options;
|
|
4530
4530
|
|
|
4531
|
-
if (!forceRefresh && cache.has(key)) {
|
|
4532
|
-
const entry = cache.get(key);
|
|
4531
|
+
if (!forceRefresh && cache$1.has(key)) {
|
|
4532
|
+
const entry = cache$1.get(key);
|
|
4533
4533
|
if (Date.now() - entry.timestamp < entry.ttl) {
|
|
4534
4534
|
console.log(`[responseCache] HIT ${key}`);
|
|
4535
4535
|
return entry.data;
|
|
4536
4536
|
}
|
|
4537
|
-
cache.delete(key);
|
|
4537
|
+
cache$1.delete(key);
|
|
4538
4538
|
}
|
|
4539
4539
|
|
|
4540
4540
|
if (!forceRefresh && inflight.has(key)) {
|
|
@@ -4548,7 +4548,7 @@ const responseCache$1 = {
|
|
|
4548
4548
|
try {
|
|
4549
4549
|
const data = await promise;
|
|
4550
4550
|
if (data && !data.error) {
|
|
4551
|
-
cache.set(key, { data, timestamp: Date.now(), ttl });
|
|
4551
|
+
cache$1.set(key, { data, timestamp: Date.now(), ttl });
|
|
4552
4552
|
}
|
|
4553
4553
|
return data;
|
|
4554
4554
|
} finally {
|
|
@@ -4586,25 +4586,25 @@ const responseCache$1 = {
|
|
|
4586
4586
|
},
|
|
4587
4587
|
|
|
4588
4588
|
invalidate(key) {
|
|
4589
|
-
cache.delete(key);
|
|
4589
|
+
cache$1.delete(key);
|
|
4590
4590
|
},
|
|
4591
4591
|
|
|
4592
4592
|
invalidatePrefix(prefix) {
|
|
4593
|
-
for (const k of cache.keys()) {
|
|
4594
|
-
if (k.startsWith(prefix)) cache.delete(k);
|
|
4593
|
+
for (const k of cache$1.keys()) {
|
|
4594
|
+
if (k.startsWith(prefix)) cache$1.delete(k);
|
|
4595
4595
|
}
|
|
4596
4596
|
},
|
|
4597
4597
|
|
|
4598
4598
|
clear() {
|
|
4599
|
-
cache.clear();
|
|
4599
|
+
cache$1.clear();
|
|
4600
4600
|
inflight.clear();
|
|
4601
4601
|
},
|
|
4602
4602
|
|
|
4603
4603
|
stats() {
|
|
4604
4604
|
return {
|
|
4605
|
-
entries: cache.size,
|
|
4605
|
+
entries: cache$1.size,
|
|
4606
4606
|
inflight: inflight.size,
|
|
4607
|
-
keys: [...cache.keys()],
|
|
4607
|
+
keys: [...cache$1.keys()],
|
|
4608
4608
|
};
|
|
4609
4609
|
},
|
|
4610
4610
|
};
|
|
@@ -45758,6 +45758,208 @@ const webSocketController$1 = {
|
|
|
45758
45758
|
|
|
45759
45759
|
var webSocketController_1 = webSocketController$1;
|
|
45760
45760
|
|
|
45761
|
+
/**
|
|
45762
|
+
* extractionCacheController.js
|
|
45763
|
+
*
|
|
45764
|
+
* LRU cache with TTL for theme-from-URL extraction results.
|
|
45765
|
+
* Caches palette results keyed by URL to avoid re-scanning recently visited sites.
|
|
45766
|
+
*
|
|
45767
|
+
* - Default TTL: 24 hours
|
|
45768
|
+
* - Max entries: 50 (LRU eviction)
|
|
45769
|
+
* - In-memory only — cleared on app restart
|
|
45770
|
+
* - Supports force refresh to bypass cache
|
|
45771
|
+
*/
|
|
45772
|
+
|
|
45773
|
+
const DEFAULT_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
45774
|
+
const MAX_ENTRIES = 50;
|
|
45775
|
+
|
|
45776
|
+
// Map preserves insertion order — we use this for LRU tracking
|
|
45777
|
+
const cache = new Map(); // url → { data, timestamp, ttl }
|
|
45778
|
+
|
|
45779
|
+
/**
|
|
45780
|
+
* Get a cached result for the given URL, or run the fetcher and cache the result.
|
|
45781
|
+
* @param {string} url - The URL key
|
|
45782
|
+
* @param {Function} fetcher - Async function that produces the extraction result
|
|
45783
|
+
* @param {Object} [options]
|
|
45784
|
+
* @param {number} [options.ttl=86400000] - Time-to-live in milliseconds
|
|
45785
|
+
* @param {boolean} [options.forceRefresh=false] - Bypass cache and re-extract
|
|
45786
|
+
* @returns {Promise<any>} The extraction result
|
|
45787
|
+
*/
|
|
45788
|
+
async function get(url, fetcher, options = {}) {
|
|
45789
|
+
const { ttl = DEFAULT_TTL, forceRefresh = false } = options;
|
|
45790
|
+
const key = url.toLowerCase().replace(/\/+$/, ""); // normalize
|
|
45791
|
+
|
|
45792
|
+
if (!forceRefresh && cache.has(key)) {
|
|
45793
|
+
const entry = cache.get(key);
|
|
45794
|
+
if (Date.now() - entry.timestamp < entry.ttl) {
|
|
45795
|
+
// Move to end (most recently used)
|
|
45796
|
+
cache.delete(key);
|
|
45797
|
+
cache.set(key, entry);
|
|
45798
|
+
console.log(`[extractionCache] HIT ${key}`);
|
|
45799
|
+
return entry.data;
|
|
45800
|
+
}
|
|
45801
|
+
// Expired
|
|
45802
|
+
cache.delete(key);
|
|
45803
|
+
}
|
|
45804
|
+
|
|
45805
|
+
console.log(`[extractionCache] ${forceRefresh ? "REFRESH" : "MISS"} ${key}`);
|
|
45806
|
+
const data = await fetcher();
|
|
45807
|
+
|
|
45808
|
+
// Store result
|
|
45809
|
+
cache.set(key, { data, timestamp: Date.now(), ttl });
|
|
45810
|
+
|
|
45811
|
+
// LRU eviction — remove oldest entries if over limit
|
|
45812
|
+
while (cache.size > MAX_ENTRIES) {
|
|
45813
|
+
const oldestKey = cache.keys().next().value;
|
|
45814
|
+
cache.delete(oldestKey);
|
|
45815
|
+
console.log(`[extractionCache] EVICT ${oldestKey}`);
|
|
45816
|
+
}
|
|
45817
|
+
|
|
45818
|
+
return data;
|
|
45819
|
+
}
|
|
45820
|
+
|
|
45821
|
+
/**
|
|
45822
|
+
* Check if a URL has a valid (non-expired) cache entry.
|
|
45823
|
+
* @param {string} url
|
|
45824
|
+
* @returns {boolean}
|
|
45825
|
+
*/
|
|
45826
|
+
function has(url) {
|
|
45827
|
+
const key = url.toLowerCase().replace(/\/+$/, "");
|
|
45828
|
+
if (!cache.has(key)) return false;
|
|
45829
|
+
const entry = cache.get(key);
|
|
45830
|
+
if (Date.now() - entry.timestamp >= entry.ttl) {
|
|
45831
|
+
cache.delete(key);
|
|
45832
|
+
return false;
|
|
45833
|
+
}
|
|
45834
|
+
return true;
|
|
45835
|
+
}
|
|
45836
|
+
|
|
45837
|
+
/** Clear all cached entries. */
|
|
45838
|
+
function clear() {
|
|
45839
|
+
cache.clear();
|
|
45840
|
+
console.log("[extractionCache] CLEARED");
|
|
45841
|
+
}
|
|
45842
|
+
|
|
45843
|
+
/**
|
|
45844
|
+
* Remove a single URL from the cache.
|
|
45845
|
+
* @param {string} url
|
|
45846
|
+
*/
|
|
45847
|
+
function invalidate(url) {
|
|
45848
|
+
const key = url.toLowerCase().replace(/\/+$/, "");
|
|
45849
|
+
cache.delete(key);
|
|
45850
|
+
}
|
|
45851
|
+
|
|
45852
|
+
/** Get cache statistics. */
|
|
45853
|
+
function stats() {
|
|
45854
|
+
return {
|
|
45855
|
+
entries: cache.size,
|
|
45856
|
+
maxEntries: MAX_ENTRIES,
|
|
45857
|
+
keys: [...cache.keys()],
|
|
45858
|
+
};
|
|
45859
|
+
}
|
|
45860
|
+
|
|
45861
|
+
const extractionCacheController$1 = { get, has, clear, invalidate, stats };
|
|
45862
|
+
|
|
45863
|
+
var extractionCacheController_1 = extractionCacheController$1;
|
|
45864
|
+
|
|
45865
|
+
/**
|
|
45866
|
+
* themeFromUrlErrors.js
|
|
45867
|
+
*
|
|
45868
|
+
* Typed error classes for the Theme from URL extraction pipeline.
|
|
45869
|
+
* Used across dash-core (controller), dash-electron (IPC handler),
|
|
45870
|
+
* and dash-react (UI error mapping).
|
|
45871
|
+
*/
|
|
45872
|
+
|
|
45873
|
+
const ERROR_TYPES = {
|
|
45874
|
+
URL_UNREACHABLE: "URL_UNREACHABLE",
|
|
45875
|
+
URL_TIMEOUT: "URL_TIMEOUT",
|
|
45876
|
+
EXTRACTION_FAILED: "EXTRACTION_FAILED",
|
|
45877
|
+
NO_COLORS_FOUND: "NO_COLORS_FOUND",
|
|
45878
|
+
FAVICON_FETCH_FAILED: "FAVICON_FETCH_FAILED",
|
|
45879
|
+
};
|
|
45880
|
+
|
|
45881
|
+
class ThemeExtractionError extends Error {
|
|
45882
|
+
/**
|
|
45883
|
+
* @param {string} message - Developer-facing error message
|
|
45884
|
+
* @param {Object} options
|
|
45885
|
+
* @param {string} options.type - Machine-readable error type (from ERROR_TYPES)
|
|
45886
|
+
* @param {string} options.userMessage - User-facing message for UI display
|
|
45887
|
+
* @param {Error} [options.cause] - Original error that triggered this one
|
|
45888
|
+
*/
|
|
45889
|
+
constructor(message, { type, userMessage, cause } = {}) {
|
|
45890
|
+
super(message);
|
|
45891
|
+
this.name = "ThemeExtractionError";
|
|
45892
|
+
this.type = type;
|
|
45893
|
+
this.userMessage = userMessage || "Something went wrong extracting colors.";
|
|
45894
|
+
this.cause = cause || null;
|
|
45895
|
+
}
|
|
45896
|
+
}
|
|
45897
|
+
|
|
45898
|
+
class UrlUnreachableError extends ThemeExtractionError {
|
|
45899
|
+
constructor(message, { cause } = {}) {
|
|
45900
|
+
super(message || "URL is unreachable", {
|
|
45901
|
+
type: ERROR_TYPES.URL_UNREACHABLE,
|
|
45902
|
+
userMessage: "Couldn't reach that URL. Check the address.",
|
|
45903
|
+
cause,
|
|
45904
|
+
});
|
|
45905
|
+
this.name = "UrlUnreachableError";
|
|
45906
|
+
}
|
|
45907
|
+
}
|
|
45908
|
+
|
|
45909
|
+
class UrlTimeoutError extends ThemeExtractionError {
|
|
45910
|
+
constructor(message, { cause } = {}) {
|
|
45911
|
+
super(message || "URL load timed out", {
|
|
45912
|
+
type: ERROR_TYPES.URL_TIMEOUT,
|
|
45913
|
+
userMessage: "The site took too long to load. Try a simpler page.",
|
|
45914
|
+
cause,
|
|
45915
|
+
});
|
|
45916
|
+
this.name = "UrlTimeoutError";
|
|
45917
|
+
}
|
|
45918
|
+
}
|
|
45919
|
+
|
|
45920
|
+
class ExtractionFailedError extends ThemeExtractionError {
|
|
45921
|
+
constructor(message, { cause } = {}) {
|
|
45922
|
+
super(message || "Color extraction failed", {
|
|
45923
|
+
type: ERROR_TYPES.EXTRACTION_FAILED,
|
|
45924
|
+
userMessage: "Failed to extract colors from this site.",
|
|
45925
|
+
cause,
|
|
45926
|
+
});
|
|
45927
|
+
this.name = "ExtractionFailedError";
|
|
45928
|
+
}
|
|
45929
|
+
}
|
|
45930
|
+
|
|
45931
|
+
class NoColorsFoundError extends ThemeExtractionError {
|
|
45932
|
+
constructor(message, { cause } = {}) {
|
|
45933
|
+
super(message || "No usable colors found", {
|
|
45934
|
+
type: ERROR_TYPES.NO_COLORS_FOUND,
|
|
45935
|
+
userMessage: "No usable colors found. Try a more styled page.",
|
|
45936
|
+
cause,
|
|
45937
|
+
});
|
|
45938
|
+
this.name = "NoColorsFoundError";
|
|
45939
|
+
}
|
|
45940
|
+
}
|
|
45941
|
+
|
|
45942
|
+
class FaviconFetchError extends ThemeExtractionError {
|
|
45943
|
+
constructor(message, { cause } = {}) {
|
|
45944
|
+
super(message || "Favicon fetch failed", {
|
|
45945
|
+
type: ERROR_TYPES.FAVICON_FETCH_FAILED,
|
|
45946
|
+
userMessage: "Couldn't fetch the site's favicon.",
|
|
45947
|
+
cause,
|
|
45948
|
+
});
|
|
45949
|
+
this.name = "FaviconFetchError";
|
|
45950
|
+
}
|
|
45951
|
+
}
|
|
45952
|
+
|
|
45953
|
+
var themeFromUrlErrors$1 = {
|
|
45954
|
+
ERROR_TYPES,
|
|
45955
|
+
ThemeExtractionError,
|
|
45956
|
+
UrlUnreachableError,
|
|
45957
|
+
UrlTimeoutError,
|
|
45958
|
+
ExtractionFailedError,
|
|
45959
|
+
NoColorsFoundError,
|
|
45960
|
+
FaviconFetchError,
|
|
45961
|
+
};
|
|
45962
|
+
|
|
45761
45963
|
/**
|
|
45762
45964
|
* clientFactories.js
|
|
45763
45965
|
*
|
|
@@ -48126,7 +48328,8 @@ const {
|
|
|
48126
48328
|
} = events$8;
|
|
48127
48329
|
|
|
48128
48330
|
const themeFromUrlApi$2 = {
|
|
48129
|
-
extractFromUrl: (url
|
|
48331
|
+
extractFromUrl: (url, { forceRefresh = false } = {}) =>
|
|
48332
|
+
ipcRenderer$4.invoke(THEME_EXTRACT_FROM_URL, { url, forceRefresh }),
|
|
48130
48333
|
mapPaletteToTheme: (palette, overrides) =>
|
|
48131
48334
|
ipcRenderer$4.invoke(THEME_MAP_PALETTE_TO_THEME, { palette, overrides }),
|
|
48132
48335
|
};
|
|
@@ -48508,6 +48711,10 @@ const themeRegistryController = themeRegistryController$1;
|
|
|
48508
48711
|
const themeFromUrlController = themeFromUrlController_1;
|
|
48509
48712
|
const paletteToThemeMapper = paletteToThemeMapper_1;
|
|
48510
48713
|
const webSocketController = webSocketController_1;
|
|
48714
|
+
const extractionCacheController = extractionCacheController_1;
|
|
48715
|
+
|
|
48716
|
+
// --- Errors ---
|
|
48717
|
+
const themeFromUrlErrors = themeFromUrlErrors$1;
|
|
48511
48718
|
|
|
48512
48719
|
// --- Utils ---
|
|
48513
48720
|
const clientCache = requireClientCache();
|
|
@@ -48584,6 +48791,7 @@ var electron = {
|
|
|
48584
48791
|
themeFromUrlController,
|
|
48585
48792
|
paletteToThemeMapper,
|
|
48586
48793
|
webSocketController,
|
|
48794
|
+
extractionCacheController,
|
|
48587
48795
|
|
|
48588
48796
|
// Controller functions (flat) — spread for convenient destructuring
|
|
48589
48797
|
...controllers,
|
|
@@ -48629,6 +48837,9 @@ var electron = {
|
|
|
48629
48837
|
clientCache,
|
|
48630
48838
|
responseCache,
|
|
48631
48839
|
|
|
48840
|
+
// Errors
|
|
48841
|
+
themeFromUrlErrors,
|
|
48842
|
+
|
|
48632
48843
|
// Schema
|
|
48633
48844
|
dashboardConfigValidator,
|
|
48634
48845
|
dashboardConfigUtils,
|