@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
@@ -1149,6 +1149,24 @@ var HandlerChain = class {
1149
1149
  });
1150
1150
  await Promise.allSettled(savePromises);
1151
1151
  }
1152
+ /** Forget `locale:namespace` in every memory and storage handler (a 404). */
1153
+ async forgetNamespaceInHandlers(context) {
1154
+ for (const handler of this.syncHandlers) {
1155
+ try {
1156
+ handler.forgetNamespace?.(context);
1157
+ } catch (error) {
1158
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1159
+ }
1160
+ }
1161
+ for (const handler of this.asyncHandlers) {
1162
+ if (handler.type !== "storage" && handler.type !== "memory") continue;
1163
+ try {
1164
+ await handler.forgetNamespace?.(context);
1165
+ } catch (error) {
1166
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1167
+ }
1168
+ }
1169
+ }
1152
1170
  async saveNamespaceToHandlers(translations, context) {
1153
1171
  this.saveNamespaceToMemoryHandlers(translations, context);
1154
1172
  await this.saveNamespaceToStorageHandlers(translations, context);
@@ -1250,6 +1268,10 @@ var HandlerChain = class {
1250
1268
  });
1251
1269
  return result.data;
1252
1270
  }
1271
+ if (result && result.status === 404) {
1272
+ await this.forgetNamespaceInHandlers({ namespace, locale });
1273
+ return null;
1274
+ }
1253
1275
  } catch (error) {
1254
1276
  logger.warn(
1255
1277
  `Failed to load namespace: ${locale}:${namespace} from ${handler.name}`,
@@ -1633,6 +1655,7 @@ var SyncHandler = class {
1633
1655
  context.locale + ":" + context.namespace,
1634
1656
  this.name
1635
1657
  );
1658
+ const previousKeys = this.storedNamespaceKeys(context.locale, context.namespace);
1636
1659
  const namespaceCacheKey = createNamespaceCacheKey(
1637
1660
  context.locale,
1638
1661
  context.namespace
@@ -1644,11 +1667,38 @@ var SyncHandler = class {
1644
1667
  context.locale,
1645
1668
  context.namespace
1646
1669
  );
1670
+ for (const key of previousKeys) {
1671
+ if (!(key in flattened)) this.removeQuietly(key);
1672
+ }
1647
1673
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1648
1674
  this.setWithTTL(flatKey, flatValue);
1649
1675
  }
1650
1676
  repoBus.setSuccess(JSON.stringify(translations));
1651
1677
  }
1678
+ /** Forget a namespace the server says does not exist (404). See AsyncStorageHandler. */
1679
+ forgetNamespace(context) {
1680
+ if (!context.namespace) return;
1681
+ for (const key of this.storedNamespaceKeys(context.locale, context.namespace)) {
1682
+ this.removeQuietly(key);
1683
+ }
1684
+ this.clearNamespace(context.locale, context.namespace);
1685
+ }
1686
+ storedNamespaceKeys(locale, namespace) {
1687
+ try {
1688
+ const blob = this.repository.get(createNamespaceCacheKey(locale, namespace));
1689
+ if (!blob) return [];
1690
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
1691
+ } catch {
1692
+ return [];
1693
+ }
1694
+ }
1695
+ removeQuietly(cacheKey) {
1696
+ this.ttlMap.delete(cacheKey);
1697
+ try {
1698
+ this.repository.remove(cacheKey);
1699
+ } catch {
1700
+ }
1701
+ }
1652
1702
  /**
1653
1703
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1654
1704
  * Returns parsed ContentUnit (text + styles) or null
@@ -1902,6 +1952,7 @@ var AsyncHandler = class {
1902
1952
  logger.debug(
1903
1953
  `Saving namespace: ${context.locale}:${context.namespace} (${keyCount} keys)`
1904
1954
  );
1955
+ const previousKeys = await this.storedNamespaceKeys(context.locale, context.namespace);
1905
1956
  const namespaceCacheKey = createNamespaceCacheKey(
1906
1957
  context.locale,
1907
1958
  context.namespace
@@ -1913,6 +1964,9 @@ var AsyncHandler = class {
1913
1964
  context.locale,
1914
1965
  context.namespace
1915
1966
  );
1967
+ for (const key of previousKeys) {
1968
+ if (!(key in flattened)) await this.removeQuietly(key);
1969
+ }
1916
1970
  let savedCount = 0;
1917
1971
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1918
1972
  await this.setWithTTL(flatKey, flatValue);
@@ -1921,6 +1975,35 @@ var AsyncHandler = class {
1921
1975
  logger.debug(`Saved: ${savedCount} keys + namespace blob`);
1922
1976
  repoBus.setSuccess(JSON.stringify(translations));
1923
1977
  }
1978
+ /**
1979
+ * Forget a namespace the server says does not exist (404): its blob and
1980
+ * every per-key entry the blob listed. A network failure never gets here,
1981
+ * so the offline floor still serves when the server is unreachable.
1982
+ */
1983
+ async forgetNamespace(context) {
1984
+ if (!context.namespace) return;
1985
+ for (const key of await this.storedNamespaceKeys(context.locale, context.namespace)) {
1986
+ await this.removeQuietly(key);
1987
+ }
1988
+ await this.removeQuietly(createNamespaceCacheKey(context.locale, context.namespace));
1989
+ }
1990
+ /** The per-key cache keys (`locale:namespace:key`) the stored blob lists. */
1991
+ async storedNamespaceKeys(locale, namespace) {
1992
+ try {
1993
+ const blob = await this.repository.get(createNamespaceCacheKey(locale, namespace));
1994
+ if (!blob) return [];
1995
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
1996
+ } catch {
1997
+ return [];
1998
+ }
1999
+ }
2000
+ async removeQuietly(cacheKey) {
2001
+ this.ttlMap.delete(cacheKey);
2002
+ try {
2003
+ await this.repository.remove(cacheKey);
2004
+ } catch {
2005
+ }
2006
+ }
1924
2007
  /**
1925
2008
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1926
2009
  * Returns parsed ContentUnit (text + styles) or null
@@ -4148,6 +4231,12 @@ var LionRapidBuilder = class _LionRapidBuilder {
4148
4231
  logger.debug(`Preloaded: ${namespaceKey}`);
4149
4232
  return;
4150
4233
  }
4234
+ if (result?.status === 404) {
4235
+ await handlerChain.forgetNamespaceInHandlers({
4236
+ locale: preloadConfig.locale,
4237
+ namespace: preloadConfig.namespace
4238
+ });
4239
+ }
4151
4240
  if (result?.error) {
4152
4241
  warnLoudlyOnce(namespaceKey, result.error);
4153
4242
  logger.warn(`Preload failed: ${namespaceKey}`, result.error);
@@ -5524,6 +5613,8 @@ var CacheStorageRepository = class {
5524
5613
  __publicField(this, "type", "storage");
5525
5614
  __publicField(this, "config");
5526
5615
  __publicField(this, "cacheName");
5616
+ /** CACHE_PREFIX, plus `<scope>/` when scoped. */
5617
+ __publicField(this, "entryPrefix");
5527
5618
  __publicField(this, "defaultTTL");
5528
5619
  __publicField(this, "available");
5529
5620
  this.config = {
@@ -5537,6 +5628,8 @@ var CacheStorageRepository = class {
5537
5628
  }
5538
5629
  };
5539
5630
  this.cacheName = this.config.options?.cacheName ?? CACHE_NAME;
5631
+ const scope = this.config.options?.scope;
5632
+ this.entryPrefix = scope ? `${CACHE_PREFIX}${encodeURIComponent(scope)}/` : CACHE_PREFIX;
5540
5633
  this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
5541
5634
  this.available = this.checkAvailability();
5542
5635
  }
@@ -5557,13 +5650,13 @@ var CacheStorageRepository = class {
5557
5650
  }
5558
5651
  try {
5559
5652
  const cache = await caches.open(this.cacheName);
5560
- const response = await cache.match(CACHE_PREFIX + key);
5653
+ const response = await cache.match(this.entryPrefix + key);
5561
5654
  if (!response) {
5562
5655
  throw new TranslationNotFoundError(key);
5563
5656
  }
5564
5657
  const data = await response.json();
5565
5658
  if (data.expiry !== null && Date.now() > data.expiry) {
5566
- await cache.delete(CACHE_PREFIX + key);
5659
+ await cache.delete(this.entryPrefix + key);
5567
5660
  throw new TranslationNotFoundError(key);
5568
5661
  }
5569
5662
  return data.value;
@@ -5587,7 +5680,7 @@ var CacheStorageRepository = class {
5587
5680
  timestamp: Date.now()
5588
5681
  };
5589
5682
  await cache.put(
5590
- CACHE_PREFIX + key,
5683
+ this.entryPrefix + key,
5591
5684
  new Response(JSON.stringify(data), {
5592
5685
  headers: { "Content-Type": "application/json" }
5593
5686
  })
@@ -5602,7 +5695,7 @@ var CacheStorageRepository = class {
5602
5695
  if (!this.available) return;
5603
5696
  try {
5604
5697
  const cache = await caches.open(this.cacheName);
5605
- await cache.delete(CACHE_PREFIX + key);
5698
+ await cache.delete(this.entryPrefix + key);
5606
5699
  } catch {
5607
5700
  }
5608
5701
  }
@@ -5615,7 +5708,7 @@ var CacheStorageRepository = class {
5615
5708
  const cache = await caches.open(this.cacheName);
5616
5709
  const keys = await cache.keys();
5617
5710
  await Promise.all(
5618
- keys.filter((request) => request.url.includes(CACHE_PREFIX)).map((request) => cache.delete(request))
5711
+ keys.filter((request) => request.url.includes(this.entryPrefix)).map((request) => cache.delete(request))
5619
5712
  );
5620
5713
  } catch {
5621
5714
  }
@@ -5633,7 +5726,7 @@ var CacheStorageRepository = class {
5633
5726
  await Promise.all(
5634
5727
  keys.filter((request) => {
5635
5728
  const url = request.url;
5636
- const key = url.includes(CACHE_PREFIX) ? url.substring(url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length) : "";
5729
+ const key = url.includes(this.entryPrefix) ? url.substring(url.indexOf(this.entryPrefix) + this.entryPrefix.length) : "";
5637
5730
  return key === namespaceCacheKey || key.startsWith(keyPrefix);
5638
5731
  }).map((request) => cache.delete(request))
5639
5732
  );
@@ -5685,9 +5778,9 @@ var CacheStorageRepository = class {
5685
5778
  const keys = await cache.keys();
5686
5779
  for (const request of keys) {
5687
5780
  const url = request.url;
5688
- if (!url.includes(CACHE_PREFIX)) continue;
5781
+ if (!url.includes(this.entryPrefix)) continue;
5689
5782
  const key = url.substring(
5690
- url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length
5783
+ url.indexOf(this.entryPrefix) + this.entryPrefix.length
5691
5784
  );
5692
5785
  try {
5693
5786
  const value = await this.get(key);
@@ -5709,7 +5802,7 @@ var CacheStorageRepository = class {
5709
5802
  try {
5710
5803
  const cache = await caches.open(this.cacheName);
5711
5804
  const keys = await cache.keys();
5712
- const size = keys.filter((r) => r.url.includes(CACHE_PREFIX)).length;
5805
+ const size = keys.filter((r) => r.url.includes(this.entryPrefix)).length;
5713
5806
  return { size, cacheName: this.cacheName };
5714
5807
  } catch {
5715
5808
  return { size: 0, cacheName: this.cacheName };
@@ -5721,7 +5814,7 @@ var CacheStorageRepository = class {
5721
5814
  var DEFAULT_API_BASE_URL = "https://app.lionrapid.com";
5722
5815
 
5723
5816
  // src/index.ts
5724
- var VERSION = "0.6.6";
5817
+ var VERSION = "0.7.0";
5725
5818
  export {
5726
5819
  CORE_CACHE_ENTRY_PREFIX,
5727
5820
  CORE_CACHE_NAMES,