@lionrapid/core 0.6.0 → 0.6.2

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 (46) hide show
  1. package/dist/browser/auto-init.d.ts +17 -3
  2. package/dist/browser/auto-init.d.ts.map +1 -1
  3. package/dist/browser/auto-init.js +57 -30
  4. package/dist/browser/auto-init.js.map +1 -1
  5. package/dist/browser/lionrapid.auto.dev.js +285 -27
  6. package/dist/browser/lionrapid.auto.dev.js.map +2 -2
  7. package/dist/browser/lionrapid.auto.js +285 -27
  8. package/dist/browser/lionrapid.auto.js.map +2 -2
  9. package/dist/browser/lionrapid.auto.min.js +4 -4
  10. package/dist/browser/lionrapid.auto.min.js.map +3 -3
  11. package/dist/browser/lionrapid.esm.js +244 -2
  12. package/dist/browser/lionrapid.esm.js.map +2 -2
  13. package/dist/browser/lionrapid.esm.min.js +3 -3
  14. package/dist/browser/lionrapid.esm.min.js.map +3 -3
  15. package/dist/browser/lionrapid.iife.dev.js +244 -2
  16. package/dist/browser/lionrapid.iife.dev.js.map +2 -2
  17. package/dist/browser/lionrapid.iife.js +244 -2
  18. package/dist/browser/lionrapid.iife.js.map +2 -2
  19. package/dist/browser/lionrapid.iife.min.js +3 -3
  20. package/dist/browser/lionrapid.iife.min.js.map +3 -3
  21. package/dist/browser/lionrapid.umd.js +244 -2
  22. package/dist/browser/lionrapid.umd.js.map +2 -2
  23. package/dist/browser/lionrapid.umd.min.js +3 -3
  24. package/dist/browser/lionrapid.umd.min.js.map +3 -3
  25. package/dist/browser/storage/cache-storage-repository.d.ts +65 -0
  26. package/dist/browser/storage/cache-storage-repository.d.ts.map +1 -1
  27. package/dist/browser/storage/cache-storage-repository.js +77 -7
  28. package/dist/browser/storage/cache-storage-repository.js.map +1 -1
  29. package/dist/browser/storage/localstorage-repository.d.ts +62 -0
  30. package/dist/browser/storage/localstorage-repository.d.ts.map +1 -1
  31. package/dist/browser/storage/localstorage-repository.js +71 -4
  32. package/dist/browser/storage/localstorage-repository.js.map +1 -1
  33. package/dist/config/repository.config.d.ts +17 -0
  34. package/dist/config/repository.config.d.ts.map +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/lionRapid.d.ts +1 -0
  39. package/dist/lionRapid.d.ts.map +1 -1
  40. package/dist/lionRapid.js +14 -0
  41. package/dist/lionRapid.js.map +1 -1
  42. package/dist/repositories/network-repository.d.ts +42 -1
  43. package/dist/repositories/network-repository.d.ts.map +1 -1
  44. package/dist/repositories/network-repository.js +149 -1
  45. package/dist/repositories/network-repository.js.map +1 -1
  46. package/package.json +1 -1
@@ -2766,6 +2766,7 @@ var __lionrapidAutoExports = (() => {
2766
2766
  // 🔥 EMBEDDED: Core integration maps
2767
2767
  __publicField(this, "formatters", /* @__PURE__ */ new Map());
2768
2768
  __publicField(this, "processors", /* @__PURE__ */ new Map());
2769
+ __publicField(this, "warnedNoInterpolate", false);
2769
2770
  // 📡 EVENT-DRIVEN: Just track for cleanup
2770
2771
  __publicField(this, "eventPlugins", /* @__PURE__ */ new Set());
2771
2772
  // 📦 CONTENT: Parser and serializer for ContentUnit processing
@@ -3058,6 +3059,11 @@ var __lionrapidAutoExports = (() => {
3058
3059
  resolvedLocale,
3059
3060
  resolvedKey
3060
3061
  );
3062
+ } else if (!this.warnedNoInterpolate && params && Object.keys(params).length > 0 && processedValue.includes("{")) {
3063
+ this.warnedNoInterpolate = true;
3064
+ console.warn(
3065
+ "[LionRapid] Translation params were passed but no interpolate formatter is registered \u2014 {placeholders} render literally. Install @lionrapid/formatters and add .use(coreFormattersPlugin) to the builder."
3066
+ );
3061
3067
  }
3062
3068
  if (this.processors.size > 0) {
3063
3069
  for (const [name, processor] of this.processors.entries()) {
@@ -3838,6 +3844,31 @@ var __lionrapidAutoExports = (() => {
3838
3844
  * and payload can never desync because they live and die together.
3839
3845
  */
3840
3846
  __publicField(this, "namespaceSnapshots", /* @__PURE__ */ new Map());
3847
+ /**
3848
+ * Miss-path flood protection (READINESS before-scale #1: one cold locale
3849
+ * switch measured ~380 requests in 10 s — every t() miss refired a key GET
3850
+ * and a namespace GET on every re-render, forever on animated pages).
3851
+ */
3852
+ /** GETs currently on the wire, keyed by `url|if-none-match` — identical
3853
+ * concurrent requests share one fetch; each awaiter gets a clone. The
3854
+ * If-None-Match value is part of the key so the 304-without-snapshot
3855
+ * unconditional refetch never coalesces onto a conditional flight. */
3856
+ __publicField(this, "inflightGets", /* @__PURE__ */ new Map());
3857
+ /** URL → 404/423 seen recently: repeats are answered locally (same typed
3858
+ * error, no network) until `until`. 423 honors the server's Retry-After.
3859
+ * Cleared entirely by any successful write — discovery sync keeps its
3860
+ * miss → batch → served-back round-trip. */
3861
+ __publicField(this, "negativeCache", /* @__PURE__ */ new Map());
3862
+ /** One-way latch: a 401 anywhere (or a 403 on a READ) means the key is
3863
+ * wrong or unauthorized for this project, and retrying cannot heal it —
3864
+ * after the first one nothing touches the network again for this
3865
+ * instance (a page reload starts fresh). Holds the status that tripped
3866
+ * it so short-circuits answer honestly. */
3867
+ __publicField(this, "authFailedStatus", null);
3868
+ /** Write-only latch (auth-byok Phase 18): a 403 (scope) or 429 (quota) on
3869
+ * a WRITE degrades this instance to read-only — writes stop, silently
3870
+ * and without retries, while reads keep serving the page. */
3871
+ __publicField(this, "writesFailedStatus", null);
3841
3872
  /**
3842
3873
  * Default endpoint templates with placeholder support
3843
3874
  * Placeholders: :key, :locale, :namespace
@@ -3845,6 +3876,7 @@ var __lionrapidAutoExports = (() => {
3845
3876
  __publicField(this, "DEFAULT_ENDPOINTS", {
3846
3877
  getTranslation: "/api/integrations/translations/:key",
3847
3878
  getNamespace: "/api/integrations/translations/:locale/:namespace",
3879
+ getProjectConfig: "/api/integrations/project-config",
3848
3880
  setTranslation: "/api/integrations/sync/:key",
3849
3881
  setNamespace: "/api/integrations/sync/:locale/:namespace",
3850
3882
  setBatch: "/api/integrations/sync/batch",
@@ -3956,6 +3988,40 @@ var __lionrapidAutoExports = (() => {
3956
3988
  this.handleCaughtError(error);
3957
3989
  }
3958
3990
  }
3991
+ /**
3992
+ * Project-level config for this key — the dashboard's language list,
3993
+ * served under the same trust model as translation reads (docs/20,
3994
+ * companion track). Returns null when the server doesn't expose the
3995
+ * endpoint (404 — older server, feature not deployed) or the payload is
3996
+ * unusable: absence means "keep your local fallback", never an error.
3997
+ */
3998
+ async getProjectConfig() {
3999
+ if (!this.config?.options?.baseUrl) {
4000
+ throw new NetworkError("API base URL is not set");
4001
+ }
4002
+ const url = this.buildUrl("getProjectConfig", {});
4003
+ logger.debug(`API GET ProjectConfig: ${url}`);
4004
+ try {
4005
+ const response = await this.makeRequest(url, "GET");
4006
+ if (response.status === 404) {
4007
+ return null;
4008
+ }
4009
+ if (!response.ok) {
4010
+ this.handleHttpError(response, "project-config");
4011
+ }
4012
+ const data = await response.json();
4013
+ if (!data || typeof data.defaultLocale !== "string" || !Array.isArray(data.languages)) {
4014
+ logger.warn("project-config payload unusable \u2014 keeping local fallback");
4015
+ return null;
4016
+ }
4017
+ return {
4018
+ defaultLocale: data.defaultLocale,
4019
+ languages: data.languages.map(String)
4020
+ };
4021
+ } catch (error) {
4022
+ this.handleCaughtError(error);
4023
+ }
4024
+ }
3959
4025
  /**
3960
4026
  * Shared namespace fetch with the canonical 304 contract:
3961
4027
  * - If-None-Match is sent ONLY when a snapshot (version + payload) is in hand.
@@ -4280,7 +4346,87 @@ var __lionrapidAutoExports = (() => {
4280
4346
  /**
4281
4347
  * Make HTTP request with timeout and headers
4282
4348
  */
4283
- async makeRequest(url, method, body, extraHeaders) {
4349
+ makeRequest(url, method, body, extraHeaders) {
4350
+ if (this.authFailedStatus !== null) {
4351
+ return Promise.resolve(
4352
+ new Response(null, {
4353
+ status: this.authFailedStatus,
4354
+ statusText: `auth latched off after ${this.authFailedStatus}`
4355
+ })
4356
+ );
4357
+ }
4358
+ if (method !== "GET") {
4359
+ if (this.writesFailedStatus !== null) {
4360
+ return Promise.resolve(
4361
+ new Response(null, {
4362
+ status: this.writesFailedStatus,
4363
+ statusText: `writes latched off after ${this.writesFailedStatus}`
4364
+ })
4365
+ );
4366
+ }
4367
+ return this.doFetch(url, method, body, extraHeaders).then(
4368
+ (resp) => this.recordOutcome(url, method, resp)
4369
+ );
4370
+ }
4371
+ const negative = this.negativeCache.get(url);
4372
+ if (negative) {
4373
+ if (Date.now() < negative.until) {
4374
+ logger.debug(`Negative-cache hit (${negative.status}): ${url}`);
4375
+ const headers = negative.status === 423 ? { "Retry-After": String(Math.ceil((negative.until - Date.now()) / 1e3)) } : void 0;
4376
+ return Promise.resolve(
4377
+ new Response(null, { status: negative.status, statusText: "negative cache", headers })
4378
+ );
4379
+ }
4380
+ this.negativeCache.delete(url);
4381
+ }
4382
+ const flightKey = `${url}|${extraHeaders?.["If-None-Match"] ?? ""}`;
4383
+ const existing = this.inflightGets.get(flightKey);
4384
+ if (existing) {
4385
+ return existing.then((resp) => this.cloneResponse(resp));
4386
+ }
4387
+ const flight = this.doFetch(url, method, body, extraHeaders).then((resp) => this.recordOutcome(url, method, resp)).finally(() => this.inflightGets.delete(flightKey));
4388
+ this.inflightGets.set(flightKey, flight);
4389
+ return flight.then((resp) => this.cloneResponse(resp));
4390
+ }
4391
+ /** Bookkeeping on every real (non-synthesized) response — runs once per
4392
+ * physical fetch, never once per coalesced awaiter. */
4393
+ recordOutcome(url, method, response) {
4394
+ const status = response.status;
4395
+ if (status === 401 || status === 403 && method === "GET") {
4396
+ if (this.authFailedStatus === null) {
4397
+ this.authFailedStatus = status;
4398
+ console.warn(
4399
+ `[LionRapid] API key rejected (${status}) \u2014 network translations are disabled for this session. Fix the key and reload; retrying cannot heal an auth failure.`
4400
+ );
4401
+ }
4402
+ return response;
4403
+ }
4404
+ if ((status === 403 || status === 429) && method !== "GET") {
4405
+ if (this.writesFailedStatus === null) {
4406
+ this.writesFailedStatus = status;
4407
+ logger.warn(
4408
+ `Write path disabled after ${status} (scope/quota) \u2014 running read-only`
4409
+ );
4410
+ }
4411
+ return response;
4412
+ }
4413
+ if (method === "GET" && (status === 404 || status === 423)) {
4414
+ const retryAfterSec = status === 423 ? Number(response.headers?.get?.("Retry-After")) : NaN;
4415
+ const ttl = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? retryAfterSec * 1e3 : this.config.options?.negativeCacheTtlMs ?? _NetworkRepository.DEFAULT_NEGATIVE_TTL_MS;
4416
+ this.negativeCache.set(url, { status, until: Date.now() + ttl });
4417
+ } else if (method !== "GET" && response.ok) {
4418
+ this.negativeCache.clear();
4419
+ } else if (method === "GET" && response.ok) {
4420
+ this.negativeCache.delete(url);
4421
+ }
4422
+ return response;
4423
+ }
4424
+ /** Test doubles (and exotic runtimes) hand us plain objects — only real
4425
+ * Responses need cloning so each coalesced awaiter can read the body. */
4426
+ cloneResponse(response) {
4427
+ return typeof response.clone === "function" ? response.clone() : response;
4428
+ }
4429
+ async doFetch(url, method, body, extraHeaders) {
4284
4430
  const controller = new AbortController();
4285
4431
  const timeout = this.config.options?.timeout ?? 1e4;
4286
4432
  const timeoutId = setTimeout(
@@ -4313,6 +4459,7 @@ var __lionrapidAutoExports = (() => {
4313
4459
  /** Legacy bare-version prefix — a version WITHOUT its payload is exactly the
4314
4460
  * desync the snapshot store exists to prevent; entries are deleted on sight. */
4315
4461
  __publicField(_NetworkRepository, "LEGACY_VERSION_PREFIX", "lionrapid:v:");
4462
+ __publicField(_NetworkRepository, "DEFAULT_NEGATIVE_TTL_MS", 6e4);
4316
4463
  var NetworkRepository = _NetworkRepository;
4317
4464
 
4318
4465
  // src/browser/exclude-filter.ts
@@ -5555,9 +5702,11 @@ var __lionrapidAutoExports = (() => {
5555
5702
  this.config = {
5556
5703
  enabled: config.enabled ?? true,
5557
5704
  priority: config.priority ?? 0,
5705
+ // Use build sequence by default
5558
5706
  options: {
5559
5707
  prefix: config.options?.prefix ?? STORAGE_PREFIX,
5560
5708
  ttl: config.options?.ttl ?? 24 * 60 * 60 * 1e3,
5709
+ // 24 hours default
5561
5710
  maxSize: config.options?.maxSize ?? 500,
5562
5711
  ...config.options
5563
5712
  }
@@ -5570,6 +5719,10 @@ var __lionrapidAutoExports = (() => {
5570
5719
  this.migrateIfNeeded();
5571
5720
  }
5572
5721
  }
5722
+ /**
5723
+ * Check if localStorage is available
5724
+ * Returns false in SSR, private browsing, or when quota is 0
5725
+ */
5573
5726
  checkAvailability() {
5574
5727
  if (typeof window === "undefined") {
5575
5728
  return false;
@@ -5583,6 +5736,9 @@ var __lionrapidAutoExports = (() => {
5583
5736
  return false;
5584
5737
  }
5585
5738
  }
5739
+ /**
5740
+ * Handle version migrations - clear cache when version changes
5741
+ */
5586
5742
  migrateIfNeeded() {
5587
5743
  try {
5588
5744
  const storedVersion = window.localStorage.getItem(VERSION_KEY);
@@ -5593,6 +5749,10 @@ var __lionrapidAutoExports = (() => {
5593
5749
  } catch {
5594
5750
  }
5595
5751
  }
5752
+ /**
5753
+ * Get a translation value by key
5754
+ * Returns null if not found or expired
5755
+ */
5596
5756
  get(key) {
5597
5757
  if (!this.available) return null;
5598
5758
  try {
@@ -5609,6 +5769,10 @@ var __lionrapidAutoExports = (() => {
5609
5769
  return null;
5610
5770
  }
5611
5771
  }
5772
+ /**
5773
+ * Set a translation value
5774
+ * Automatically handles quota exceeded by evicting oldest entries
5775
+ */
5612
5776
  set(key, value) {
5613
5777
  if (!this.available) return;
5614
5778
  const fullKey = this.prefix + key;
@@ -5633,6 +5797,9 @@ var __lionrapidAutoExports = (() => {
5633
5797
  }
5634
5798
  }
5635
5799
  }
5800
+ /**
5801
+ * Remove a translation by key
5802
+ */
5636
5803
  remove(key) {
5637
5804
  if (!this.available) return;
5638
5805
  try {
@@ -5640,6 +5807,10 @@ var __lionrapidAutoExports = (() => {
5640
5807
  } catch {
5641
5808
  }
5642
5809
  }
5810
+ /**
5811
+ * Clear all LionRapid entries from localStorage
5812
+ * Preserves entries from other applications
5813
+ */
5643
5814
  clear() {
5644
5815
  if (!this.available) return;
5645
5816
  try {
@@ -5654,6 +5825,9 @@ var __lionrapidAutoExports = (() => {
5654
5825
  } catch {
5655
5826
  }
5656
5827
  }
5828
+ /**
5829
+ * Clear all entries for a specific namespace
5830
+ */
5657
5831
  clearNamespace(locale, namespace) {
5658
5832
  if (!this.available) return;
5659
5833
  const namespaceCacheKey = `@${locale}:${namespace}`;
@@ -5672,6 +5846,9 @@ var __lionrapidAutoExports = (() => {
5672
5846
  } catch {
5673
5847
  }
5674
5848
  }
5849
+ /**
5850
+ * Get namespace translations
5851
+ */
5675
5852
  getNamespace(locale, namespace) {
5676
5853
  const namespaceCacheKey = `@${locale}:${namespace}`;
5677
5854
  const value = this.get(namespaceCacheKey);
@@ -5683,10 +5860,16 @@ var __lionrapidAutoExports = (() => {
5683
5860
  return null;
5684
5861
  }
5685
5862
  }
5863
+ /**
5864
+ * Set namespace translations
5865
+ */
5686
5866
  setNamespace(locale, namespace, translations) {
5687
5867
  const namespaceCacheKey = `@${locale}:${namespace}`;
5688
5868
  this.set(namespaceCacheKey, JSON.stringify(translations));
5689
5869
  }
5870
+ /**
5871
+ * Get all cache entries for debugging
5872
+ */
5690
5873
  getEntries() {
5691
5874
  const entries = {};
5692
5875
  if (!this.available) return entries;
@@ -5706,12 +5889,21 @@ var __lionrapidAutoExports = (() => {
5706
5889
  }
5707
5890
  return entries;
5708
5891
  }
5892
+ /**
5893
+ * Get repository configuration
5894
+ */
5709
5895
  getConfig() {
5710
5896
  return this.config;
5711
5897
  }
5898
+ /**
5899
+ * Check if localStorage is available
5900
+ */
5712
5901
  isAvailable() {
5713
5902
  return this.available;
5714
5903
  }
5904
+ /**
5905
+ * Get the number of entries in the cache
5906
+ */
5715
5907
  getEntryCount() {
5716
5908
  if (!this.available) return 0;
5717
5909
  let count = 0;
@@ -5726,12 +5918,18 @@ var __lionrapidAutoExports = (() => {
5726
5918
  }
5727
5919
  return count;
5728
5920
  }
5921
+ /**
5922
+ * Check if error is a quota exceeded error
5923
+ */
5729
5924
  isQuotaError(error) {
5730
5925
  if (error instanceof DOMException) {
5731
5926
  return error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED";
5732
5927
  }
5733
5928
  return false;
5734
5929
  }
5930
+ /**
5931
+ * Evict oldest 25% of entries when quota is exceeded
5932
+ */
5735
5933
  evictOldest() {
5736
5934
  if (!this.available) return;
5737
5935
  try {
@@ -5759,6 +5957,9 @@ var __lionrapidAutoExports = (() => {
5759
5957
  } catch {
5760
5958
  }
5761
5959
  }
5960
+ /**
5961
+ * Get cache statistics
5962
+ */
5762
5963
  getCacheStats() {
5763
5964
  const size = this.getEntryCount();
5764
5965
  const usage = this.maxSize > 0 ? `${Math.round(size / this.maxSize * 100)}%` : "0%";
@@ -5772,7 +5973,7 @@ var __lionrapidAutoExports = (() => {
5772
5973
 
5773
5974
  // src/browser/storage/cache-storage-repository.ts
5774
5975
  var CACHE_NAME = "lionrapid-v1";
5775
- var CACHE_PREFIX = "lionrapid:";
5976
+ var CACHE_PREFIX = "https://cache.lionrapid.internal/";
5776
5977
  var CacheStorageRepository = class {
5777
5978
  constructor(config = {}) {
5778
5979
  __publicField(this, "name", "CacheStorageRepository");
@@ -5787,6 +5988,7 @@ var __lionrapidAutoExports = (() => {
5787
5988
  options: {
5788
5989
  cacheName: config.options?.cacheName ?? CACHE_NAME,
5789
5990
  ttl: config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3,
5991
+ // 7 days default
5790
5992
  ...config.options
5791
5993
  }
5792
5994
  };
@@ -5794,9 +5996,17 @@ var __lionrapidAutoExports = (() => {
5794
5996
  this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
5795
5997
  this.available = this.checkAvailability();
5796
5998
  }
5999
+ /**
6000
+ * Check if Cache API is available
6001
+ * Returns false in SSR, old browsers, or insecure contexts
6002
+ */
5797
6003
  checkAvailability() {
5798
6004
  return typeof caches !== "undefined";
5799
6005
  }
6006
+ /**
6007
+ * Get a translation value by key
6008
+ * @throws {TranslationNotFoundError} When translation doesn't exist or is expired
6009
+ */
5800
6010
  async get(key) {
5801
6011
  if (!this.available) {
5802
6012
  throw new TranslationNotFoundError(key);
@@ -5820,6 +6030,9 @@ var __lionrapidAutoExports = (() => {
5820
6030
  throw new TranslationNotFoundError(key);
5821
6031
  }
5822
6032
  }
6033
+ /**
6034
+ * Set a translation value
6035
+ */
5823
6036
  async set(key, value) {
5824
6037
  if (!this.available) return;
5825
6038
  try {
@@ -5838,6 +6051,9 @@ var __lionrapidAutoExports = (() => {
5838
6051
  } catch {
5839
6052
  }
5840
6053
  }
6054
+ /**
6055
+ * Remove a translation by key
6056
+ */
5841
6057
  async remove(key) {
5842
6058
  if (!this.available) return;
5843
6059
  try {
@@ -5846,6 +6062,9 @@ var __lionrapidAutoExports = (() => {
5846
6062
  } catch {
5847
6063
  }
5848
6064
  }
6065
+ /**
6066
+ * Clear all LionRapid entries from the cache
6067
+ */
5849
6068
  async clear() {
5850
6069
  if (!this.available) return;
5851
6070
  try {
@@ -5857,6 +6076,9 @@ var __lionrapidAutoExports = (() => {
5857
6076
  } catch {
5858
6077
  }
5859
6078
  }
6079
+ /**
6080
+ * Clear all entries for a specific namespace
6081
+ */
5860
6082
  async clearNamespace(locale, namespace) {
5861
6083
  if (!this.available) return;
5862
6084
  const namespaceCacheKey = `@${locale}:${namespace}`;
@@ -5874,6 +6096,10 @@ var __lionrapidAutoExports = (() => {
5874
6096
  } catch {
5875
6097
  }
5876
6098
  }
6099
+ /**
6100
+ * Get namespace translations
6101
+ * @throws {TranslationNotFoundError} When namespace doesn't exist
6102
+ */
5877
6103
  async getNamespace(locale, namespace) {
5878
6104
  const namespaceCacheKey = `@${locale}:${namespace}`;
5879
6105
  const value = await this.get(namespaceCacheKey);
@@ -5884,16 +6110,29 @@ var __lionrapidAutoExports = (() => {
5884
6110
  throw new TranslationNotFoundError(namespaceCacheKey);
5885
6111
  }
5886
6112
  }
6113
+ /**
6114
+ * Set namespace translations
6115
+ */
5887
6116
  async setNamespace(locale, namespace, translations) {
5888
6117
  const namespaceCacheKey = `@${locale}:${namespace}`;
5889
6118
  await this.set(namespaceCacheKey, JSON.stringify(translations));
5890
6119
  }
6120
+ /**
6121
+ * Get repository configuration
6122
+ */
5891
6123
  getConfig() {
5892
6124
  return this.config;
5893
6125
  }
6126
+ /**
6127
+ * Check if Cache API is available
6128
+ */
5894
6129
  isAvailable() {
5895
6130
  return this.available;
5896
6131
  }
6132
+ /**
6133
+ * Get all cached entries (for debugging)
6134
+ * Returns an object mapping keys to values
6135
+ */
5897
6136
  async getEntries() {
5898
6137
  const entries = {};
5899
6138
  if (!this.available) return entries;
@@ -5916,6 +6155,9 @@ var __lionrapidAutoExports = (() => {
5916
6155
  }
5917
6156
  return entries;
5918
6157
  }
6158
+ /**
6159
+ * Get cache statistics
6160
+ */
5919
6161
  async getCacheStats() {
5920
6162
  if (!this.available) {
5921
6163
  return { size: 0, cacheName: this.cacheName };
@@ -5935,8 +6177,9 @@ var __lionrapidAutoExports = (() => {
5935
6177
  function resolveWritePolicy(apiKey) {
5936
6178
  const isPublishable = apiKey.startsWith("pk_");
5937
6179
  return {
6180
+ bootNetwork: isPublishable,
5938
6181
  syncMissing: { enabled: isPublishable },
5939
- warnReadOnly: !isPublishable
6182
+ warnRefused: !isPublishable
5940
6183
  };
5941
6184
  }
5942
6185
  function configForWindow(config) {
@@ -6013,7 +6256,7 @@ var __lionrapidAutoExports = (() => {
6013
6256
  function readLocaleCookie(availableLanguages) {
6014
6257
  if (typeof document === "undefined") return null;
6015
6258
  const match = document.cookie.match(
6016
- /lionrapid_locale=([a-z]{2,3}(?:-[a-z0-9]{2,4})*)/i
6259
+ /(?:^|;\s*)lionrapid_locale=([a-z]{2,3}(?:-[a-z0-9]{2,4})*)/i
6017
6260
  );
6018
6261
  if (!match) return null;
6019
6262
  return matchLocale([match[1]], availableLanguages, "") || null;
@@ -6059,9 +6302,21 @@ var __lionrapidAutoExports = (() => {
6059
6302
  document.documentElement.dir = localeDir(locale);
6060
6303
  }
6061
6304
  }
6062
- async function autoInit() {
6305
+ function findConfigScript() {
6306
+ const current = document.currentScript;
6307
+ if (current instanceof HTMLScriptElement && current.dataset.apiKey) {
6308
+ return current;
6309
+ }
6063
6310
  const scripts = document.querySelectorAll("script[data-api-key]");
6064
- const script = scripts[scripts.length - 1];
6311
+ if (scripts.length > 1) {
6312
+ console.warn(
6313
+ `[LionRapid] ${scripts.length} script tags with data-api-key found; using the last one`
6314
+ );
6315
+ }
6316
+ return scripts[scripts.length - 1];
6317
+ }
6318
+ async function autoInit() {
6319
+ const script = findConfigScript();
6065
6320
  if (!script) {
6066
6321
  console.warn("[LionRapid] No script tag with data-api-key found");
6067
6322
  return;
@@ -6073,9 +6328,9 @@ var __lionrapidAutoExports = (() => {
6073
6328
  return;
6074
6329
  }
6075
6330
  const writePolicy = resolveWritePolicy(config.apiKey);
6076
- if (writePolicy.warnReadOnly) {
6331
+ if (writePolicy.warnRefused) {
6077
6332
  console.error(
6078
- "[LionRapid] data-api-key must be a publishable pk_ key; running read-only"
6333
+ "[LionRapid] data-api-key must be a publishable pk_ key \u2014 network translations are disabled. Copy the snippet from the integration hub to get the pk_ key for this project."
6079
6334
  );
6080
6335
  }
6081
6336
  const initialLocale = resolveInitialLocale(config);
@@ -6129,25 +6384,28 @@ var __lionrapidAutoExports = (() => {
6129
6384
  })
6130
6385
  );
6131
6386
  }
6132
- const core = await builder.use(
6133
- new NetworkRepository({
6134
- enabled: true,
6135
- options: {
6136
- baseUrl: config.baseUrl,
6137
- apiKey: config.apiKey,
6138
- // pk_ keys sync missing source strings (discovery); anything else
6139
- // runs read-only — see resolveWritePolicy.
6140
- syncMissing: writePolicy.syncMissing,
6141
- preloadNamespaces: [
6142
- {
6143
- locale: initialLocale,
6144
- namespace: config.namespace,
6145
- eager: true
6146
- }
6147
- ]
6148
- }
6149
- })
6150
- ).build();
6387
+ if (writePolicy.bootNetwork) {
6388
+ builder.use(
6389
+ new NetworkRepository({
6390
+ enabled: true,
6391
+ options: {
6392
+ baseUrl: config.baseUrl,
6393
+ apiKey: config.apiKey,
6394
+ // pk_ keys sync missing source strings (discovery) — see
6395
+ // resolveWritePolicy.
6396
+ syncMissing: writePolicy.syncMissing,
6397
+ preloadNamespaces: [
6398
+ {
6399
+ locale: initialLocale,
6400
+ namespace: config.namespace,
6401
+ eager: true
6402
+ }
6403
+ ]
6404
+ }
6405
+ })
6406
+ );
6407
+ }
6408
+ const core = await builder.build();
6151
6409
  const autoTranslator = new AutoTranslator({
6152
6410
  excludeSelectors: config.excludeSelectors
6153
6411
  });