@lionrapid/core 0.6.5 → 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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -2
  3. package/dist/browser/auto-init.d.ts +6 -0
  4. package/dist/browser/auto-init.d.ts.map +1 -1
  5. package/dist/browser/auto-init.js +38 -7
  6. package/dist/browser/auto-init.js.map +1 -1
  7. package/dist/browser/editor/editor-api.d.ts +12 -0
  8. package/dist/browser/editor/editor-api.d.ts.map +1 -1
  9. package/dist/browser/editor/editor-api.js.map +1 -1
  10. package/dist/browser/editor/editor-init.js +2 -1
  11. package/dist/browser/editor/editor-init.js.map +1 -1
  12. package/dist/browser/editor/sidebar.d.ts.map +1 -1
  13. package/dist/browser/editor/sidebar.js +11 -1
  14. package/dist/browser/editor/sidebar.js.map +1 -1
  15. package/dist/browser/language-switcher.d.ts +12 -0
  16. package/dist/browser/language-switcher.d.ts.map +1 -1
  17. package/dist/browser/language-switcher.js +58 -0
  18. package/dist/browser/language-switcher.js.map +1 -1
  19. package/dist/browser/lionrapid.auto.dev.js +404 -184
  20. package/dist/browser/lionrapid.auto.dev.js.map +3 -3
  21. package/dist/browser/lionrapid.auto.js +404 -184
  22. package/dist/browser/lionrapid.auto.js.map +3 -3
  23. package/dist/browser/lionrapid.auto.min.js +6 -4
  24. package/dist/browser/lionrapid.auto.min.js.map +4 -4
  25. package/dist/browser/lionrapid.editor.js +5 -2
  26. package/dist/browser/lionrapid.editor.js.map +3 -3
  27. package/dist/browser/lionrapid.editor.min.js +4 -4
  28. package/dist/browser/lionrapid.editor.min.js.map +4 -4
  29. package/dist/browser/lionrapid.esm.js +344 -179
  30. package/dist/browser/lionrapid.esm.js.map +3 -3
  31. package/dist/browser/lionrapid.esm.min.js +5 -3
  32. package/dist/browser/lionrapid.esm.min.js.map +4 -4
  33. package/dist/browser/lionrapid.iife.dev.js +344 -179
  34. package/dist/browser/lionrapid.iife.dev.js.map +3 -3
  35. package/dist/browser/lionrapid.iife.js +344 -179
  36. package/dist/browser/lionrapid.iife.js.map +3 -3
  37. package/dist/browser/lionrapid.iife.min.js +5 -3
  38. package/dist/browser/lionrapid.iife.min.js.map +4 -4
  39. package/dist/browser/lionrapid.umd.js +344 -179
  40. package/dist/browser/lionrapid.umd.js.map +3 -3
  41. package/dist/browser/lionrapid.umd.min.js +5 -3
  42. package/dist/browser/lionrapid.umd.min.js.map +4 -4
  43. package/dist/browser/storage/cache-storage-repository.d.ts +9 -0
  44. package/dist/browser/storage/cache-storage-repository.d.ts.map +1 -1
  45. package/dist/browser/storage/cache-storage-repository.js +14 -10
  46. package/dist/browser/storage/cache-storage-repository.js.map +1 -1
  47. package/dist/browser/storage/scope.d.ts +12 -0
  48. package/dist/browser/storage/scope.d.ts.map +1 -0
  49. package/dist/browser/storage/scope.js +15 -0
  50. package/dist/browser/storage/scope.js.map +1 -0
  51. package/dist/builder.d.ts.map +1 -1
  52. package/dist/builder.js +18 -2
  53. package/dist/builder.js.map +1 -1
  54. package/dist/config/api.config.d.ts +35 -0
  55. package/dist/config/api.config.d.ts.map +1 -0
  56. package/dist/config/api.config.js +35 -0
  57. package/dist/config/api.config.js.map +1 -0
  58. package/dist/handlers/async-storage-handler.d.ts +9 -0
  59. package/dist/handlers/async-storage-handler.d.ts.map +1 -1
  60. package/dist/handlers/async-storage-handler.js +42 -0
  61. package/dist/handlers/async-storage-handler.js.map +1 -1
  62. package/dist/handlers/handler-chain.d.ts +2 -0
  63. package/dist/handlers/handler-chain.d.ts.map +1 -1
  64. package/dist/handlers/handler-chain.js +29 -0
  65. package/dist/handlers/handler-chain.js.map +1 -1
  66. package/dist/handlers/network-handler.d.ts +6 -0
  67. package/dist/handlers/network-handler.d.ts.map +1 -1
  68. package/dist/handlers/network-handler.js +9 -0
  69. package/dist/handlers/network-handler.js.map +1 -1
  70. package/dist/handlers/sync-storage-handler.d.ts +4 -0
  71. package/dist/handlers/sync-storage-handler.d.ts.map +1 -1
  72. package/dist/handlers/sync-storage-handler.js +35 -0
  73. package/dist/handlers/sync-storage-handler.js.map +1 -1
  74. package/dist/index.d.ts +2 -1
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +4 -1
  77. package/dist/index.js.map +1 -1
  78. package/dist/interfaces/handler.interface.d.ts +4 -0
  79. package/dist/interfaces/handler.interface.d.ts.map +1 -1
  80. package/dist/lionRapid.d.ts.map +1 -1
  81. package/dist/lionRapid.js +11 -2
  82. package/dist/lionRapid.js.map +1 -1
  83. package/dist/repositories/network-repository.d.ts.map +1 -1
  84. package/dist/repositories/network-repository.js +4 -3
  85. package/dist/repositories/network-repository.js.map +1 -1
  86. package/dist/utils/log-safe.d.ts +92 -0
  87. package/dist/utils/log-safe.d.ts.map +1 -0
  88. package/dist/utils/log-safe.js +168 -0
  89. package/dist/utils/log-safe.js.map +1 -0
  90. package/dist/utils/logger.d.ts.map +1 -1
  91. package/dist/utils/logger.js +3 -2
  92. package/dist/utils/logger.js.map +1 -1
  93. package/package.json +8 -5
@@ -29,6 +29,7 @@ var LionRapid = (() => {
29
29
  CacheStorageRepository: () => CacheStorageRepository,
30
30
  Colors: () => Colors,
31
31
  ContentValidator: () => ContentValidator,
32
+ DEFAULT_API_BASE_URL: () => DEFAULT_API_BASE_URL,
32
33
  EventEmitter: () => EventEmitter,
33
34
  ExcludeFilter: () => ExcludeFilter,
34
35
  HTMLSerializer: () => HTMLSerializer,
@@ -86,6 +87,216 @@ var LionRapid = (() => {
86
87
  splitBlocks: () => splitBlocks
87
88
  });
88
89
 
90
+ // src/browser/md5.ts
91
+ function safeAdd(x, y) {
92
+ const lsw = (x & 65535) + (y & 65535);
93
+ const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
94
+ return msw << 16 | lsw & 65535;
95
+ }
96
+ function bitRotateLeft(num, cnt) {
97
+ return num << cnt | num >>> 32 - cnt;
98
+ }
99
+ function md5cmn(q, a, b, x, s, t) {
100
+ return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
101
+ }
102
+ function md5ff(a, b, c, d, x, s, t) {
103
+ return md5cmn(b & c | ~b & d, a, b, x, s, t);
104
+ }
105
+ function md5gg(a, b, c, d, x, s, t) {
106
+ return md5cmn(b & d | c & ~d, a, b, x, s, t);
107
+ }
108
+ function md5hh(a, b, c, d, x, s, t) {
109
+ return md5cmn(b ^ c ^ d, a, b, x, s, t);
110
+ }
111
+ function md5ii(a, b, c, d, x, s, t) {
112
+ return md5cmn(c ^ (b | ~d), a, b, x, s, t);
113
+ }
114
+ function binlMD5(x, len) {
115
+ x[len >> 5] |= 128 << len % 32;
116
+ x[(len + 64 >>> 9 << 4) + 14] = len;
117
+ let a = 1732584193;
118
+ let b = -271733879;
119
+ let c = -1732584194;
120
+ let d = 271733878;
121
+ for (let i = 0; i < x.length; i += 16) {
122
+ const olda = a;
123
+ const oldb = b;
124
+ const oldc = c;
125
+ const oldd = d;
126
+ a = md5ff(a, b, c, d, x[i], 7, -680876936);
127
+ d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
128
+ c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
129
+ b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
130
+ a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
131
+ d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
132
+ c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
133
+ b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
134
+ a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
135
+ d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
136
+ c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
137
+ b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
138
+ a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
139
+ d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
140
+ c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
141
+ b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
142
+ a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
143
+ d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
144
+ c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
145
+ b = md5gg(b, c, d, a, x[i], 20, -373897302);
146
+ a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
147
+ d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
148
+ c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
149
+ b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
150
+ a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
151
+ d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
152
+ c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
153
+ b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
154
+ a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
155
+ d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
156
+ c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
157
+ b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
158
+ a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
159
+ d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
160
+ c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
161
+ b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
162
+ a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
163
+ d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
164
+ c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
165
+ b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
166
+ a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
167
+ d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
168
+ c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
169
+ b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
170
+ a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
171
+ d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
172
+ c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
173
+ b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
174
+ a = md5ii(a, b, c, d, x[i], 6, -198630844);
175
+ d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
176
+ c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
177
+ b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
178
+ a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
179
+ d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
180
+ c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
181
+ b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
182
+ a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
183
+ d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
184
+ c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
185
+ b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
186
+ a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
187
+ d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
188
+ c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
189
+ b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
190
+ a = safeAdd(a, olda);
191
+ b = safeAdd(b, oldb);
192
+ c = safeAdd(c, oldc);
193
+ d = safeAdd(d, oldd);
194
+ }
195
+ return [a, b, c, d];
196
+ }
197
+ function binl2hex(binarray) {
198
+ const hexTab = "0123456789abcdef";
199
+ let str = "";
200
+ for (let i = 0; i < binarray.length * 4; i++) {
201
+ str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
202
+ }
203
+ return str;
204
+ }
205
+ function utf8Bytes(str) {
206
+ if (typeof TextEncoder !== "undefined") {
207
+ return new TextEncoder().encode(str);
208
+ }
209
+ const out = [];
210
+ for (let i = 0; i < str.length; i++) {
211
+ const c = str.charCodeAt(i);
212
+ if (c < 128) {
213
+ out.push(c);
214
+ } else if (c < 2048) {
215
+ out.push(192 | c >> 6, 128 | c & 63);
216
+ } else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
217
+ const c2 = str.charCodeAt(++i);
218
+ const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
219
+ out.push(
220
+ 240 | cp >> 18,
221
+ 128 | cp >> 12 & 63,
222
+ 128 | cp >> 6 & 63,
223
+ 128 | cp & 63
224
+ );
225
+ } else {
226
+ out.push(
227
+ 224 | c >> 12,
228
+ 128 | c >> 6 & 63,
229
+ 128 | c & 63
230
+ );
231
+ }
232
+ }
233
+ return new Uint8Array(out);
234
+ }
235
+ function bytes2binl(bytes) {
236
+ const bin = [];
237
+ for (let i = 0; i < bytes.length; i++) {
238
+ bin[i >> 2] |= bytes[i] << i % 4 * 8;
239
+ }
240
+ return bin;
241
+ }
242
+ function md5(str) {
243
+ const bytes = utf8Bytes(str);
244
+ return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
245
+ }
246
+ function generateAutoKey(text) {
247
+ const trimmed = text.trim();
248
+ return "auto_" + md5(trimmed).substring(0, 12);
249
+ }
250
+
251
+ // src/utils/log-safe.ts
252
+ var LOG_VALUE_FULL_MAX = 200;
253
+ var LOG_VALUE_EDGE = 90;
254
+ function contentIdentity(value) {
255
+ return `${value.length} chars, md5:${md5(value.trim()).slice(0, 12)}`;
256
+ }
257
+ function contentExcerpt(value) {
258
+ if (value.length <= LOG_VALUE_FULL_MAX) {
259
+ return JSON.stringify(value);
260
+ }
261
+ return JSON.stringify(
262
+ `${value.slice(0, LOG_VALUE_EDGE)}\u2026${value.slice(-LOG_VALUE_EDGE)}`
263
+ );
264
+ }
265
+ var LOG_ERROR_MAX = 200;
266
+ var LOG_DATA_MAX = 500;
267
+ var LOG_STACK_FRAMES = 2;
268
+ var LOG_FRAME_MAX = 120;
269
+ function bound(text, max) {
270
+ return text.length <= max ? text : `${text.slice(0, max)}\u2026 (${text.length} chars)`;
271
+ }
272
+ function oneLine(text) {
273
+ return JSON.stringify(text).slice(1, -1);
274
+ }
275
+ function describeError(error) {
276
+ if (error instanceof Error) {
277
+ const name = error.name || "Error";
278
+ return bound(oneLine(`${name}: ${error.message}`), LOG_ERROR_MAX);
279
+ }
280
+ return bound(oneLine(String(error)), LOG_ERROR_MAX);
281
+ }
282
+ function describeLogData(data) {
283
+ if (data instanceof Error) {
284
+ const headerLines = String(data.message ?? "").split("\n").length;
285
+ const frames = (data.stack ?? "").split("\n").slice(headerLines).map((frame) => frame.trim()).filter((frame) => frame.startsWith("at ")).slice(0, LOG_STACK_FRAMES).map((frame) => bound(frame, LOG_FRAME_MAX));
286
+ return [describeError(data), ...frames].join(" | ");
287
+ }
288
+ let rendered;
289
+ try {
290
+ rendered = JSON.stringify(data);
291
+ } catch {
292
+ return "[unserializable]";
293
+ }
294
+ return bound(rendered ?? oneLine(String(data)), LOG_DATA_MAX);
295
+ }
296
+ function describeContent(value) {
297
+ return `${contentIdentity(value)}, ${contentExcerpt(value)}`;
298
+ }
299
+
89
300
  // src/config/logger.config.ts
90
301
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
91
302
  LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
@@ -213,7 +424,7 @@ var LionRapid = (() => {
213
424
  const locationInfo2 = callSite ? ` (${callSite.fileName}:${callSite.lineNumber})` : "";
214
425
  let logMessage = `${this.config.prefix} ${emoji} ${levelName} ${message}${locationInfo2}`;
215
426
  if (data !== void 0) {
216
- logMessage += ` ${JSON.stringify(data)}`;
427
+ logMessage += ` ${describeLogData(data)}`;
217
428
  }
218
429
  return logMessage;
219
430
  }
@@ -224,7 +435,7 @@ var LionRapid = (() => {
224
435
  const coloredLevel = `${levelColor}${Colors.BOLD}${levelName}${reset}`;
225
436
  const coloredMessage = `${levelColor}${message}${reset}`;
226
437
  const locationInfo = callSite ? `${Colors.DIM}${Colors.BRIGHT_BLACK} (${callSite.fileName}:${callSite.lineNumber})${reset}` : "";
227
- const dataInfo = data !== void 0 ? `${Colors.DIM}${Colors.CYAN} ${JSON.stringify(data)}${reset}` : "";
438
+ const dataInfo = data !== void 0 ? `${Colors.DIM}${Colors.CYAN} ${describeLogData(data)}${reset}` : "";
228
439
  return `${coloredPrefix} ${coloredEmoji} ${coloredLevel} ${coloredMessage}${locationInfo}${dataInfo}`;
229
440
  }
230
441
  getLevelColor(level) {
@@ -1023,6 +1234,24 @@ var LionRapid = (() => {
1023
1234
  });
1024
1235
  await Promise.allSettled(savePromises);
1025
1236
  }
1237
+ /** Forget `locale:namespace` in every memory and storage handler (a 404). */
1238
+ async forgetNamespaceInHandlers(context) {
1239
+ for (const handler of this.syncHandlers) {
1240
+ try {
1241
+ handler.forgetNamespace?.(context);
1242
+ } catch (error) {
1243
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1244
+ }
1245
+ }
1246
+ for (const handler of this.asyncHandlers) {
1247
+ if (handler.type !== "storage" && handler.type !== "memory") continue;
1248
+ try {
1249
+ await handler.forgetNamespace?.(context);
1250
+ } catch (error) {
1251
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1252
+ }
1253
+ }
1254
+ }
1026
1255
  async saveNamespaceToHandlers(translations, context) {
1027
1256
  this.saveNamespaceToMemoryHandlers(translations, context);
1028
1257
  await this.saveNamespaceToStorageHandlers(translations, context);
@@ -1124,6 +1353,10 @@ var LionRapid = (() => {
1124
1353
  });
1125
1354
  return result.data;
1126
1355
  }
1356
+ if (result && result.status === 404) {
1357
+ await this.forgetNamespaceInHandlers({ namespace, locale });
1358
+ return null;
1359
+ }
1127
1360
  } catch (error) {
1128
1361
  logger.warn(
1129
1362
  `Failed to load namespace: ${locale}:${namespace} from ${handler.name}`,
@@ -1507,6 +1740,7 @@ var LionRapid = (() => {
1507
1740
  context.locale + ":" + context.namespace,
1508
1741
  this.name
1509
1742
  );
1743
+ const previousKeys = this.storedNamespaceKeys(context.locale, context.namespace);
1510
1744
  const namespaceCacheKey = createNamespaceCacheKey(
1511
1745
  context.locale,
1512
1746
  context.namespace
@@ -1518,11 +1752,38 @@ var LionRapid = (() => {
1518
1752
  context.locale,
1519
1753
  context.namespace
1520
1754
  );
1755
+ for (const key of previousKeys) {
1756
+ if (!(key in flattened)) this.removeQuietly(key);
1757
+ }
1521
1758
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1522
1759
  this.setWithTTL(flatKey, flatValue);
1523
1760
  }
1524
1761
  repoBus.setSuccess(JSON.stringify(translations));
1525
1762
  }
1763
+ /** Forget a namespace the server says does not exist (404). See AsyncStorageHandler. */
1764
+ forgetNamespace(context) {
1765
+ if (!context.namespace) return;
1766
+ for (const key of this.storedNamespaceKeys(context.locale, context.namespace)) {
1767
+ this.removeQuietly(key);
1768
+ }
1769
+ this.clearNamespace(context.locale, context.namespace);
1770
+ }
1771
+ storedNamespaceKeys(locale, namespace) {
1772
+ try {
1773
+ const blob = this.repository.get(createNamespaceCacheKey(locale, namespace));
1774
+ if (!blob) return [];
1775
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
1776
+ } catch {
1777
+ return [];
1778
+ }
1779
+ }
1780
+ removeQuietly(cacheKey) {
1781
+ this.ttlMap.delete(cacheKey);
1782
+ try {
1783
+ this.repository.remove(cacheKey);
1784
+ } catch {
1785
+ }
1786
+ }
1526
1787
  /**
1527
1788
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1528
1789
  * Returns parsed ContentUnit (text + styles) or null
@@ -1776,6 +2037,7 @@ var LionRapid = (() => {
1776
2037
  logger.debug(
1777
2038
  `Saving namespace: ${context.locale}:${context.namespace} (${keyCount} keys)`
1778
2039
  );
2040
+ const previousKeys = await this.storedNamespaceKeys(context.locale, context.namespace);
1779
2041
  const namespaceCacheKey = createNamespaceCacheKey(
1780
2042
  context.locale,
1781
2043
  context.namespace
@@ -1787,6 +2049,9 @@ var LionRapid = (() => {
1787
2049
  context.locale,
1788
2050
  context.namespace
1789
2051
  );
2052
+ for (const key of previousKeys) {
2053
+ if (!(key in flattened)) await this.removeQuietly(key);
2054
+ }
1790
2055
  let savedCount = 0;
1791
2056
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1792
2057
  await this.setWithTTL(flatKey, flatValue);
@@ -1795,6 +2060,35 @@ var LionRapid = (() => {
1795
2060
  logger.debug(`Saved: ${savedCount} keys + namespace blob`);
1796
2061
  repoBus.setSuccess(JSON.stringify(translations));
1797
2062
  }
2063
+ /**
2064
+ * Forget a namespace the server says does not exist (404): its blob and
2065
+ * every per-key entry the blob listed. A network failure never gets here,
2066
+ * so the offline floor still serves when the server is unreachable.
2067
+ */
2068
+ async forgetNamespace(context) {
2069
+ if (!context.namespace) return;
2070
+ for (const key of await this.storedNamespaceKeys(context.locale, context.namespace)) {
2071
+ await this.removeQuietly(key);
2072
+ }
2073
+ await this.removeQuietly(createNamespaceCacheKey(context.locale, context.namespace));
2074
+ }
2075
+ /** The per-key cache keys (`locale:namespace:key`) the stored blob lists. */
2076
+ async storedNamespaceKeys(locale, namespace) {
2077
+ try {
2078
+ const blob = await this.repository.get(createNamespaceCacheKey(locale, namespace));
2079
+ if (!blob) return [];
2080
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
2081
+ } catch {
2082
+ return [];
2083
+ }
2084
+ }
2085
+ async removeQuietly(cacheKey) {
2086
+ this.ttlMap.delete(cacheKey);
2087
+ try {
2088
+ await this.repository.remove(cacheKey);
2089
+ } catch {
2090
+ }
2091
+ }
1798
2092
  /**
1799
2093
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1800
2094
  * Returns parsed ContentUnit (text + styles) or null
@@ -2345,6 +2639,15 @@ var LionRapid = (() => {
2345
2639
  const config = this.repository.getConfig();
2346
2640
  return config.options?.preloadNamespaces || [];
2347
2641
  }
2642
+ /**
2643
+ * The host this handler talks to. Exposed so a failure can NAME it: "the
2644
+ * server is unreachable" is not actionable, and a default pointing at a dead
2645
+ * host is exactly the case where the reader needs to be told which host.
2646
+ */
2647
+ getBaseUrl() {
2648
+ const config = this.repository.getConfig();
2649
+ return config.options?.baseUrl || "";
2650
+ }
2348
2651
  };
2349
2652
 
2350
2653
  // src/content/canonical-tag.ts
@@ -3217,7 +3520,9 @@ var LionRapid = (() => {
3217
3520
  const processedText = this.processTranslationValue(unit.text, request);
3218
3521
  return processedText.replace(/<\/?[0-9]+\/?>/g, "");
3219
3522
  } catch (error) {
3220
- logger.warn(`ContentUnit processing failed for "${value}":`, error);
3523
+ logger.warn(
3524
+ `ContentUnit processing failed for key "${request.cleanKey}" \u2014 ${describeContent(value)}: ${describeError(error)}`
3525
+ );
3221
3526
  return this.processTranslationValue(value, request);
3222
3527
  }
3223
3528
  }
@@ -3270,7 +3575,9 @@ var LionRapid = (() => {
3270
3575
  }
3271
3576
  return processedValue;
3272
3577
  } catch (error) {
3273
- logger.warn(`Translation processing failed for "${value}":`, error);
3578
+ logger.warn(
3579
+ `Translation processing failed for key "${resolvedKey ?? "(no key)"}" \u2014 ${describeContent(value)}: ${describeError(error)}`
3580
+ );
3274
3581
  return value;
3275
3582
  }
3276
3583
  }
@@ -3987,8 +4294,9 @@ var LionRapid = (() => {
3987
4294
  const warnLoudlyOnce = (namespaceKey, error) => {
3988
4295
  if (!isLoudPreloadFailure(error) || warnedLoudly) return;
3989
4296
  warnedLoudly = true;
4297
+ const baseUrl = networkHandler.getBaseUrl() || "(no base URL configured)";
3990
4298
  console.warn(
3991
- `[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
4299
+ `[LionRapid] Could not load translations for ${namespaceKey} from ${baseUrl} \u2014 the page stays untranslated until that host is reachable. If it is wrong, set data-base-url on the snippet tag (or \`baseUrl\` in the network repository options).`,
3992
4300
  error
3993
4301
  );
3994
4302
  };
@@ -4008,6 +4316,12 @@ var LionRapid = (() => {
4008
4316
  logger.debug(`Preloaded: ${namespaceKey}`);
4009
4317
  return;
4010
4318
  }
4319
+ if (result?.status === 404) {
4320
+ await handlerChain.forgetNamespaceInHandlers({
4321
+ locale: preloadConfig.locale,
4322
+ namespace: preloadConfig.namespace
4323
+ });
4324
+ }
4011
4325
  if (result?.error) {
4012
4326
  warnLoudlyOnce(namespaceKey, result.error);
4013
4327
  logger.warn(`Preload failed: ${namespaceKey}`, result.error);
@@ -4116,7 +4430,7 @@ var LionRapid = (() => {
4116
4430
  throw new TranslationNotFoundError(key);
4117
4431
  }
4118
4432
  if (typeof translation === "object" && "text" in translation) {
4119
- logger.debug(`ContentUnit text: "${translation.text}"`);
4433
+ logger.debug(`ContentUnit text: ${describeContent(translation.text)}`);
4120
4434
  return translation.text;
4121
4435
  }
4122
4436
  return String(translation);
@@ -4151,13 +4465,18 @@ var LionRapid = (() => {
4151
4465
  throw new TranslationNotFoundError(key);
4152
4466
  }
4153
4467
  if (typeof translation === "object" && "text" in translation) {
4154
- logger.debug(`ContentUnit for "${key}": text="${translation.text}", styles=`, translation.styles);
4468
+ logger.debug(
4469
+ `ContentUnit for "${key}": text=${describeContent(translation.text)}, styles=`,
4470
+ translation.styles
4471
+ );
4155
4472
  return {
4156
4473
  text: translation.text,
4157
4474
  styles: translation.styles || {}
4158
4475
  };
4159
4476
  }
4160
- logger.debug(`Plain string for "${key}": "${translation}" (no styles)`);
4477
+ logger.debug(
4478
+ `Plain string for "${key}": ${describeContent(String(translation))} (no styles)`
4479
+ );
4161
4480
  return {
4162
4481
  text: String(translation),
4163
4482
  styles: {}
@@ -4965,167 +5284,6 @@ var LionRapid = (() => {
4965
5284
  return result;
4966
5285
  }
4967
5286
 
4968
- // src/browser/md5.ts
4969
- function safeAdd(x, y) {
4970
- const lsw = (x & 65535) + (y & 65535);
4971
- const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
4972
- return msw << 16 | lsw & 65535;
4973
- }
4974
- function bitRotateLeft(num, cnt) {
4975
- return num << cnt | num >>> 32 - cnt;
4976
- }
4977
- function md5cmn(q, a, b, x, s, t) {
4978
- return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
4979
- }
4980
- function md5ff(a, b, c, d, x, s, t) {
4981
- return md5cmn(b & c | ~b & d, a, b, x, s, t);
4982
- }
4983
- function md5gg(a, b, c, d, x, s, t) {
4984
- return md5cmn(b & d | c & ~d, a, b, x, s, t);
4985
- }
4986
- function md5hh(a, b, c, d, x, s, t) {
4987
- return md5cmn(b ^ c ^ d, a, b, x, s, t);
4988
- }
4989
- function md5ii(a, b, c, d, x, s, t) {
4990
- return md5cmn(c ^ (b | ~d), a, b, x, s, t);
4991
- }
4992
- function binlMD5(x, len) {
4993
- x[len >> 5] |= 128 << len % 32;
4994
- x[(len + 64 >>> 9 << 4) + 14] = len;
4995
- let a = 1732584193;
4996
- let b = -271733879;
4997
- let c = -1732584194;
4998
- let d = 271733878;
4999
- for (let i = 0; i < x.length; i += 16) {
5000
- const olda = a;
5001
- const oldb = b;
5002
- const oldc = c;
5003
- const oldd = d;
5004
- a = md5ff(a, b, c, d, x[i], 7, -680876936);
5005
- d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
5006
- c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
5007
- b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
5008
- a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
5009
- d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
5010
- c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
5011
- b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
5012
- a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
5013
- d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
5014
- c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
5015
- b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
5016
- a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
5017
- d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
5018
- c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
5019
- b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
5020
- a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
5021
- d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
5022
- c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
5023
- b = md5gg(b, c, d, a, x[i], 20, -373897302);
5024
- a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
5025
- d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
5026
- c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
5027
- b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
5028
- a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
5029
- d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
5030
- c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
5031
- b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
5032
- a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
5033
- d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
5034
- c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
5035
- b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
5036
- a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
5037
- d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
5038
- c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
5039
- b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
5040
- a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
5041
- d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
5042
- c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
5043
- b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
5044
- a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
5045
- d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
5046
- c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
5047
- b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
5048
- a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
5049
- d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
5050
- c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
5051
- b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
5052
- a = md5ii(a, b, c, d, x[i], 6, -198630844);
5053
- d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
5054
- c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
5055
- b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
5056
- a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
5057
- d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
5058
- c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
5059
- b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
5060
- a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
5061
- d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
5062
- c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
5063
- b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
5064
- a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
5065
- d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
5066
- c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
5067
- b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
5068
- a = safeAdd(a, olda);
5069
- b = safeAdd(b, oldb);
5070
- c = safeAdd(c, oldc);
5071
- d = safeAdd(d, oldd);
5072
- }
5073
- return [a, b, c, d];
5074
- }
5075
- function binl2hex(binarray) {
5076
- const hexTab = "0123456789abcdef";
5077
- let str = "";
5078
- for (let i = 0; i < binarray.length * 4; i++) {
5079
- str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
5080
- }
5081
- return str;
5082
- }
5083
- function utf8Bytes(str) {
5084
- if (typeof TextEncoder !== "undefined") {
5085
- return new TextEncoder().encode(str);
5086
- }
5087
- const out = [];
5088
- for (let i = 0; i < str.length; i++) {
5089
- const c = str.charCodeAt(i);
5090
- if (c < 128) {
5091
- out.push(c);
5092
- } else if (c < 2048) {
5093
- out.push(192 | c >> 6, 128 | c & 63);
5094
- } else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
5095
- const c2 = str.charCodeAt(++i);
5096
- const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
5097
- out.push(
5098
- 240 | cp >> 18,
5099
- 128 | cp >> 12 & 63,
5100
- 128 | cp >> 6 & 63,
5101
- 128 | cp & 63
5102
- );
5103
- } else {
5104
- out.push(
5105
- 224 | c >> 12,
5106
- 128 | c >> 6 & 63,
5107
- 128 | c & 63
5108
- );
5109
- }
5110
- }
5111
- return new Uint8Array(out);
5112
- }
5113
- function bytes2binl(bytes) {
5114
- const bin = [];
5115
- for (let i = 0; i < bytes.length; i++) {
5116
- bin[i >> 2] |= bytes[i] << i % 4 * 8;
5117
- }
5118
- return bin;
5119
- }
5120
- function md5(str) {
5121
- const bytes = utf8Bytes(str);
5122
- return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
5123
- }
5124
- function generateAutoKey(text) {
5125
- const trimmed = text.trim();
5126
- return "auto_" + md5(trimmed).substring(0, 12);
5127
- }
5128
-
5129
5287
  // src/browser/exclude-filter.ts
5130
5288
  var DEFAULT_PATTERNS = {
5131
5289
  emails: true,
@@ -5540,6 +5698,8 @@ var LionRapid = (() => {
5540
5698
  __publicField(this, "type", "storage");
5541
5699
  __publicField(this, "config");
5542
5700
  __publicField(this, "cacheName");
5701
+ /** CACHE_PREFIX, plus `<scope>/` when scoped. */
5702
+ __publicField(this, "entryPrefix");
5543
5703
  __publicField(this, "defaultTTL");
5544
5704
  __publicField(this, "available");
5545
5705
  this.config = {
@@ -5553,6 +5713,8 @@ var LionRapid = (() => {
5553
5713
  }
5554
5714
  };
5555
5715
  this.cacheName = this.config.options?.cacheName ?? CACHE_NAME;
5716
+ const scope = this.config.options?.scope;
5717
+ this.entryPrefix = scope ? `${CACHE_PREFIX}${encodeURIComponent(scope)}/` : CACHE_PREFIX;
5556
5718
  this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
5557
5719
  this.available = this.checkAvailability();
5558
5720
  }
@@ -5573,13 +5735,13 @@ var LionRapid = (() => {
5573
5735
  }
5574
5736
  try {
5575
5737
  const cache = await caches.open(this.cacheName);
5576
- const response = await cache.match(CACHE_PREFIX + key);
5738
+ const response = await cache.match(this.entryPrefix + key);
5577
5739
  if (!response) {
5578
5740
  throw new TranslationNotFoundError(key);
5579
5741
  }
5580
5742
  const data = await response.json();
5581
5743
  if (data.expiry !== null && Date.now() > data.expiry) {
5582
- await cache.delete(CACHE_PREFIX + key);
5744
+ await cache.delete(this.entryPrefix + key);
5583
5745
  throw new TranslationNotFoundError(key);
5584
5746
  }
5585
5747
  return data.value;
@@ -5603,7 +5765,7 @@ var LionRapid = (() => {
5603
5765
  timestamp: Date.now()
5604
5766
  };
5605
5767
  await cache.put(
5606
- CACHE_PREFIX + key,
5768
+ this.entryPrefix + key,
5607
5769
  new Response(JSON.stringify(data), {
5608
5770
  headers: { "Content-Type": "application/json" }
5609
5771
  })
@@ -5618,7 +5780,7 @@ var LionRapid = (() => {
5618
5780
  if (!this.available) return;
5619
5781
  try {
5620
5782
  const cache = await caches.open(this.cacheName);
5621
- await cache.delete(CACHE_PREFIX + key);
5783
+ await cache.delete(this.entryPrefix + key);
5622
5784
  } catch {
5623
5785
  }
5624
5786
  }
@@ -5631,7 +5793,7 @@ var LionRapid = (() => {
5631
5793
  const cache = await caches.open(this.cacheName);
5632
5794
  const keys = await cache.keys();
5633
5795
  await Promise.all(
5634
- keys.filter((request) => request.url.includes(CACHE_PREFIX)).map((request) => cache.delete(request))
5796
+ keys.filter((request) => request.url.includes(this.entryPrefix)).map((request) => cache.delete(request))
5635
5797
  );
5636
5798
  } catch {
5637
5799
  }
@@ -5649,7 +5811,7 @@ var LionRapid = (() => {
5649
5811
  await Promise.all(
5650
5812
  keys.filter((request) => {
5651
5813
  const url = request.url;
5652
- const key = url.includes(CACHE_PREFIX) ? url.substring(url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length) : "";
5814
+ const key = url.includes(this.entryPrefix) ? url.substring(url.indexOf(this.entryPrefix) + this.entryPrefix.length) : "";
5653
5815
  return key === namespaceCacheKey || key.startsWith(keyPrefix);
5654
5816
  }).map((request) => cache.delete(request))
5655
5817
  );
@@ -5701,9 +5863,9 @@ var LionRapid = (() => {
5701
5863
  const keys = await cache.keys();
5702
5864
  for (const request of keys) {
5703
5865
  const url = request.url;
5704
- if (!url.includes(CACHE_PREFIX)) continue;
5866
+ if (!url.includes(this.entryPrefix)) continue;
5705
5867
  const key = url.substring(
5706
- url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length
5868
+ url.indexOf(this.entryPrefix) + this.entryPrefix.length
5707
5869
  );
5708
5870
  try {
5709
5871
  const value = await this.get(key);
@@ -5725,7 +5887,7 @@ var LionRapid = (() => {
5725
5887
  try {
5726
5888
  const cache = await caches.open(this.cacheName);
5727
5889
  const keys = await cache.keys();
5728
- const size = keys.filter((r) => r.url.includes(CACHE_PREFIX)).length;
5890
+ const size = keys.filter((r) => r.url.includes(this.entryPrefix)).length;
5729
5891
  return { size, cacheName: this.cacheName };
5730
5892
  } catch {
5731
5893
  return { size: 0, cacheName: this.cacheName };
@@ -5733,8 +5895,11 @@ var LionRapid = (() => {
5733
5895
  }
5734
5896
  };
5735
5897
 
5898
+ // src/config/api.config.ts
5899
+ var DEFAULT_API_BASE_URL = "https://app.lionrapid.com";
5900
+
5736
5901
  // src/index.ts
5737
- var VERSION = "0.6.5";
5902
+ var VERSION = "0.7.0";
5738
5903
  return __toCommonJS(index_exports);
5739
5904
  })();
5740
5905
  //# sourceMappingURL=lionrapid.iife.js.map