@lionrapid/core 0.6.6 → 0.7.0

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/dist/browser/auto-init.d.ts +6 -0
  3. package/dist/browser/auto-init.d.ts.map +1 -1
  4. package/dist/browser/auto-init.js +36 -6
  5. package/dist/browser/auto-init.js.map +1 -1
  6. package/dist/browser/language-switcher.d.ts +12 -0
  7. package/dist/browser/language-switcher.d.ts.map +1 -1
  8. package/dist/browser/language-switcher.js +58 -0
  9. package/dist/browser/language-switcher.js.map +1 -1
  10. package/dist/browser/lionrapid.auto.dev.js +163 -14
  11. package/dist/browser/lionrapid.auto.dev.js.map +3 -3
  12. package/dist/browser/lionrapid.auto.js +163 -14
  13. package/dist/browser/lionrapid.auto.js.map +3 -3
  14. package/dist/browser/lionrapid.auto.min.js +5 -5
  15. package/dist/browser/lionrapid.auto.min.js.map +4 -4
  16. package/dist/browser/lionrapid.esm.js +103 -10
  17. package/dist/browser/lionrapid.esm.js.map +2 -2
  18. package/dist/browser/lionrapid.esm.min.js +4 -4
  19. package/dist/browser/lionrapid.esm.min.js.map +3 -3
  20. package/dist/browser/lionrapid.iife.dev.js +103 -10
  21. package/dist/browser/lionrapid.iife.dev.js.map +2 -2
  22. package/dist/browser/lionrapid.iife.js +103 -10
  23. package/dist/browser/lionrapid.iife.js.map +2 -2
  24. package/dist/browser/lionrapid.iife.min.js +4 -4
  25. package/dist/browser/lionrapid.iife.min.js.map +3 -3
  26. package/dist/browser/lionrapid.umd.js +103 -10
  27. package/dist/browser/lionrapid.umd.js.map +2 -2
  28. package/dist/browser/lionrapid.umd.min.js +4 -4
  29. package/dist/browser/lionrapid.umd.min.js.map +3 -3
  30. package/dist/browser/storage/cache-storage-repository.d.ts +9 -0
  31. package/dist/browser/storage/cache-storage-repository.d.ts.map +1 -1
  32. package/dist/browser/storage/cache-storage-repository.js +14 -10
  33. package/dist/browser/storage/cache-storage-repository.js.map +1 -1
  34. package/dist/browser/storage/scope.d.ts +12 -0
  35. package/dist/browser/storage/scope.d.ts.map +1 -0
  36. package/dist/browser/storage/scope.js +15 -0
  37. package/dist/browser/storage/scope.js.map +1 -0
  38. package/dist/builder.d.ts.map +1 -1
  39. package/dist/builder.js +9 -0
  40. package/dist/builder.js.map +1 -1
  41. package/dist/handlers/async-storage-handler.d.ts +9 -0
  42. package/dist/handlers/async-storage-handler.d.ts.map +1 -1
  43. package/dist/handlers/async-storage-handler.js +42 -0
  44. package/dist/handlers/async-storage-handler.js.map +1 -1
  45. package/dist/handlers/handler-chain.d.ts +2 -0
  46. package/dist/handlers/handler-chain.d.ts.map +1 -1
  47. package/dist/handlers/handler-chain.js +29 -0
  48. package/dist/handlers/handler-chain.js.map +1 -1
  49. package/dist/handlers/sync-storage-handler.d.ts +4 -0
  50. package/dist/handlers/sync-storage-handler.d.ts.map +1 -1
  51. package/dist/handlers/sync-storage-handler.js +35 -0
  52. package/dist/handlers/sync-storage-handler.js.map +1 -1
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/interfaces/handler.interface.d.ts +4 -0
  56. package/dist/interfaces/handler.interface.d.ts.map +1 -1
  57. package/package.json +3 -3
@@ -1246,6 +1246,24 @@ var HandlerChain = class {
1246
1246
  });
1247
1247
  await Promise.allSettled(savePromises);
1248
1248
  }
1249
+ /** Forget `locale:namespace` in every memory and storage handler (a 404). */
1250
+ async forgetNamespaceInHandlers(context) {
1251
+ for (const handler of this.syncHandlers) {
1252
+ try {
1253
+ handler.forgetNamespace?.(context);
1254
+ } catch (error) {
1255
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1256
+ }
1257
+ }
1258
+ for (const handler of this.asyncHandlers) {
1259
+ if (handler.type !== "storage" && handler.type !== "memory") continue;
1260
+ try {
1261
+ await handler.forgetNamespace?.(context);
1262
+ } catch (error) {
1263
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1264
+ }
1265
+ }
1266
+ }
1249
1267
  async saveNamespaceToHandlers(translations, context) {
1250
1268
  this.saveNamespaceToMemoryHandlers(translations, context);
1251
1269
  await this.saveNamespaceToStorageHandlers(translations, context);
@@ -1347,6 +1365,10 @@ var HandlerChain = class {
1347
1365
  });
1348
1366
  return result.data;
1349
1367
  }
1368
+ if (result && result.status === 404) {
1369
+ await this.forgetNamespaceInHandlers({ namespace, locale });
1370
+ return null;
1371
+ }
1350
1372
  } catch (error) {
1351
1373
  logger.warn(
1352
1374
  `Failed to load namespace: ${locale}:${namespace} from ${handler.name}`,
@@ -1730,6 +1752,7 @@ var SyncHandler = class {
1730
1752
  context.locale + ":" + context.namespace,
1731
1753
  this.name
1732
1754
  );
1755
+ const previousKeys = this.storedNamespaceKeys(context.locale, context.namespace);
1733
1756
  const namespaceCacheKey = createNamespaceCacheKey(
1734
1757
  context.locale,
1735
1758
  context.namespace
@@ -1741,11 +1764,38 @@ var SyncHandler = class {
1741
1764
  context.locale,
1742
1765
  context.namespace
1743
1766
  );
1767
+ for (const key of previousKeys) {
1768
+ if (!(key in flattened)) this.removeQuietly(key);
1769
+ }
1744
1770
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1745
1771
  this.setWithTTL(flatKey, flatValue);
1746
1772
  }
1747
1773
  repoBus.setSuccess(JSON.stringify(translations));
1748
1774
  }
1775
+ /** Forget a namespace the server says does not exist (404). See AsyncStorageHandler. */
1776
+ forgetNamespace(context) {
1777
+ if (!context.namespace) return;
1778
+ for (const key of this.storedNamespaceKeys(context.locale, context.namespace)) {
1779
+ this.removeQuietly(key);
1780
+ }
1781
+ this.clearNamespace(context.locale, context.namespace);
1782
+ }
1783
+ storedNamespaceKeys(locale, namespace) {
1784
+ try {
1785
+ const blob = this.repository.get(createNamespaceCacheKey(locale, namespace));
1786
+ if (!blob) return [];
1787
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
1788
+ } catch {
1789
+ return [];
1790
+ }
1791
+ }
1792
+ removeQuietly(cacheKey) {
1793
+ this.ttlMap.delete(cacheKey);
1794
+ try {
1795
+ this.repository.remove(cacheKey);
1796
+ } catch {
1797
+ }
1798
+ }
1749
1799
  /**
1750
1800
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1751
1801
  * Returns parsed ContentUnit (text + styles) or null
@@ -1999,6 +2049,7 @@ var AsyncHandler = class {
1999
2049
  logger.debug(
2000
2050
  `Saving namespace: ${context.locale}:${context.namespace} (${keyCount} keys)`
2001
2051
  );
2052
+ const previousKeys = await this.storedNamespaceKeys(context.locale, context.namespace);
2002
2053
  const namespaceCacheKey = createNamespaceCacheKey(
2003
2054
  context.locale,
2004
2055
  context.namespace
@@ -2010,6 +2061,9 @@ var AsyncHandler = class {
2010
2061
  context.locale,
2011
2062
  context.namespace
2012
2063
  );
2064
+ for (const key of previousKeys) {
2065
+ if (!(key in flattened)) await this.removeQuietly(key);
2066
+ }
2013
2067
  let savedCount = 0;
2014
2068
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
2015
2069
  await this.setWithTTL(flatKey, flatValue);
@@ -2018,6 +2072,35 @@ var AsyncHandler = class {
2018
2072
  logger.debug(`Saved: ${savedCount} keys + namespace blob`);
2019
2073
  repoBus.setSuccess(JSON.stringify(translations));
2020
2074
  }
2075
+ /**
2076
+ * Forget a namespace the server says does not exist (404): its blob and
2077
+ * every per-key entry the blob listed. A network failure never gets here,
2078
+ * so the offline floor still serves when the server is unreachable.
2079
+ */
2080
+ async forgetNamespace(context) {
2081
+ if (!context.namespace) return;
2082
+ for (const key of await this.storedNamespaceKeys(context.locale, context.namespace)) {
2083
+ await this.removeQuietly(key);
2084
+ }
2085
+ await this.removeQuietly(createNamespaceCacheKey(context.locale, context.namespace));
2086
+ }
2087
+ /** The per-key cache keys (`locale:namespace:key`) the stored blob lists. */
2088
+ async storedNamespaceKeys(locale, namespace) {
2089
+ try {
2090
+ const blob = await this.repository.get(createNamespaceCacheKey(locale, namespace));
2091
+ if (!blob) return [];
2092
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
2093
+ } catch {
2094
+ return [];
2095
+ }
2096
+ }
2097
+ async removeQuietly(cacheKey) {
2098
+ this.ttlMap.delete(cacheKey);
2099
+ try {
2100
+ await this.repository.remove(cacheKey);
2101
+ } catch {
2102
+ }
2103
+ }
2021
2104
  /**
2022
2105
  * ✅ Strategy 1: Direct cache lookup using unified pattern
2023
2106
  * Returns parsed ContentUnit (text + styles) or null
@@ -4245,6 +4328,12 @@ var LionRapidBuilder = class _LionRapidBuilder {
4245
4328
  logger.debug(`Preloaded: ${namespaceKey}`);
4246
4329
  return;
4247
4330
  }
4331
+ if (result?.status === 404) {
4332
+ await handlerChain.forgetNamespaceInHandlers({
4333
+ locale: preloadConfig.locale,
4334
+ namespace: preloadConfig.namespace
4335
+ });
4336
+ }
4248
4337
  if (result?.error) {
4249
4338
  warnLoudlyOnce(namespaceKey, result.error);
4250
4339
  logger.warn(`Preload failed: ${namespaceKey}`, result.error);
@@ -5621,6 +5710,8 @@ var CacheStorageRepository = class {
5621
5710
  __publicField(this, "type", "storage");
5622
5711
  __publicField(this, "config");
5623
5712
  __publicField(this, "cacheName");
5713
+ /** CACHE_PREFIX, plus `<scope>/` when scoped. */
5714
+ __publicField(this, "entryPrefix");
5624
5715
  __publicField(this, "defaultTTL");
5625
5716
  __publicField(this, "available");
5626
5717
  this.config = {
@@ -5634,6 +5725,8 @@ var CacheStorageRepository = class {
5634
5725
  }
5635
5726
  };
5636
5727
  this.cacheName = this.config.options?.cacheName ?? CACHE_NAME;
5728
+ const scope = this.config.options?.scope;
5729
+ this.entryPrefix = scope ? `${CACHE_PREFIX}${encodeURIComponent(scope)}/` : CACHE_PREFIX;
5637
5730
  this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
5638
5731
  this.available = this.checkAvailability();
5639
5732
  }
@@ -5654,13 +5747,13 @@ var CacheStorageRepository = class {
5654
5747
  }
5655
5748
  try {
5656
5749
  const cache = await caches.open(this.cacheName);
5657
- const response = await cache.match(CACHE_PREFIX + key);
5750
+ const response = await cache.match(this.entryPrefix + key);
5658
5751
  if (!response) {
5659
5752
  throw new TranslationNotFoundError(key);
5660
5753
  }
5661
5754
  const data = await response.json();
5662
5755
  if (data.expiry !== null && Date.now() > data.expiry) {
5663
- await cache.delete(CACHE_PREFIX + key);
5756
+ await cache.delete(this.entryPrefix + key);
5664
5757
  throw new TranslationNotFoundError(key);
5665
5758
  }
5666
5759
  return data.value;
@@ -5684,7 +5777,7 @@ var CacheStorageRepository = class {
5684
5777
  timestamp: Date.now()
5685
5778
  };
5686
5779
  await cache.put(
5687
- CACHE_PREFIX + key,
5780
+ this.entryPrefix + key,
5688
5781
  new Response(JSON.stringify(data), {
5689
5782
  headers: { "Content-Type": "application/json" }
5690
5783
  })
@@ -5699,7 +5792,7 @@ var CacheStorageRepository = class {
5699
5792
  if (!this.available) return;
5700
5793
  try {
5701
5794
  const cache = await caches.open(this.cacheName);
5702
- await cache.delete(CACHE_PREFIX + key);
5795
+ await cache.delete(this.entryPrefix + key);
5703
5796
  } catch {
5704
5797
  }
5705
5798
  }
@@ -5712,7 +5805,7 @@ var CacheStorageRepository = class {
5712
5805
  const cache = await caches.open(this.cacheName);
5713
5806
  const keys = await cache.keys();
5714
5807
  await Promise.all(
5715
- keys.filter((request) => request.url.includes(CACHE_PREFIX)).map((request) => cache.delete(request))
5808
+ keys.filter((request) => request.url.includes(this.entryPrefix)).map((request) => cache.delete(request))
5716
5809
  );
5717
5810
  } catch {
5718
5811
  }
@@ -5730,7 +5823,7 @@ var CacheStorageRepository = class {
5730
5823
  await Promise.all(
5731
5824
  keys.filter((request) => {
5732
5825
  const url = request.url;
5733
- const key = url.includes(CACHE_PREFIX) ? url.substring(url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length) : "";
5826
+ const key = url.includes(this.entryPrefix) ? url.substring(url.indexOf(this.entryPrefix) + this.entryPrefix.length) : "";
5734
5827
  return key === namespaceCacheKey || key.startsWith(keyPrefix);
5735
5828
  }).map((request) => cache.delete(request))
5736
5829
  );
@@ -5782,9 +5875,9 @@ var CacheStorageRepository = class {
5782
5875
  const keys = await cache.keys();
5783
5876
  for (const request of keys) {
5784
5877
  const url = request.url;
5785
- if (!url.includes(CACHE_PREFIX)) continue;
5878
+ if (!url.includes(this.entryPrefix)) continue;
5786
5879
  const key = url.substring(
5787
- url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length
5880
+ url.indexOf(this.entryPrefix) + this.entryPrefix.length
5788
5881
  );
5789
5882
  try {
5790
5883
  const value = await this.get(key);
@@ -5806,7 +5899,7 @@ var CacheStorageRepository = class {
5806
5899
  try {
5807
5900
  const cache = await caches.open(this.cacheName);
5808
5901
  const keys = await cache.keys();
5809
- const size = keys.filter((r) => r.url.includes(CACHE_PREFIX)).length;
5902
+ const size = keys.filter((r) => r.url.includes(this.entryPrefix)).length;
5810
5903
  return { size, cacheName: this.cacheName };
5811
5904
  } catch {
5812
5905
  return { size: 0, cacheName: this.cacheName };
@@ -5818,7 +5911,7 @@ var CacheStorageRepository = class {
5818
5911
  var DEFAULT_API_BASE_URL = "https://app.lionrapid.com";
5819
5912
 
5820
5913
  // src/index.ts
5821
- var VERSION = "0.6.6";
5914
+ var VERSION = "0.7.0";
5822
5915
  //# sourceMappingURL=lionrapid.umd.js.map
5823
5916
 
5824
5917
  return exports;