@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
@@ -2,6 +2,216 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
+ // src/browser/md5.ts
6
+ function safeAdd(x, y) {
7
+ const lsw = (x & 65535) + (y & 65535);
8
+ const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
9
+ return msw << 16 | lsw & 65535;
10
+ }
11
+ function bitRotateLeft(num, cnt) {
12
+ return num << cnt | num >>> 32 - cnt;
13
+ }
14
+ function md5cmn(q, a, b, x, s, t) {
15
+ return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
16
+ }
17
+ function md5ff(a, b, c, d, x, s, t) {
18
+ return md5cmn(b & c | ~b & d, a, b, x, s, t);
19
+ }
20
+ function md5gg(a, b, c, d, x, s, t) {
21
+ return md5cmn(b & d | c & ~d, a, b, x, s, t);
22
+ }
23
+ function md5hh(a, b, c, d, x, s, t) {
24
+ return md5cmn(b ^ c ^ d, a, b, x, s, t);
25
+ }
26
+ function md5ii(a, b, c, d, x, s, t) {
27
+ return md5cmn(c ^ (b | ~d), a, b, x, s, t);
28
+ }
29
+ function binlMD5(x, len) {
30
+ x[len >> 5] |= 128 << len % 32;
31
+ x[(len + 64 >>> 9 << 4) + 14] = len;
32
+ let a = 1732584193;
33
+ let b = -271733879;
34
+ let c = -1732584194;
35
+ let d = 271733878;
36
+ for (let i = 0; i < x.length; i += 16) {
37
+ const olda = a;
38
+ const oldb = b;
39
+ const oldc = c;
40
+ const oldd = d;
41
+ a = md5ff(a, b, c, d, x[i], 7, -680876936);
42
+ d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
43
+ c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
44
+ b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
45
+ a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
46
+ d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
47
+ c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
48
+ b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
49
+ a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
50
+ d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
51
+ c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
52
+ b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
53
+ a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
54
+ d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
55
+ c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
56
+ b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
57
+ a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
58
+ d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
59
+ c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
60
+ b = md5gg(b, c, d, a, x[i], 20, -373897302);
61
+ a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
62
+ d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
63
+ c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
64
+ b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
65
+ a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
66
+ d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
67
+ c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
68
+ b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
69
+ a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
70
+ d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
71
+ c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
72
+ b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
73
+ a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
74
+ d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
75
+ c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
76
+ b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
77
+ a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
78
+ d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
79
+ c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
80
+ b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
81
+ a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
82
+ d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
83
+ c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
84
+ b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
85
+ a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
86
+ d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
87
+ c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
88
+ b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
89
+ a = md5ii(a, b, c, d, x[i], 6, -198630844);
90
+ d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
91
+ c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
92
+ b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
93
+ a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
94
+ d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
95
+ c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
96
+ b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
97
+ a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
98
+ d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
99
+ c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
100
+ b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
101
+ a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
102
+ d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
103
+ c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
104
+ b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
105
+ a = safeAdd(a, olda);
106
+ b = safeAdd(b, oldb);
107
+ c = safeAdd(c, oldc);
108
+ d = safeAdd(d, oldd);
109
+ }
110
+ return [a, b, c, d];
111
+ }
112
+ function binl2hex(binarray) {
113
+ const hexTab = "0123456789abcdef";
114
+ let str = "";
115
+ for (let i = 0; i < binarray.length * 4; i++) {
116
+ str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
117
+ }
118
+ return str;
119
+ }
120
+ function utf8Bytes(str) {
121
+ if (typeof TextEncoder !== "undefined") {
122
+ return new TextEncoder().encode(str);
123
+ }
124
+ const out = [];
125
+ for (let i = 0; i < str.length; i++) {
126
+ const c = str.charCodeAt(i);
127
+ if (c < 128) {
128
+ out.push(c);
129
+ } else if (c < 2048) {
130
+ out.push(192 | c >> 6, 128 | c & 63);
131
+ } else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
132
+ const c2 = str.charCodeAt(++i);
133
+ const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
134
+ out.push(
135
+ 240 | cp >> 18,
136
+ 128 | cp >> 12 & 63,
137
+ 128 | cp >> 6 & 63,
138
+ 128 | cp & 63
139
+ );
140
+ } else {
141
+ out.push(
142
+ 224 | c >> 12,
143
+ 128 | c >> 6 & 63,
144
+ 128 | c & 63
145
+ );
146
+ }
147
+ }
148
+ return new Uint8Array(out);
149
+ }
150
+ function bytes2binl(bytes) {
151
+ const bin = [];
152
+ for (let i = 0; i < bytes.length; i++) {
153
+ bin[i >> 2] |= bytes[i] << i % 4 * 8;
154
+ }
155
+ return bin;
156
+ }
157
+ function md5(str) {
158
+ const bytes = utf8Bytes(str);
159
+ return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
160
+ }
161
+ function generateAutoKey(text) {
162
+ const trimmed = text.trim();
163
+ return "auto_" + md5(trimmed).substring(0, 12);
164
+ }
165
+
166
+ // src/utils/log-safe.ts
167
+ var LOG_VALUE_FULL_MAX = 200;
168
+ var LOG_VALUE_EDGE = 90;
169
+ function contentIdentity(value) {
170
+ return `${value.length} chars, md5:${md5(value.trim()).slice(0, 12)}`;
171
+ }
172
+ function contentExcerpt(value) {
173
+ if (value.length <= LOG_VALUE_FULL_MAX) {
174
+ return JSON.stringify(value);
175
+ }
176
+ return JSON.stringify(
177
+ `${value.slice(0, LOG_VALUE_EDGE)}\u2026${value.slice(-LOG_VALUE_EDGE)}`
178
+ );
179
+ }
180
+ var LOG_ERROR_MAX = 200;
181
+ var LOG_DATA_MAX = 500;
182
+ var LOG_STACK_FRAMES = 2;
183
+ var LOG_FRAME_MAX = 120;
184
+ function bound(text, max) {
185
+ return text.length <= max ? text : `${text.slice(0, max)}\u2026 (${text.length} chars)`;
186
+ }
187
+ function oneLine(text) {
188
+ return JSON.stringify(text).slice(1, -1);
189
+ }
190
+ function describeError(error) {
191
+ if (error instanceof Error) {
192
+ const name = error.name || "Error";
193
+ return bound(oneLine(`${name}: ${error.message}`), LOG_ERROR_MAX);
194
+ }
195
+ return bound(oneLine(String(error)), LOG_ERROR_MAX);
196
+ }
197
+ function describeLogData(data) {
198
+ if (data instanceof Error) {
199
+ const headerLines = String(data.message ?? "").split("\n").length;
200
+ 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));
201
+ return [describeError(data), ...frames].join(" | ");
202
+ }
203
+ let rendered;
204
+ try {
205
+ rendered = JSON.stringify(data);
206
+ } catch {
207
+ return "[unserializable]";
208
+ }
209
+ return bound(rendered ?? oneLine(String(data)), LOG_DATA_MAX);
210
+ }
211
+ function describeContent(value) {
212
+ return `${contentIdentity(value)}, ${contentExcerpt(value)}`;
213
+ }
214
+
5
215
  // src/config/logger.config.ts
6
216
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
7
217
  LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
@@ -129,7 +339,7 @@ var DevelopmentLogger = class _DevelopmentLogger {
129
339
  const locationInfo2 = callSite ? ` (${callSite.fileName}:${callSite.lineNumber})` : "";
130
340
  let logMessage = `${this.config.prefix} ${emoji} ${levelName} ${message}${locationInfo2}`;
131
341
  if (data !== void 0) {
132
- logMessage += ` ${JSON.stringify(data)}`;
342
+ logMessage += ` ${describeLogData(data)}`;
133
343
  }
134
344
  return logMessage;
135
345
  }
@@ -140,7 +350,7 @@ var DevelopmentLogger = class _DevelopmentLogger {
140
350
  const coloredLevel = `${levelColor}${Colors.BOLD}${levelName}${reset}`;
141
351
  const coloredMessage = `${levelColor}${message}${reset}`;
142
352
  const locationInfo = callSite ? `${Colors.DIM}${Colors.BRIGHT_BLACK} (${callSite.fileName}:${callSite.lineNumber})${reset}` : "";
143
- const dataInfo = data !== void 0 ? `${Colors.DIM}${Colors.CYAN} ${JSON.stringify(data)}${reset}` : "";
353
+ const dataInfo = data !== void 0 ? `${Colors.DIM}${Colors.CYAN} ${describeLogData(data)}${reset}` : "";
144
354
  return `${coloredPrefix} ${coloredEmoji} ${coloredLevel} ${coloredMessage}${locationInfo}${dataInfo}`;
145
355
  }
146
356
  getLevelColor(level) {
@@ -939,6 +1149,24 @@ var HandlerChain = class {
939
1149
  });
940
1150
  await Promise.allSettled(savePromises);
941
1151
  }
1152
+ /** Forget `locale:namespace` in every memory and storage handler (a 404). */
1153
+ async forgetNamespaceInHandlers(context) {
1154
+ for (const handler of this.syncHandlers) {
1155
+ try {
1156
+ handler.forgetNamespace?.(context);
1157
+ } catch (error) {
1158
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1159
+ }
1160
+ }
1161
+ for (const handler of this.asyncHandlers) {
1162
+ if (handler.type !== "storage" && handler.type !== "memory") continue;
1163
+ try {
1164
+ await handler.forgetNamespace?.(context);
1165
+ } catch (error) {
1166
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1167
+ }
1168
+ }
1169
+ }
942
1170
  async saveNamespaceToHandlers(translations, context) {
943
1171
  this.saveNamespaceToMemoryHandlers(translations, context);
944
1172
  await this.saveNamespaceToStorageHandlers(translations, context);
@@ -1040,6 +1268,10 @@ var HandlerChain = class {
1040
1268
  });
1041
1269
  return result.data;
1042
1270
  }
1271
+ if (result && result.status === 404) {
1272
+ await this.forgetNamespaceInHandlers({ namespace, locale });
1273
+ return null;
1274
+ }
1043
1275
  } catch (error) {
1044
1276
  logger.warn(
1045
1277
  `Failed to load namespace: ${locale}:${namespace} from ${handler.name}`,
@@ -1423,6 +1655,7 @@ var SyncHandler = class {
1423
1655
  context.locale + ":" + context.namespace,
1424
1656
  this.name
1425
1657
  );
1658
+ const previousKeys = this.storedNamespaceKeys(context.locale, context.namespace);
1426
1659
  const namespaceCacheKey = createNamespaceCacheKey(
1427
1660
  context.locale,
1428
1661
  context.namespace
@@ -1434,11 +1667,38 @@ var SyncHandler = class {
1434
1667
  context.locale,
1435
1668
  context.namespace
1436
1669
  );
1670
+ for (const key of previousKeys) {
1671
+ if (!(key in flattened)) this.removeQuietly(key);
1672
+ }
1437
1673
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1438
1674
  this.setWithTTL(flatKey, flatValue);
1439
1675
  }
1440
1676
  repoBus.setSuccess(JSON.stringify(translations));
1441
1677
  }
1678
+ /** Forget a namespace the server says does not exist (404). See AsyncStorageHandler. */
1679
+ forgetNamespace(context) {
1680
+ if (!context.namespace) return;
1681
+ for (const key of this.storedNamespaceKeys(context.locale, context.namespace)) {
1682
+ this.removeQuietly(key);
1683
+ }
1684
+ this.clearNamespace(context.locale, context.namespace);
1685
+ }
1686
+ storedNamespaceKeys(locale, namespace) {
1687
+ try {
1688
+ const blob = this.repository.get(createNamespaceCacheKey(locale, namespace));
1689
+ if (!blob) return [];
1690
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
1691
+ } catch {
1692
+ return [];
1693
+ }
1694
+ }
1695
+ removeQuietly(cacheKey) {
1696
+ this.ttlMap.delete(cacheKey);
1697
+ try {
1698
+ this.repository.remove(cacheKey);
1699
+ } catch {
1700
+ }
1701
+ }
1442
1702
  /**
1443
1703
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1444
1704
  * Returns parsed ContentUnit (text + styles) or null
@@ -1692,6 +1952,7 @@ var AsyncHandler = class {
1692
1952
  logger.debug(
1693
1953
  `Saving namespace: ${context.locale}:${context.namespace} (${keyCount} keys)`
1694
1954
  );
1955
+ const previousKeys = await this.storedNamespaceKeys(context.locale, context.namespace);
1695
1956
  const namespaceCacheKey = createNamespaceCacheKey(
1696
1957
  context.locale,
1697
1958
  context.namespace
@@ -1703,6 +1964,9 @@ var AsyncHandler = class {
1703
1964
  context.locale,
1704
1965
  context.namespace
1705
1966
  );
1967
+ for (const key of previousKeys) {
1968
+ if (!(key in flattened)) await this.removeQuietly(key);
1969
+ }
1706
1970
  let savedCount = 0;
1707
1971
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1708
1972
  await this.setWithTTL(flatKey, flatValue);
@@ -1711,6 +1975,35 @@ var AsyncHandler = class {
1711
1975
  logger.debug(`Saved: ${savedCount} keys + namespace blob`);
1712
1976
  repoBus.setSuccess(JSON.stringify(translations));
1713
1977
  }
1978
+ /**
1979
+ * Forget a namespace the server says does not exist (404): its blob and
1980
+ * every per-key entry the blob listed. A network failure never gets here,
1981
+ * so the offline floor still serves when the server is unreachable.
1982
+ */
1983
+ async forgetNamespace(context) {
1984
+ if (!context.namespace) return;
1985
+ for (const key of await this.storedNamespaceKeys(context.locale, context.namespace)) {
1986
+ await this.removeQuietly(key);
1987
+ }
1988
+ await this.removeQuietly(createNamespaceCacheKey(context.locale, context.namespace));
1989
+ }
1990
+ /** The per-key cache keys (`locale:namespace:key`) the stored blob lists. */
1991
+ async storedNamespaceKeys(locale, namespace) {
1992
+ try {
1993
+ const blob = await this.repository.get(createNamespaceCacheKey(locale, namespace));
1994
+ if (!blob) return [];
1995
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
1996
+ } catch {
1997
+ return [];
1998
+ }
1999
+ }
2000
+ async removeQuietly(cacheKey) {
2001
+ this.ttlMap.delete(cacheKey);
2002
+ try {
2003
+ await this.repository.remove(cacheKey);
2004
+ } catch {
2005
+ }
2006
+ }
1714
2007
  /**
1715
2008
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1716
2009
  * Returns parsed ContentUnit (text + styles) or null
@@ -2261,6 +2554,15 @@ var NetworkHandler = class {
2261
2554
  const config = this.repository.getConfig();
2262
2555
  return config.options?.preloadNamespaces || [];
2263
2556
  }
2557
+ /**
2558
+ * The host this handler talks to. Exposed so a failure can NAME it: "the
2559
+ * server is unreachable" is not actionable, and a default pointing at a dead
2560
+ * host is exactly the case where the reader needs to be told which host.
2561
+ */
2562
+ getBaseUrl() {
2563
+ const config = this.repository.getConfig();
2564
+ return config.options?.baseUrl || "";
2565
+ }
2264
2566
  };
2265
2567
 
2266
2568
  // src/content/canonical-tag.ts
@@ -3133,7 +3435,9 @@ var LionRapidCore = class {
3133
3435
  const processedText = this.processTranslationValue(unit.text, request);
3134
3436
  return processedText.replace(/<\/?[0-9]+\/?>/g, "");
3135
3437
  } catch (error) {
3136
- logger.warn(`ContentUnit processing failed for "${value}":`, error);
3438
+ logger.warn(
3439
+ `ContentUnit processing failed for key "${request.cleanKey}" \u2014 ${describeContent(value)}: ${describeError(error)}`
3440
+ );
3137
3441
  return this.processTranslationValue(value, request);
3138
3442
  }
3139
3443
  }
@@ -3186,7 +3490,9 @@ var LionRapidCore = class {
3186
3490
  }
3187
3491
  return processedValue;
3188
3492
  } catch (error) {
3189
- logger.warn(`Translation processing failed for "${value}":`, error);
3493
+ logger.warn(
3494
+ `Translation processing failed for key "${resolvedKey ?? "(no key)"}" \u2014 ${describeContent(value)}: ${describeError(error)}`
3495
+ );
3190
3496
  return value;
3191
3497
  }
3192
3498
  }
@@ -3903,8 +4209,9 @@ var LionRapidBuilder = class _LionRapidBuilder {
3903
4209
  const warnLoudlyOnce = (namespaceKey, error) => {
3904
4210
  if (!isLoudPreloadFailure(error) || warnedLoudly) return;
3905
4211
  warnedLoudly = true;
4212
+ const baseUrl = networkHandler.getBaseUrl() || "(no base URL configured)";
3906
4213
  console.warn(
3907
- `[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
4214
+ `[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).`,
3908
4215
  error
3909
4216
  );
3910
4217
  };
@@ -3924,6 +4231,12 @@ var LionRapidBuilder = class _LionRapidBuilder {
3924
4231
  logger.debug(`Preloaded: ${namespaceKey}`);
3925
4232
  return;
3926
4233
  }
4234
+ if (result?.status === 404) {
4235
+ await handlerChain.forgetNamespaceInHandlers({
4236
+ locale: preloadConfig.locale,
4237
+ namespace: preloadConfig.namespace
4238
+ });
4239
+ }
3927
4240
  if (result?.error) {
3928
4241
  warnLoudlyOnce(namespaceKey, result.error);
3929
4242
  logger.warn(`Preload failed: ${namespaceKey}`, result.error);
@@ -4032,7 +4345,7 @@ var _NetworkRepository = class _NetworkRepository {
4032
4345
  throw new TranslationNotFoundError(key);
4033
4346
  }
4034
4347
  if (typeof translation === "object" && "text" in translation) {
4035
- logger.debug(`ContentUnit text: "${translation.text}"`);
4348
+ logger.debug(`ContentUnit text: ${describeContent(translation.text)}`);
4036
4349
  return translation.text;
4037
4350
  }
4038
4351
  return String(translation);
@@ -4067,13 +4380,18 @@ var _NetworkRepository = class _NetworkRepository {
4067
4380
  throw new TranslationNotFoundError(key);
4068
4381
  }
4069
4382
  if (typeof translation === "object" && "text" in translation) {
4070
- logger.debug(`ContentUnit for "${key}": text="${translation.text}", styles=`, translation.styles);
4383
+ logger.debug(
4384
+ `ContentUnit for "${key}": text=${describeContent(translation.text)}, styles=`,
4385
+ translation.styles
4386
+ );
4071
4387
  return {
4072
4388
  text: translation.text,
4073
4389
  styles: translation.styles || {}
4074
4390
  };
4075
4391
  }
4076
- logger.debug(`Plain string for "${key}": "${translation}" (no styles)`);
4392
+ logger.debug(
4393
+ `Plain string for "${key}": ${describeContent(String(translation))} (no styles)`
4394
+ );
4077
4395
  return {
4078
4396
  text: String(translation),
4079
4397
  styles: {}
@@ -4881,167 +5199,6 @@ function replaceBlockInners(html, replacements) {
4881
5199
  return result;
4882
5200
  }
4883
5201
 
4884
- // src/browser/md5.ts
4885
- function safeAdd(x, y) {
4886
- const lsw = (x & 65535) + (y & 65535);
4887
- const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
4888
- return msw << 16 | lsw & 65535;
4889
- }
4890
- function bitRotateLeft(num, cnt) {
4891
- return num << cnt | num >>> 32 - cnt;
4892
- }
4893
- function md5cmn(q, a, b, x, s, t) {
4894
- return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
4895
- }
4896
- function md5ff(a, b, c, d, x, s, t) {
4897
- return md5cmn(b & c | ~b & d, a, b, x, s, t);
4898
- }
4899
- function md5gg(a, b, c, d, x, s, t) {
4900
- return md5cmn(b & d | c & ~d, a, b, x, s, t);
4901
- }
4902
- function md5hh(a, b, c, d, x, s, t) {
4903
- return md5cmn(b ^ c ^ d, a, b, x, s, t);
4904
- }
4905
- function md5ii(a, b, c, d, x, s, t) {
4906
- return md5cmn(c ^ (b | ~d), a, b, x, s, t);
4907
- }
4908
- function binlMD5(x, len) {
4909
- x[len >> 5] |= 128 << len % 32;
4910
- x[(len + 64 >>> 9 << 4) + 14] = len;
4911
- let a = 1732584193;
4912
- let b = -271733879;
4913
- let c = -1732584194;
4914
- let d = 271733878;
4915
- for (let i = 0; i < x.length; i += 16) {
4916
- const olda = a;
4917
- const oldb = b;
4918
- const oldc = c;
4919
- const oldd = d;
4920
- a = md5ff(a, b, c, d, x[i], 7, -680876936);
4921
- d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
4922
- c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
4923
- b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
4924
- a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
4925
- d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
4926
- c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
4927
- b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
4928
- a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
4929
- d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
4930
- c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
4931
- b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
4932
- a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
4933
- d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
4934
- c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
4935
- b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
4936
- a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
4937
- d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
4938
- c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
4939
- b = md5gg(b, c, d, a, x[i], 20, -373897302);
4940
- a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
4941
- d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
4942
- c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
4943
- b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
4944
- a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
4945
- d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
4946
- c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
4947
- b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
4948
- a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
4949
- d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
4950
- c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
4951
- b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
4952
- a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
4953
- d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
4954
- c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
4955
- b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
4956
- a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
4957
- d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
4958
- c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
4959
- b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
4960
- a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
4961
- d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
4962
- c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
4963
- b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
4964
- a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
4965
- d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
4966
- c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
4967
- b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
4968
- a = md5ii(a, b, c, d, x[i], 6, -198630844);
4969
- d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
4970
- c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
4971
- b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
4972
- a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
4973
- d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
4974
- c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
4975
- b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
4976
- a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
4977
- d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
4978
- c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
4979
- b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
4980
- a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
4981
- d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
4982
- c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
4983
- b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
4984
- a = safeAdd(a, olda);
4985
- b = safeAdd(b, oldb);
4986
- c = safeAdd(c, oldc);
4987
- d = safeAdd(d, oldd);
4988
- }
4989
- return [a, b, c, d];
4990
- }
4991
- function binl2hex(binarray) {
4992
- const hexTab = "0123456789abcdef";
4993
- let str = "";
4994
- for (let i = 0; i < binarray.length * 4; i++) {
4995
- str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
4996
- }
4997
- return str;
4998
- }
4999
- function utf8Bytes(str) {
5000
- if (typeof TextEncoder !== "undefined") {
5001
- return new TextEncoder().encode(str);
5002
- }
5003
- const out = [];
5004
- for (let i = 0; i < str.length; i++) {
5005
- const c = str.charCodeAt(i);
5006
- if (c < 128) {
5007
- out.push(c);
5008
- } else if (c < 2048) {
5009
- out.push(192 | c >> 6, 128 | c & 63);
5010
- } else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
5011
- const c2 = str.charCodeAt(++i);
5012
- const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
5013
- out.push(
5014
- 240 | cp >> 18,
5015
- 128 | cp >> 12 & 63,
5016
- 128 | cp >> 6 & 63,
5017
- 128 | cp & 63
5018
- );
5019
- } else {
5020
- out.push(
5021
- 224 | c >> 12,
5022
- 128 | c >> 6 & 63,
5023
- 128 | c & 63
5024
- );
5025
- }
5026
- }
5027
- return new Uint8Array(out);
5028
- }
5029
- function bytes2binl(bytes) {
5030
- const bin = [];
5031
- for (let i = 0; i < bytes.length; i++) {
5032
- bin[i >> 2] |= bytes[i] << i % 4 * 8;
5033
- }
5034
- return bin;
5035
- }
5036
- function md5(str) {
5037
- const bytes = utf8Bytes(str);
5038
- return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
5039
- }
5040
- function generateAutoKey(text) {
5041
- const trimmed = text.trim();
5042
- return "auto_" + md5(trimmed).substring(0, 12);
5043
- }
5044
-
5045
5202
  // src/browser/exclude-filter.ts
5046
5203
  var DEFAULT_PATTERNS = {
5047
5204
  emails: true,
@@ -5456,6 +5613,8 @@ var CacheStorageRepository = class {
5456
5613
  __publicField(this, "type", "storage");
5457
5614
  __publicField(this, "config");
5458
5615
  __publicField(this, "cacheName");
5616
+ /** CACHE_PREFIX, plus `<scope>/` when scoped. */
5617
+ __publicField(this, "entryPrefix");
5459
5618
  __publicField(this, "defaultTTL");
5460
5619
  __publicField(this, "available");
5461
5620
  this.config = {
@@ -5469,6 +5628,8 @@ var CacheStorageRepository = class {
5469
5628
  }
5470
5629
  };
5471
5630
  this.cacheName = this.config.options?.cacheName ?? CACHE_NAME;
5631
+ const scope = this.config.options?.scope;
5632
+ this.entryPrefix = scope ? `${CACHE_PREFIX}${encodeURIComponent(scope)}/` : CACHE_PREFIX;
5472
5633
  this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
5473
5634
  this.available = this.checkAvailability();
5474
5635
  }
@@ -5489,13 +5650,13 @@ var CacheStorageRepository = class {
5489
5650
  }
5490
5651
  try {
5491
5652
  const cache = await caches.open(this.cacheName);
5492
- const response = await cache.match(CACHE_PREFIX + key);
5653
+ const response = await cache.match(this.entryPrefix + key);
5493
5654
  if (!response) {
5494
5655
  throw new TranslationNotFoundError(key);
5495
5656
  }
5496
5657
  const data = await response.json();
5497
5658
  if (data.expiry !== null && Date.now() > data.expiry) {
5498
- await cache.delete(CACHE_PREFIX + key);
5659
+ await cache.delete(this.entryPrefix + key);
5499
5660
  throw new TranslationNotFoundError(key);
5500
5661
  }
5501
5662
  return data.value;
@@ -5519,7 +5680,7 @@ var CacheStorageRepository = class {
5519
5680
  timestamp: Date.now()
5520
5681
  };
5521
5682
  await cache.put(
5522
- CACHE_PREFIX + key,
5683
+ this.entryPrefix + key,
5523
5684
  new Response(JSON.stringify(data), {
5524
5685
  headers: { "Content-Type": "application/json" }
5525
5686
  })
@@ -5534,7 +5695,7 @@ var CacheStorageRepository = class {
5534
5695
  if (!this.available) return;
5535
5696
  try {
5536
5697
  const cache = await caches.open(this.cacheName);
5537
- await cache.delete(CACHE_PREFIX + key);
5698
+ await cache.delete(this.entryPrefix + key);
5538
5699
  } catch {
5539
5700
  }
5540
5701
  }
@@ -5547,7 +5708,7 @@ var CacheStorageRepository = class {
5547
5708
  const cache = await caches.open(this.cacheName);
5548
5709
  const keys = await cache.keys();
5549
5710
  await Promise.all(
5550
- keys.filter((request) => request.url.includes(CACHE_PREFIX)).map((request) => cache.delete(request))
5711
+ keys.filter((request) => request.url.includes(this.entryPrefix)).map((request) => cache.delete(request))
5551
5712
  );
5552
5713
  } catch {
5553
5714
  }
@@ -5565,7 +5726,7 @@ var CacheStorageRepository = class {
5565
5726
  await Promise.all(
5566
5727
  keys.filter((request) => {
5567
5728
  const url = request.url;
5568
- const key = url.includes(CACHE_PREFIX) ? url.substring(url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length) : "";
5729
+ const key = url.includes(this.entryPrefix) ? url.substring(url.indexOf(this.entryPrefix) + this.entryPrefix.length) : "";
5569
5730
  return key === namespaceCacheKey || key.startsWith(keyPrefix);
5570
5731
  }).map((request) => cache.delete(request))
5571
5732
  );
@@ -5617,9 +5778,9 @@ var CacheStorageRepository = class {
5617
5778
  const keys = await cache.keys();
5618
5779
  for (const request of keys) {
5619
5780
  const url = request.url;
5620
- if (!url.includes(CACHE_PREFIX)) continue;
5781
+ if (!url.includes(this.entryPrefix)) continue;
5621
5782
  const key = url.substring(
5622
- url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length
5783
+ url.indexOf(this.entryPrefix) + this.entryPrefix.length
5623
5784
  );
5624
5785
  try {
5625
5786
  const value = await this.get(key);
@@ -5641,7 +5802,7 @@ var CacheStorageRepository = class {
5641
5802
  try {
5642
5803
  const cache = await caches.open(this.cacheName);
5643
5804
  const keys = await cache.keys();
5644
- const size = keys.filter((r) => r.url.includes(CACHE_PREFIX)).length;
5805
+ const size = keys.filter((r) => r.url.includes(this.entryPrefix)).length;
5645
5806
  return { size, cacheName: this.cacheName };
5646
5807
  } catch {
5647
5808
  return { size: 0, cacheName: this.cacheName };
@@ -5649,8 +5810,11 @@ var CacheStorageRepository = class {
5649
5810
  }
5650
5811
  };
5651
5812
 
5813
+ // src/config/api.config.ts
5814
+ var DEFAULT_API_BASE_URL = "https://app.lionrapid.com";
5815
+
5652
5816
  // src/index.ts
5653
- var VERSION = "0.6.5";
5817
+ var VERSION = "0.7.0";
5654
5818
  export {
5655
5819
  CORE_CACHE_ENTRY_PREFIX,
5656
5820
  CORE_CACHE_NAMES,
@@ -5658,6 +5822,7 @@ export {
5658
5822
  CacheStorageRepository,
5659
5823
  Colors,
5660
5824
  ContentValidator,
5825
+ DEFAULT_API_BASE_URL,
5661
5826
  EventEmitter,
5662
5827
  ExcludeFilter,
5663
5828
  HTMLSerializer,