@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
@@ -40,6 +40,7 @@ __export(index_exports, {
40
40
  CacheStorageRepository: () => CacheStorageRepository,
41
41
  Colors: () => Colors,
42
42
  ContentValidator: () => ContentValidator,
43
+ DEFAULT_API_BASE_URL: () => DEFAULT_API_BASE_URL,
43
44
  EventEmitter: () => EventEmitter,
44
45
  ExcludeFilter: () => ExcludeFilter,
45
46
  HTMLSerializer: () => HTMLSerializer,
@@ -98,6 +99,216 @@ __export(index_exports, {
98
99
  });
99
100
  module.exports = __toCommonJS(index_exports);
100
101
 
102
+ // src/browser/md5.ts
103
+ function safeAdd(x, y) {
104
+ const lsw = (x & 65535) + (y & 65535);
105
+ const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
106
+ return msw << 16 | lsw & 65535;
107
+ }
108
+ function bitRotateLeft(num, cnt) {
109
+ return num << cnt | num >>> 32 - cnt;
110
+ }
111
+ function md5cmn(q, a, b, x, s, t) {
112
+ return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
113
+ }
114
+ function md5ff(a, b, c, d, x, s, t) {
115
+ return md5cmn(b & c | ~b & d, a, b, x, s, t);
116
+ }
117
+ function md5gg(a, b, c, d, x, s, t) {
118
+ return md5cmn(b & d | c & ~d, a, b, x, s, t);
119
+ }
120
+ function md5hh(a, b, c, d, x, s, t) {
121
+ return md5cmn(b ^ c ^ d, a, b, x, s, t);
122
+ }
123
+ function md5ii(a, b, c, d, x, s, t) {
124
+ return md5cmn(c ^ (b | ~d), a, b, x, s, t);
125
+ }
126
+ function binlMD5(x, len) {
127
+ x[len >> 5] |= 128 << len % 32;
128
+ x[(len + 64 >>> 9 << 4) + 14] = len;
129
+ let a = 1732584193;
130
+ let b = -271733879;
131
+ let c = -1732584194;
132
+ let d = 271733878;
133
+ for (let i = 0; i < x.length; i += 16) {
134
+ const olda = a;
135
+ const oldb = b;
136
+ const oldc = c;
137
+ const oldd = d;
138
+ a = md5ff(a, b, c, d, x[i], 7, -680876936);
139
+ d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
140
+ c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
141
+ b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
142
+ a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
143
+ d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
144
+ c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
145
+ b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
146
+ a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
147
+ d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
148
+ c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
149
+ b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
150
+ a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
151
+ d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
152
+ c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
153
+ b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
154
+ a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
155
+ d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
156
+ c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
157
+ b = md5gg(b, c, d, a, x[i], 20, -373897302);
158
+ a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
159
+ d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
160
+ c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
161
+ b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
162
+ a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
163
+ d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
164
+ c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
165
+ b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
166
+ a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
167
+ d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
168
+ c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
169
+ b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
170
+ a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
171
+ d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
172
+ c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
173
+ b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
174
+ a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
175
+ d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
176
+ c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
177
+ b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
178
+ a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
179
+ d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
180
+ c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
181
+ b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
182
+ a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
183
+ d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
184
+ c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
185
+ b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
186
+ a = md5ii(a, b, c, d, x[i], 6, -198630844);
187
+ d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
188
+ c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
189
+ b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
190
+ a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
191
+ d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
192
+ c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
193
+ b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
194
+ a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
195
+ d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
196
+ c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
197
+ b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
198
+ a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
199
+ d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
200
+ c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
201
+ b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
202
+ a = safeAdd(a, olda);
203
+ b = safeAdd(b, oldb);
204
+ c = safeAdd(c, oldc);
205
+ d = safeAdd(d, oldd);
206
+ }
207
+ return [a, b, c, d];
208
+ }
209
+ function binl2hex(binarray) {
210
+ const hexTab = "0123456789abcdef";
211
+ let str = "";
212
+ for (let i = 0; i < binarray.length * 4; i++) {
213
+ str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
214
+ }
215
+ return str;
216
+ }
217
+ function utf8Bytes(str) {
218
+ if (typeof TextEncoder !== "undefined") {
219
+ return new TextEncoder().encode(str);
220
+ }
221
+ const out = [];
222
+ for (let i = 0; i < str.length; i++) {
223
+ const c = str.charCodeAt(i);
224
+ if (c < 128) {
225
+ out.push(c);
226
+ } else if (c < 2048) {
227
+ out.push(192 | c >> 6, 128 | c & 63);
228
+ } else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
229
+ const c2 = str.charCodeAt(++i);
230
+ const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
231
+ out.push(
232
+ 240 | cp >> 18,
233
+ 128 | cp >> 12 & 63,
234
+ 128 | cp >> 6 & 63,
235
+ 128 | cp & 63
236
+ );
237
+ } else {
238
+ out.push(
239
+ 224 | c >> 12,
240
+ 128 | c >> 6 & 63,
241
+ 128 | c & 63
242
+ );
243
+ }
244
+ }
245
+ return new Uint8Array(out);
246
+ }
247
+ function bytes2binl(bytes) {
248
+ const bin = [];
249
+ for (let i = 0; i < bytes.length; i++) {
250
+ bin[i >> 2] |= bytes[i] << i % 4 * 8;
251
+ }
252
+ return bin;
253
+ }
254
+ function md5(str) {
255
+ const bytes = utf8Bytes(str);
256
+ return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
257
+ }
258
+ function generateAutoKey(text) {
259
+ const trimmed = text.trim();
260
+ return "auto_" + md5(trimmed).substring(0, 12);
261
+ }
262
+
263
+ // src/utils/log-safe.ts
264
+ var LOG_VALUE_FULL_MAX = 200;
265
+ var LOG_VALUE_EDGE = 90;
266
+ function contentIdentity(value) {
267
+ return `${value.length} chars, md5:${md5(value.trim()).slice(0, 12)}`;
268
+ }
269
+ function contentExcerpt(value) {
270
+ if (value.length <= LOG_VALUE_FULL_MAX) {
271
+ return JSON.stringify(value);
272
+ }
273
+ return JSON.stringify(
274
+ `${value.slice(0, LOG_VALUE_EDGE)}\u2026${value.slice(-LOG_VALUE_EDGE)}`
275
+ );
276
+ }
277
+ var LOG_ERROR_MAX = 200;
278
+ var LOG_DATA_MAX = 500;
279
+ var LOG_STACK_FRAMES = 2;
280
+ var LOG_FRAME_MAX = 120;
281
+ function bound(text, max) {
282
+ return text.length <= max ? text : `${text.slice(0, max)}\u2026 (${text.length} chars)`;
283
+ }
284
+ function oneLine(text) {
285
+ return JSON.stringify(text).slice(1, -1);
286
+ }
287
+ function describeError(error) {
288
+ if (error instanceof Error) {
289
+ const name = error.name || "Error";
290
+ return bound(oneLine(`${name}: ${error.message}`), LOG_ERROR_MAX);
291
+ }
292
+ return bound(oneLine(String(error)), LOG_ERROR_MAX);
293
+ }
294
+ function describeLogData(data) {
295
+ if (data instanceof Error) {
296
+ const headerLines = String(data.message ?? "").split("\n").length;
297
+ 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));
298
+ return [describeError(data), ...frames].join(" | ");
299
+ }
300
+ let rendered;
301
+ try {
302
+ rendered = JSON.stringify(data);
303
+ } catch {
304
+ return "[unserializable]";
305
+ }
306
+ return bound(rendered ?? oneLine(String(data)), LOG_DATA_MAX);
307
+ }
308
+ function describeContent(value) {
309
+ return `${contentIdentity(value)}, ${contentExcerpt(value)}`;
310
+ }
311
+
101
312
  // src/config/logger.config.ts
102
313
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
103
314
  LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
@@ -225,7 +436,7 @@ var DevelopmentLogger = class _DevelopmentLogger {
225
436
  const locationInfo2 = callSite ? ` (${callSite.fileName}:${callSite.lineNumber})` : "";
226
437
  let logMessage = `${this.config.prefix} ${emoji} ${levelName} ${message}${locationInfo2}`;
227
438
  if (data !== void 0) {
228
- logMessage += ` ${JSON.stringify(data)}`;
439
+ logMessage += ` ${describeLogData(data)}`;
229
440
  }
230
441
  return logMessage;
231
442
  }
@@ -236,7 +447,7 @@ var DevelopmentLogger = class _DevelopmentLogger {
236
447
  const coloredLevel = `${levelColor}${Colors.BOLD}${levelName}${reset}`;
237
448
  const coloredMessage = `${levelColor}${message}${reset}`;
238
449
  const locationInfo = callSite ? `${Colors.DIM}${Colors.BRIGHT_BLACK} (${callSite.fileName}:${callSite.lineNumber})${reset}` : "";
239
- const dataInfo = data !== void 0 ? `${Colors.DIM}${Colors.CYAN} ${JSON.stringify(data)}${reset}` : "";
450
+ const dataInfo = data !== void 0 ? `${Colors.DIM}${Colors.CYAN} ${describeLogData(data)}${reset}` : "";
240
451
  return `${coloredPrefix} ${coloredEmoji} ${coloredLevel} ${coloredMessage}${locationInfo}${dataInfo}`;
241
452
  }
242
453
  getLevelColor(level) {
@@ -1035,6 +1246,24 @@ var HandlerChain = class {
1035
1246
  });
1036
1247
  await Promise.allSettled(savePromises);
1037
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
+ }
1038
1267
  async saveNamespaceToHandlers(translations, context) {
1039
1268
  this.saveNamespaceToMemoryHandlers(translations, context);
1040
1269
  await this.saveNamespaceToStorageHandlers(translations, context);
@@ -1136,6 +1365,10 @@ var HandlerChain = class {
1136
1365
  });
1137
1366
  return result.data;
1138
1367
  }
1368
+ if (result && result.status === 404) {
1369
+ await this.forgetNamespaceInHandlers({ namespace, locale });
1370
+ return null;
1371
+ }
1139
1372
  } catch (error) {
1140
1373
  logger.warn(
1141
1374
  `Failed to load namespace: ${locale}:${namespace} from ${handler.name}`,
@@ -1519,6 +1752,7 @@ var SyncHandler = class {
1519
1752
  context.locale + ":" + context.namespace,
1520
1753
  this.name
1521
1754
  );
1755
+ const previousKeys = this.storedNamespaceKeys(context.locale, context.namespace);
1522
1756
  const namespaceCacheKey = createNamespaceCacheKey(
1523
1757
  context.locale,
1524
1758
  context.namespace
@@ -1530,11 +1764,38 @@ var SyncHandler = class {
1530
1764
  context.locale,
1531
1765
  context.namespace
1532
1766
  );
1767
+ for (const key of previousKeys) {
1768
+ if (!(key in flattened)) this.removeQuietly(key);
1769
+ }
1533
1770
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1534
1771
  this.setWithTTL(flatKey, flatValue);
1535
1772
  }
1536
1773
  repoBus.setSuccess(JSON.stringify(translations));
1537
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
+ }
1538
1799
  /**
1539
1800
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1540
1801
  * Returns parsed ContentUnit (text + styles) or null
@@ -1788,6 +2049,7 @@ var AsyncHandler = class {
1788
2049
  logger.debug(
1789
2050
  `Saving namespace: ${context.locale}:${context.namespace} (${keyCount} keys)`
1790
2051
  );
2052
+ const previousKeys = await this.storedNamespaceKeys(context.locale, context.namespace);
1791
2053
  const namespaceCacheKey = createNamespaceCacheKey(
1792
2054
  context.locale,
1793
2055
  context.namespace
@@ -1799,6 +2061,9 @@ var AsyncHandler = class {
1799
2061
  context.locale,
1800
2062
  context.namespace
1801
2063
  );
2064
+ for (const key of previousKeys) {
2065
+ if (!(key in flattened)) await this.removeQuietly(key);
2066
+ }
1802
2067
  let savedCount = 0;
1803
2068
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1804
2069
  await this.setWithTTL(flatKey, flatValue);
@@ -1807,6 +2072,35 @@ var AsyncHandler = class {
1807
2072
  logger.debug(`Saved: ${savedCount} keys + namespace blob`);
1808
2073
  repoBus.setSuccess(JSON.stringify(translations));
1809
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
+ }
1810
2104
  /**
1811
2105
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1812
2106
  * Returns parsed ContentUnit (text + styles) or null
@@ -2357,6 +2651,15 @@ var NetworkHandler = class {
2357
2651
  const config = this.repository.getConfig();
2358
2652
  return config.options?.preloadNamespaces || [];
2359
2653
  }
2654
+ /**
2655
+ * The host this handler talks to. Exposed so a failure can NAME it: "the
2656
+ * server is unreachable" is not actionable, and a default pointing at a dead
2657
+ * host is exactly the case where the reader needs to be told which host.
2658
+ */
2659
+ getBaseUrl() {
2660
+ const config = this.repository.getConfig();
2661
+ return config.options?.baseUrl || "";
2662
+ }
2360
2663
  };
2361
2664
 
2362
2665
  // src/content/canonical-tag.ts
@@ -3229,7 +3532,9 @@ var LionRapidCore = class {
3229
3532
  const processedText = this.processTranslationValue(unit.text, request);
3230
3533
  return processedText.replace(/<\/?[0-9]+\/?>/g, "");
3231
3534
  } catch (error) {
3232
- logger.warn(`ContentUnit processing failed for "${value}":`, error);
3535
+ logger.warn(
3536
+ `ContentUnit processing failed for key "${request.cleanKey}" \u2014 ${describeContent(value)}: ${describeError(error)}`
3537
+ );
3233
3538
  return this.processTranslationValue(value, request);
3234
3539
  }
3235
3540
  }
@@ -3282,7 +3587,9 @@ var LionRapidCore = class {
3282
3587
  }
3283
3588
  return processedValue;
3284
3589
  } catch (error) {
3285
- logger.warn(`Translation processing failed for "${value}":`, error);
3590
+ logger.warn(
3591
+ `Translation processing failed for key "${resolvedKey ?? "(no key)"}" \u2014 ${describeContent(value)}: ${describeError(error)}`
3592
+ );
3286
3593
  return value;
3287
3594
  }
3288
3595
  }
@@ -3999,8 +4306,9 @@ var LionRapidBuilder = class _LionRapidBuilder {
3999
4306
  const warnLoudlyOnce = (namespaceKey, error) => {
4000
4307
  if (!isLoudPreloadFailure(error) || warnedLoudly) return;
4001
4308
  warnedLoudly = true;
4309
+ const baseUrl = networkHandler.getBaseUrl() || "(no base URL configured)";
4002
4310
  console.warn(
4003
- `[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
4311
+ `[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).`,
4004
4312
  error
4005
4313
  );
4006
4314
  };
@@ -4020,6 +4328,12 @@ var LionRapidBuilder = class _LionRapidBuilder {
4020
4328
  logger.debug(`Preloaded: ${namespaceKey}`);
4021
4329
  return;
4022
4330
  }
4331
+ if (result?.status === 404) {
4332
+ await handlerChain.forgetNamespaceInHandlers({
4333
+ locale: preloadConfig.locale,
4334
+ namespace: preloadConfig.namespace
4335
+ });
4336
+ }
4023
4337
  if (result?.error) {
4024
4338
  warnLoudlyOnce(namespaceKey, result.error);
4025
4339
  logger.warn(`Preload failed: ${namespaceKey}`, result.error);
@@ -4128,7 +4442,7 @@ var _NetworkRepository = class _NetworkRepository {
4128
4442
  throw new TranslationNotFoundError(key);
4129
4443
  }
4130
4444
  if (typeof translation === "object" && "text" in translation) {
4131
- logger.debug(`ContentUnit text: "${translation.text}"`);
4445
+ logger.debug(`ContentUnit text: ${describeContent(translation.text)}`);
4132
4446
  return translation.text;
4133
4447
  }
4134
4448
  return String(translation);
@@ -4163,13 +4477,18 @@ var _NetworkRepository = class _NetworkRepository {
4163
4477
  throw new TranslationNotFoundError(key);
4164
4478
  }
4165
4479
  if (typeof translation === "object" && "text" in translation) {
4166
- logger.debug(`ContentUnit for "${key}": text="${translation.text}", styles=`, translation.styles);
4480
+ logger.debug(
4481
+ `ContentUnit for "${key}": text=${describeContent(translation.text)}, styles=`,
4482
+ translation.styles
4483
+ );
4167
4484
  return {
4168
4485
  text: translation.text,
4169
4486
  styles: translation.styles || {}
4170
4487
  };
4171
4488
  }
4172
- logger.debug(`Plain string for "${key}": "${translation}" (no styles)`);
4489
+ logger.debug(
4490
+ `Plain string for "${key}": ${describeContent(String(translation))} (no styles)`
4491
+ );
4173
4492
  return {
4174
4493
  text: String(translation),
4175
4494
  styles: {}
@@ -4977,167 +5296,6 @@ function replaceBlockInners(html, replacements) {
4977
5296
  return result;
4978
5297
  }
4979
5298
 
4980
- // src/browser/md5.ts
4981
- function safeAdd(x, y) {
4982
- const lsw = (x & 65535) + (y & 65535);
4983
- const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
4984
- return msw << 16 | lsw & 65535;
4985
- }
4986
- function bitRotateLeft(num, cnt) {
4987
- return num << cnt | num >>> 32 - cnt;
4988
- }
4989
- function md5cmn(q, a, b, x, s, t) {
4990
- return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
4991
- }
4992
- function md5ff(a, b, c, d, x, s, t) {
4993
- return md5cmn(b & c | ~b & d, a, b, x, s, t);
4994
- }
4995
- function md5gg(a, b, c, d, x, s, t) {
4996
- return md5cmn(b & d | c & ~d, a, b, x, s, t);
4997
- }
4998
- function md5hh(a, b, c, d, x, s, t) {
4999
- return md5cmn(b ^ c ^ d, a, b, x, s, t);
5000
- }
5001
- function md5ii(a, b, c, d, x, s, t) {
5002
- return md5cmn(c ^ (b | ~d), a, b, x, s, t);
5003
- }
5004
- function binlMD5(x, len) {
5005
- x[len >> 5] |= 128 << len % 32;
5006
- x[(len + 64 >>> 9 << 4) + 14] = len;
5007
- let a = 1732584193;
5008
- let b = -271733879;
5009
- let c = -1732584194;
5010
- let d = 271733878;
5011
- for (let i = 0; i < x.length; i += 16) {
5012
- const olda = a;
5013
- const oldb = b;
5014
- const oldc = c;
5015
- const oldd = d;
5016
- a = md5ff(a, b, c, d, x[i], 7, -680876936);
5017
- d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
5018
- c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
5019
- b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
5020
- a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
5021
- d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
5022
- c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
5023
- b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
5024
- a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
5025
- d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
5026
- c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
5027
- b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
5028
- a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
5029
- d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
5030
- c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
5031
- b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
5032
- a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
5033
- d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
5034
- c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
5035
- b = md5gg(b, c, d, a, x[i], 20, -373897302);
5036
- a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
5037
- d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
5038
- c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
5039
- b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
5040
- a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
5041
- d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
5042
- c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
5043
- b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
5044
- a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
5045
- d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
5046
- c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
5047
- b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
5048
- a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
5049
- d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
5050
- c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
5051
- b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
5052
- a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
5053
- d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
5054
- c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
5055
- b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
5056
- a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
5057
- d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
5058
- c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
5059
- b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
5060
- a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
5061
- d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
5062
- c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
5063
- b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
5064
- a = md5ii(a, b, c, d, x[i], 6, -198630844);
5065
- d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
5066
- c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
5067
- b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
5068
- a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
5069
- d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
5070
- c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
5071
- b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
5072
- a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
5073
- d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
5074
- c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
5075
- b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
5076
- a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
5077
- d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
5078
- c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
5079
- b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
5080
- a = safeAdd(a, olda);
5081
- b = safeAdd(b, oldb);
5082
- c = safeAdd(c, oldc);
5083
- d = safeAdd(d, oldd);
5084
- }
5085
- return [a, b, c, d];
5086
- }
5087
- function binl2hex(binarray) {
5088
- const hexTab = "0123456789abcdef";
5089
- let str = "";
5090
- for (let i = 0; i < binarray.length * 4; i++) {
5091
- str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
5092
- }
5093
- return str;
5094
- }
5095
- function utf8Bytes(str) {
5096
- if (typeof TextEncoder !== "undefined") {
5097
- return new TextEncoder().encode(str);
5098
- }
5099
- const out = [];
5100
- for (let i = 0; i < str.length; i++) {
5101
- const c = str.charCodeAt(i);
5102
- if (c < 128) {
5103
- out.push(c);
5104
- } else if (c < 2048) {
5105
- out.push(192 | c >> 6, 128 | c & 63);
5106
- } else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
5107
- const c2 = str.charCodeAt(++i);
5108
- const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
5109
- out.push(
5110
- 240 | cp >> 18,
5111
- 128 | cp >> 12 & 63,
5112
- 128 | cp >> 6 & 63,
5113
- 128 | cp & 63
5114
- );
5115
- } else {
5116
- out.push(
5117
- 224 | c >> 12,
5118
- 128 | c >> 6 & 63,
5119
- 128 | c & 63
5120
- );
5121
- }
5122
- }
5123
- return new Uint8Array(out);
5124
- }
5125
- function bytes2binl(bytes) {
5126
- const bin = [];
5127
- for (let i = 0; i < bytes.length; i++) {
5128
- bin[i >> 2] |= bytes[i] << i % 4 * 8;
5129
- }
5130
- return bin;
5131
- }
5132
- function md5(str) {
5133
- const bytes = utf8Bytes(str);
5134
- return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
5135
- }
5136
- function generateAutoKey(text) {
5137
- const trimmed = text.trim();
5138
- return "auto_" + md5(trimmed).substring(0, 12);
5139
- }
5140
-
5141
5299
  // src/browser/exclude-filter.ts
5142
5300
  var DEFAULT_PATTERNS = {
5143
5301
  emails: true,
@@ -5552,6 +5710,8 @@ var CacheStorageRepository = class {
5552
5710
  __publicField(this, "type", "storage");
5553
5711
  __publicField(this, "config");
5554
5712
  __publicField(this, "cacheName");
5713
+ /** CACHE_PREFIX, plus `<scope>/` when scoped. */
5714
+ __publicField(this, "entryPrefix");
5555
5715
  __publicField(this, "defaultTTL");
5556
5716
  __publicField(this, "available");
5557
5717
  this.config = {
@@ -5565,6 +5725,8 @@ var CacheStorageRepository = class {
5565
5725
  }
5566
5726
  };
5567
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;
5568
5730
  this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
5569
5731
  this.available = this.checkAvailability();
5570
5732
  }
@@ -5585,13 +5747,13 @@ var CacheStorageRepository = class {
5585
5747
  }
5586
5748
  try {
5587
5749
  const cache = await caches.open(this.cacheName);
5588
- const response = await cache.match(CACHE_PREFIX + key);
5750
+ const response = await cache.match(this.entryPrefix + key);
5589
5751
  if (!response) {
5590
5752
  throw new TranslationNotFoundError(key);
5591
5753
  }
5592
5754
  const data = await response.json();
5593
5755
  if (data.expiry !== null && Date.now() > data.expiry) {
5594
- await cache.delete(CACHE_PREFIX + key);
5756
+ await cache.delete(this.entryPrefix + key);
5595
5757
  throw new TranslationNotFoundError(key);
5596
5758
  }
5597
5759
  return data.value;
@@ -5615,7 +5777,7 @@ var CacheStorageRepository = class {
5615
5777
  timestamp: Date.now()
5616
5778
  };
5617
5779
  await cache.put(
5618
- CACHE_PREFIX + key,
5780
+ this.entryPrefix + key,
5619
5781
  new Response(JSON.stringify(data), {
5620
5782
  headers: { "Content-Type": "application/json" }
5621
5783
  })
@@ -5630,7 +5792,7 @@ var CacheStorageRepository = class {
5630
5792
  if (!this.available) return;
5631
5793
  try {
5632
5794
  const cache = await caches.open(this.cacheName);
5633
- await cache.delete(CACHE_PREFIX + key);
5795
+ await cache.delete(this.entryPrefix + key);
5634
5796
  } catch {
5635
5797
  }
5636
5798
  }
@@ -5643,7 +5805,7 @@ var CacheStorageRepository = class {
5643
5805
  const cache = await caches.open(this.cacheName);
5644
5806
  const keys = await cache.keys();
5645
5807
  await Promise.all(
5646
- 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))
5647
5809
  );
5648
5810
  } catch {
5649
5811
  }
@@ -5661,7 +5823,7 @@ var CacheStorageRepository = class {
5661
5823
  await Promise.all(
5662
5824
  keys.filter((request) => {
5663
5825
  const url = request.url;
5664
- 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) : "";
5665
5827
  return key === namespaceCacheKey || key.startsWith(keyPrefix);
5666
5828
  }).map((request) => cache.delete(request))
5667
5829
  );
@@ -5713,9 +5875,9 @@ var CacheStorageRepository = class {
5713
5875
  const keys = await cache.keys();
5714
5876
  for (const request of keys) {
5715
5877
  const url = request.url;
5716
- if (!url.includes(CACHE_PREFIX)) continue;
5878
+ if (!url.includes(this.entryPrefix)) continue;
5717
5879
  const key = url.substring(
5718
- url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length
5880
+ url.indexOf(this.entryPrefix) + this.entryPrefix.length
5719
5881
  );
5720
5882
  try {
5721
5883
  const value = await this.get(key);
@@ -5737,7 +5899,7 @@ var CacheStorageRepository = class {
5737
5899
  try {
5738
5900
  const cache = await caches.open(this.cacheName);
5739
5901
  const keys = await cache.keys();
5740
- const size = keys.filter((r) => r.url.includes(CACHE_PREFIX)).length;
5902
+ const size = keys.filter((r) => r.url.includes(this.entryPrefix)).length;
5741
5903
  return { size, cacheName: this.cacheName };
5742
5904
  } catch {
5743
5905
  return { size: 0, cacheName: this.cacheName };
@@ -5745,8 +5907,11 @@ var CacheStorageRepository = class {
5745
5907
  }
5746
5908
  };
5747
5909
 
5910
+ // src/config/api.config.ts
5911
+ var DEFAULT_API_BASE_URL = "https://app.lionrapid.com";
5912
+
5748
5913
  // src/index.ts
5749
- var VERSION = "0.6.5";
5914
+ var VERSION = "0.7.0";
5750
5915
  //# sourceMappingURL=lionrapid.umd.js.map
5751
5916
 
5752
5917
  return exports;