@lionrapid/core 0.1.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 (302) hide show
  1. package/README.md +106 -0
  2. package/dist/base/event-emitter.d.ts +91 -0
  3. package/dist/base/event-emitter.d.ts.map +1 -0
  4. package/dist/base/event-emitter.js +330 -0
  5. package/dist/base/event-emitter.js.map +1 -0
  6. package/dist/base/index.d.ts +5 -0
  7. package/dist/base/index.d.ts.map +1 -0
  8. package/dist/base/index.js +5 -0
  9. package/dist/base/index.js.map +1 -0
  10. package/dist/browser/auto-init.d.ts +57 -0
  11. package/dist/browser/auto-init.d.ts.map +1 -0
  12. package/dist/browser/auto-init.js +304 -0
  13. package/dist/browser/auto-init.js.map +1 -0
  14. package/dist/browser/auto-translator.d.ts +98 -0
  15. package/dist/browser/auto-translator.d.ts.map +1 -0
  16. package/dist/browser/auto-translator.js +218 -0
  17. package/dist/browser/auto-translator.js.map +1 -0
  18. package/dist/browser/block-extractor.d.ts +26 -0
  19. package/dist/browser/block-extractor.d.ts.map +1 -0
  20. package/dist/browser/block-extractor.js +143 -0
  21. package/dist/browser/block-extractor.js.map +1 -0
  22. package/dist/browser/dom-observer.d.ts +42 -0
  23. package/dist/browser/dom-observer.d.ts.map +1 -0
  24. package/dist/browser/dom-observer.js +109 -0
  25. package/dist/browser/dom-observer.js.map +1 -0
  26. package/dist/browser/editor/editor-api.d.ts +67 -0
  27. package/dist/browser/editor/editor-api.d.ts.map +1 -0
  28. package/dist/browser/editor/editor-api.js +233 -0
  29. package/dist/browser/editor/editor-api.js.map +1 -0
  30. package/dist/browser/editor/editor-init.d.ts +11 -0
  31. package/dist/browser/editor/editor-init.d.ts.map +1 -0
  32. package/dist/browser/editor/editor-init.js +181 -0
  33. package/dist/browser/editor/editor-init.js.map +1 -0
  34. package/dist/browser/editor/editor-styles.d.ts +6 -0
  35. package/dist/browser/editor/editor-styles.d.ts.map +1 -0
  36. package/dist/browser/editor/editor-styles.js +419 -0
  37. package/dist/browser/editor/editor-styles.js.map +1 -0
  38. package/dist/browser/editor/inspector.d.ts +22 -0
  39. package/dist/browser/editor/inspector.d.ts.map +1 -0
  40. package/dist/browser/editor/inspector.js +125 -0
  41. package/dist/browser/editor/inspector.js.map +1 -0
  42. package/dist/browser/editor/key-resolver.d.ts +19 -0
  43. package/dist/browser/editor/key-resolver.d.ts.map +1 -0
  44. package/dist/browser/editor/key-resolver.js +49 -0
  45. package/dist/browser/editor/key-resolver.js.map +1 -0
  46. package/dist/browser/editor/live-preview.d.ts +10 -0
  47. package/dist/browser/editor/live-preview.d.ts.map +1 -0
  48. package/dist/browser/editor/live-preview.js +57 -0
  49. package/dist/browser/editor/live-preview.js.map +1 -0
  50. package/dist/browser/editor/sidebar.d.ts +50 -0
  51. package/dist/browser/editor/sidebar.d.ts.map +1 -0
  52. package/dist/browser/editor/sidebar.js +353 -0
  53. package/dist/browser/editor/sidebar.js.map +1 -0
  54. package/dist/browser/editor/toolbar.d.ts +29 -0
  55. package/dist/browser/editor/toolbar.d.ts.map +1 -0
  56. package/dist/browser/editor/toolbar.js +131 -0
  57. package/dist/browser/editor/toolbar.js.map +1 -0
  58. package/dist/browser/exclude-filter.d.ts +21 -0
  59. package/dist/browser/exclude-filter.d.ts.map +1 -0
  60. package/dist/browser/exclude-filter.js +51 -0
  61. package/dist/browser/exclude-filter.js.map +1 -0
  62. package/dist/browser/index.d.ts +32 -0
  63. package/dist/browser/index.d.ts.map +1 -0
  64. package/dist/browser/index.js +28 -0
  65. package/dist/browser/index.js.map +1 -0
  66. package/dist/browser/language-switcher.d.ts +70 -0
  67. package/dist/browser/language-switcher.d.ts.map +1 -0
  68. package/dist/browser/language-switcher.js +339 -0
  69. package/dist/browser/language-switcher.js.map +1 -0
  70. package/dist/browser/lionrapid.auto.dev.js +5757 -0
  71. package/dist/browser/lionrapid.auto.dev.js.map +7 -0
  72. package/dist/browser/lionrapid.auto.js +5757 -0
  73. package/dist/browser/lionrapid.auto.js.map +7 -0
  74. package/dist/browser/lionrapid.auto.min.js +80 -0
  75. package/dist/browser/lionrapid.auto.min.js.map +7 -0
  76. package/dist/browser/lionrapid.editor.js +1869 -0
  77. package/dist/browser/lionrapid.editor.js.map +7 -0
  78. package/dist/browser/lionrapid.editor.min.js +415 -0
  79. package/dist/browser/lionrapid.editor.min.js.map +7 -0
  80. package/dist/browser/lionrapid.esm.js +4786 -0
  81. package/dist/browser/lionrapid.esm.js.map +7 -0
  82. package/dist/browser/lionrapid.esm.min.js +3 -0
  83. package/dist/browser/lionrapid.esm.min.js.map +7 -0
  84. package/dist/browser/lionrapid.iife.dev.js +4809 -0
  85. package/dist/browser/lionrapid.iife.dev.js.map +7 -0
  86. package/dist/browser/lionrapid.iife.js +4809 -0
  87. package/dist/browser/lionrapid.iife.js.map +7 -0
  88. package/dist/browser/lionrapid.iife.min.js +3 -0
  89. package/dist/browser/lionrapid.iife.min.js.map +7 -0
  90. package/dist/browser/lionrapid.umd.js +4822 -0
  91. package/dist/browser/lionrapid.umd.js.map +7 -0
  92. package/dist/browser/lionrapid.umd.min.js +18 -0
  93. package/dist/browser/lionrapid.umd.min.js.map +7 -0
  94. package/dist/browser/markup-parser.d.ts +13 -0
  95. package/dist/browser/markup-parser.d.ts.map +1 -0
  96. package/dist/browser/markup-parser.js +56 -0
  97. package/dist/browser/markup-parser.js.map +1 -0
  98. package/dist/browser/md5.d.ts +16 -0
  99. package/dist/browser/md5.d.ts.map +1 -0
  100. package/dist/browser/md5.js +145 -0
  101. package/dist/browser/md5.js.map +1 -0
  102. package/dist/browser/storage/cache-storage-repository.d.ts +44 -0
  103. package/dist/browser/storage/cache-storage-repository.d.ts.map +1 -0
  104. package/dist/browser/storage/cache-storage-repository.js +186 -0
  105. package/dist/browser/storage/cache-storage-repository.js.map +1 -0
  106. package/dist/browser/storage/localstorage-repository.d.ts +51 -0
  107. package/dist/browser/storage/localstorage-repository.d.ts.map +1 -0
  108. package/dist/browser/storage/localstorage-repository.js +270 -0
  109. package/dist/browser/storage/localstorage-repository.js.map +1 -0
  110. package/dist/builder.d.ts +24 -0
  111. package/dist/builder.d.ts.map +1 -0
  112. package/dist/builder.js +169 -0
  113. package/dist/builder.js.map +1 -0
  114. package/dist/cache/CacheManager.d.ts +132 -0
  115. package/dist/cache/CacheManager.d.ts.map +1 -0
  116. package/dist/cache/CacheManager.js +277 -0
  117. package/dist/cache/CacheManager.js.map +1 -0
  118. package/dist/cache/index.d.ts +6 -0
  119. package/dist/cache/index.d.ts.map +1 -0
  120. package/dist/cache/index.js +5 -0
  121. package/dist/cache/index.js.map +1 -0
  122. package/dist/config/index.d.ts +6 -0
  123. package/dist/config/index.d.ts.map +1 -0
  124. package/dist/config/index.js +6 -0
  125. package/dist/config/index.js.map +1 -0
  126. package/dist/config/lionrapid.config.d.ts +12 -0
  127. package/dist/config/lionrapid.config.d.ts.map +1 -0
  128. package/dist/config/lionrapid.config.js +2 -0
  129. package/dist/config/lionrapid.config.js.map +1 -0
  130. package/dist/config/logger.config.d.ts +19 -0
  131. package/dist/config/logger.config.d.ts.map +1 -0
  132. package/dist/config/logger.config.js +9 -0
  133. package/dist/config/logger.config.js.map +1 -0
  134. package/dist/config/repository.config.d.ts +87 -0
  135. package/dist/config/repository.config.d.ts.map +1 -0
  136. package/dist/config/repository.config.js +5 -0
  137. package/dist/config/repository.config.js.map +1 -0
  138. package/dist/content/index.d.ts +20 -0
  139. package/dist/content/index.d.ts.map +1 -0
  140. package/dist/content/index.js +13 -0
  141. package/dist/content/index.js.map +1 -0
  142. package/dist/content/parser/IParser.d.ts +33 -0
  143. package/dist/content/parser/IParser.d.ts.map +1 -0
  144. package/dist/content/parser/IParser.js +6 -0
  145. package/dist/content/parser/IParser.js.map +1 -0
  146. package/dist/content/parser/MarkupParser.d.ts +67 -0
  147. package/dist/content/parser/MarkupParser.d.ts.map +1 -0
  148. package/dist/content/parser/MarkupParser.js +231 -0
  149. package/dist/content/parser/MarkupParser.js.map +1 -0
  150. package/dist/content/serializer/HTMLSerializer.d.ts +61 -0
  151. package/dist/content/serializer/HTMLSerializer.d.ts.map +1 -0
  152. package/dist/content/serializer/HTMLSerializer.js +100 -0
  153. package/dist/content/serializer/HTMLSerializer.js.map +1 -0
  154. package/dist/content/serializer/ISerializer.d.ts +33 -0
  155. package/dist/content/serializer/ISerializer.d.ts.map +1 -0
  156. package/dist/content/serializer/ISerializer.js +6 -0
  157. package/dist/content/serializer/ISerializer.js.map +1 -0
  158. package/dist/content/style-mapper/DefaultStyleMapper.d.ts +59 -0
  159. package/dist/content/style-mapper/DefaultStyleMapper.d.ts.map +1 -0
  160. package/dist/content/style-mapper/DefaultStyleMapper.js +128 -0
  161. package/dist/content/style-mapper/DefaultStyleMapper.js.map +1 -0
  162. package/dist/content/style-mapper/IStyleMapper.d.ts +50 -0
  163. package/dist/content/style-mapper/IStyleMapper.d.ts.map +1 -0
  164. package/dist/content/style-mapper/IStyleMapper.js +6 -0
  165. package/dist/content/style-mapper/IStyleMapper.js.map +1 -0
  166. package/dist/content/types.d.ts +39 -0
  167. package/dist/content/types.d.ts.map +1 -0
  168. package/dist/content/types.js +6 -0
  169. package/dist/content/types.js.map +1 -0
  170. package/dist/content/validator/ContentValidator.d.ts +57 -0
  171. package/dist/content/validator/ContentValidator.d.ts.map +1 -0
  172. package/dist/content/validator/ContentValidator.js +215 -0
  173. package/dist/content/validator/ContentValidator.js.map +1 -0
  174. package/dist/content/validator/IValidator.d.ts +82 -0
  175. package/dist/content/validator/IValidator.d.ts.map +1 -0
  176. package/dist/content/validator/IValidator.js +6 -0
  177. package/dist/content/validator/IValidator.js.map +1 -0
  178. package/dist/errors/translation-errors.d.ts +55 -0
  179. package/dist/errors/translation-errors.d.ts.map +1 -0
  180. package/dist/errors/translation-errors.js +85 -0
  181. package/dist/errors/translation-errors.js.map +1 -0
  182. package/dist/handlers/async-storage-handler.d.ts +77 -0
  183. package/dist/handlers/async-storage-handler.d.ts.map +1 -0
  184. package/dist/handlers/async-storage-handler.js +239 -0
  185. package/dist/handlers/async-storage-handler.js.map +1 -0
  186. package/dist/handlers/handler-chain.d.ts +79 -0
  187. package/dist/handlers/handler-chain.d.ts.map +1 -0
  188. package/dist/handlers/handler-chain.js +366 -0
  189. package/dist/handlers/handler-chain.js.map +1 -0
  190. package/dist/handlers/index.d.ts +7 -0
  191. package/dist/handlers/index.d.ts.map +1 -0
  192. package/dist/handlers/index.js +7 -0
  193. package/dist/handlers/index.js.map +1 -0
  194. package/dist/handlers/network-handler.d.ts +90 -0
  195. package/dist/handlers/network-handler.d.ts.map +1 -0
  196. package/dist/handlers/network-handler.js +283 -0
  197. package/dist/handlers/network-handler.js.map +1 -0
  198. package/dist/handlers/sync-storage-handler.d.ts +88 -0
  199. package/dist/handlers/sync-storage-handler.d.ts.map +1 -0
  200. package/dist/handlers/sync-storage-handler.js +270 -0
  201. package/dist/handlers/sync-storage-handler.js.map +1 -0
  202. package/dist/index.d.ts +37 -0
  203. package/dist/index.d.ts.map +1 -0
  204. package/dist/index.js +30 -0
  205. package/dist/index.js.map +1 -0
  206. package/dist/interfaces/emitter.interface.d.ts +33 -0
  207. package/dist/interfaces/emitter.interface.d.ts.map +1 -0
  208. package/dist/interfaces/emitter.interface.js +2 -0
  209. package/dist/interfaces/emitter.interface.js.map +1 -0
  210. package/dist/interfaces/handler.interface.d.ts +50 -0
  211. package/dist/interfaces/handler.interface.d.ts.map +1 -0
  212. package/dist/interfaces/handler.interface.js +5 -0
  213. package/dist/interfaces/handler.interface.js.map +1 -0
  214. package/dist/interfaces/index.d.ts +8 -0
  215. package/dist/interfaces/index.d.ts.map +1 -0
  216. package/dist/interfaces/index.js +8 -0
  217. package/dist/interfaces/index.js.map +1 -0
  218. package/dist/interfaces/plugin.interface.d.ts +49 -0
  219. package/dist/interfaces/plugin.interface.d.ts.map +1 -0
  220. package/dist/interfaces/plugin.interface.js +6 -0
  221. package/dist/interfaces/plugin.interface.js.map +1 -0
  222. package/dist/interfaces/repository.interface.d.ts +56 -0
  223. package/dist/interfaces/repository.interface.d.ts.map +1 -0
  224. package/dist/interfaces/repository.interface.js +2 -0
  225. package/dist/interfaces/repository.interface.js.map +1 -0
  226. package/dist/lionRapid.d.ts +225 -0
  227. package/dist/lionRapid.d.ts.map +1 -0
  228. package/dist/lionRapid.js +596 -0
  229. package/dist/lionRapid.js.map +1 -0
  230. package/dist/repositories/index.d.ts +3 -0
  231. package/dist/repositories/index.d.ts.map +1 -0
  232. package/dist/repositories/index.js +3 -0
  233. package/dist/repositories/index.js.map +1 -0
  234. package/dist/repositories/memory-repository.d.ts +30 -0
  235. package/dist/repositories/memory-repository.d.ts.map +1 -0
  236. package/dist/repositories/memory-repository.js +100 -0
  237. package/dist/repositories/memory-repository.js.map +1 -0
  238. package/dist/repositories/network-repository.d.ts +138 -0
  239. package/dist/repositories/network-repository.d.ts.map +1 -0
  240. package/dist/repositories/network-repository.js +516 -0
  241. package/dist/repositories/network-repository.js.map +1 -0
  242. package/dist/sync/missing-translation-queue.d.ts +64 -0
  243. package/dist/sync/missing-translation-queue.d.ts.map +1 -0
  244. package/dist/sync/missing-translation-queue.js +184 -0
  245. package/dist/sync/missing-translation-queue.js.map +1 -0
  246. package/dist/types/cache.types.d.ts +62 -0
  247. package/dist/types/cache.types.d.ts.map +1 -0
  248. package/dist/types/cache.types.js +6 -0
  249. package/dist/types/cache.types.js.map +1 -0
  250. package/dist/types/event-payloads.types.d.ts +46 -0
  251. package/dist/types/event-payloads.types.d.ts.map +1 -0
  252. package/dist/types/event-payloads.types.js +6 -0
  253. package/dist/types/event-payloads.types.js.map +1 -0
  254. package/dist/types/generated.types.d.ts +31 -0
  255. package/dist/types/generated.types.d.ts.map +1 -0
  256. package/dist/types/generated.types.js +6 -0
  257. package/dist/types/generated.types.js.map +1 -0
  258. package/dist/types/index.d.ts +7 -0
  259. package/dist/types/index.d.ts.map +1 -0
  260. package/dist/types/index.js +7 -0
  261. package/dist/types/index.js.map +1 -0
  262. package/dist/types/lionRapid.types.d.ts +169 -0
  263. package/dist/types/lionRapid.types.d.ts.map +1 -0
  264. package/dist/types/lionRapid.types.js +8 -0
  265. package/dist/types/lionRapid.types.js.map +1 -0
  266. package/dist/types/parameters.types.d.ts +36 -0
  267. package/dist/types/parameters.types.d.ts.map +1 -0
  268. package/dist/types/parameters.types.js +6 -0
  269. package/dist/types/parameters.types.js.map +1 -0
  270. package/dist/types/translation-data.types.d.ts +54 -0
  271. package/dist/types/translation-data.types.d.ts.map +1 -0
  272. package/dist/types/translation-data.types.js +6 -0
  273. package/dist/types/translation-data.types.js.map +1 -0
  274. package/dist/types/translation.types.d.ts +94 -0
  275. package/dist/types/translation.types.d.ts.map +1 -0
  276. package/dist/types/translation.types.js +5 -0
  277. package/dist/types/translation.types.js.map +1 -0
  278. package/dist/utils/context-utils.d.ts +23 -0
  279. package/dist/utils/context-utils.d.ts.map +1 -0
  280. package/dist/utils/context-utils.js +36 -0
  281. package/dist/utils/context-utils.js.map +1 -0
  282. package/dist/utils/index.d.ts +10 -0
  283. package/dist/utils/index.d.ts.map +1 -0
  284. package/dist/utils/index.js +19 -0
  285. package/dist/utils/index.js.map +1 -0
  286. package/dist/utils/logger.d.ts +229 -0
  287. package/dist/utils/logger.d.ts.map +1 -0
  288. package/dist/utils/logger.js +345 -0
  289. package/dist/utils/logger.js.map +1 -0
  290. package/dist/utils/namespace-utils.d.ts +30 -0
  291. package/dist/utils/namespace-utils.d.ts.map +1 -0
  292. package/dist/utils/namespace-utils.js +56 -0
  293. package/dist/utils/namespace-utils.js.map +1 -0
  294. package/dist/utils/translation-flow.d.ts +18 -0
  295. package/dist/utils/translation-flow.d.ts.map +1 -0
  296. package/dist/utils/translation-flow.js +74 -0
  297. package/dist/utils/translation-flow.js.map +1 -0
  298. package/dist/utils/translation-value-utils.d.ts +57 -0
  299. package/dist/utils/translation-value-utils.d.ts.map +1 -0
  300. package/dist/utils/translation-value-utils.js +194 -0
  301. package/dist/utils/translation-value-utils.js.map +1 -0
  302. package/package.json +76 -0
@@ -0,0 +1,4786 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/config/logger.config.ts
6
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
7
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
8
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
9
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
10
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
11
+ LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
12
+ return LogLevel2;
13
+ })(LogLevel || {});
14
+
15
+ // src/utils/logger.ts
16
+ var IS_PRODUCTION = true;
17
+ var IS_DEVELOPMENT = false;
18
+ var IS_CI = false;
19
+ var LIONRAPID_DEV = false;
20
+ var PERFORMANCE_TRACKING = false;
21
+ var LOG_LEVEL_ENV = IS_PRODUCTION ? "ERROR" : "DEBUG";
22
+ var Colors = {
23
+ // Reset
24
+ RESET: "\x1B[0m",
25
+ // Foreground colors
26
+ BLACK: "\x1B[30m",
27
+ RED: "\x1B[31m",
28
+ GREEN: "\x1B[32m",
29
+ YELLOW: "\x1B[33m",
30
+ BLUE: "\x1B[34m",
31
+ MAGENTA: "\x1B[35m",
32
+ CYAN: "\x1B[36m",
33
+ WHITE: "\x1B[37m",
34
+ ORANGE: "\x1B[38;5;208m",
35
+ // Bright foreground colors
36
+ BRIGHT_BLACK: "\x1B[90m",
37
+ BRIGHT_RED: "\x1B[91m",
38
+ BRIGHT_GREEN: "\x1B[92m",
39
+ BRIGHT_YELLOW: "\x1B[93m",
40
+ BRIGHT_BLUE: "\x1B[94m",
41
+ BRIGHT_MAGENTA: "\x1B[95m",
42
+ BRIGHT_CYAN: "\x1B[96m",
43
+ BRIGHT_WHITE: "\x1B[97m",
44
+ // Styles
45
+ BOLD: "\x1B[1m",
46
+ DIM: "\x1B[2m",
47
+ UNDERLINE: "\x1B[4m"
48
+ };
49
+ var DevelopmentLogger = class _DevelopmentLogger {
50
+ constructor(config = {}) {
51
+ __publicField(this, "config");
52
+ this.config = {
53
+ level: this.getLogLevelFromEnv(),
54
+ enabled: this.shouldEnableLogging(),
55
+ prefix: `${config.packageEmoji || "\u{1F4E6}"} ${config.packageName || "LionRapid"}`,
56
+ showLineNumbers: LIONRAPID_DEV && !IS_CI,
57
+ useColors: this.shouldUseColors(),
58
+ ...config
59
+ };
60
+ }
61
+ configure(config) {
62
+ this.config = { ...this.config, ...config };
63
+ }
64
+ debug(message, data) {
65
+ if (!this.config.enabled || this.config.level > 0 /* DEBUG */) return;
66
+ this.log(0 /* DEBUG */, message, data);
67
+ }
68
+ info(message, data) {
69
+ if (this.config.level > 1 /* INFO */) return;
70
+ this.log(1 /* INFO */, message, data);
71
+ }
72
+ warn(message, data) {
73
+ this.log(2 /* WARN */, message, data);
74
+ }
75
+ error(message, data) {
76
+ this.log(3 /* ERROR */, message, data);
77
+ }
78
+ time(label) {
79
+ if (!PERFORMANCE_TRACKING || IS_PRODUCTION) return () => {
80
+ };
81
+ const start = IS_DEVELOPMENT ? performance.now() : 0;
82
+ return () => {
83
+ if (IS_DEVELOPMENT) {
84
+ const duration = performance.now() - start;
85
+ this.debug(`\u23F1\uFE0F ${label}: ${duration.toFixed(2)}ms`, { duration });
86
+ }
87
+ };
88
+ }
89
+ log(level, message, data) {
90
+ if (!this.config.enabled || level < this.config.level) return;
91
+ const levelName = LogLevel[level];
92
+ const levelEmoji = this.getLevelEmoji(level);
93
+ const callSite = this.config.showLineNumbers ? this.getCallSite() : null;
94
+ const logMessage = this.buildColoredMessage(
95
+ level,
96
+ levelEmoji,
97
+ levelName,
98
+ message,
99
+ callSite,
100
+ data
101
+ );
102
+ switch (level) {
103
+ case 3 /* ERROR */:
104
+ console.error(logMessage);
105
+ break;
106
+ case 2 /* WARN */:
107
+ console.warn(logMessage);
108
+ break;
109
+ case 1 /* INFO */:
110
+ console.info(logMessage);
111
+ break;
112
+ default:
113
+ console.log(logMessage);
114
+ }
115
+ }
116
+ buildColoredMessage(level, emoji, levelName, message, callSite, data) {
117
+ if (!this.config.useColors) {
118
+ const locationInfo2 = callSite ? ` (${callSite.fileName}:${callSite.lineNumber})` : "";
119
+ let logMessage = `${this.config.prefix} ${emoji} ${levelName} ${message}${locationInfo2}`;
120
+ if (data !== void 0) {
121
+ logMessage += ` ${JSON.stringify(data)}`;
122
+ }
123
+ return logMessage;
124
+ }
125
+ const levelColor = this.getLevelColor(level);
126
+ const reset = Colors.RESET;
127
+ const coloredPrefix = `${Colors.BOLD}${Colors.ORANGE}${this.config.prefix}${reset}`;
128
+ const coloredEmoji = emoji;
129
+ const coloredLevel = `${levelColor}${Colors.BOLD}${levelName}${reset}`;
130
+ const coloredMessage = `${levelColor}${message}${reset}`;
131
+ const locationInfo = callSite ? `${Colors.DIM}${Colors.BRIGHT_BLACK} (${callSite.fileName}:${callSite.lineNumber})${reset}` : "";
132
+ const dataInfo = data !== void 0 ? `${Colors.DIM}${Colors.CYAN} ${JSON.stringify(data)}${reset}` : "";
133
+ return `${coloredPrefix} ${coloredEmoji} ${coloredLevel} ${coloredMessage}${locationInfo}${dataInfo}`;
134
+ }
135
+ getLevelColor(level) {
136
+ switch (level) {
137
+ case 0 /* DEBUG */:
138
+ return Colors.BRIGHT_GREEN;
139
+ case 1 /* INFO */:
140
+ return Colors.BRIGHT_CYAN;
141
+ case 2 /* WARN */:
142
+ return Colors.BRIGHT_YELLOW;
143
+ case 3 /* ERROR */:
144
+ return Colors.BRIGHT_RED;
145
+ default:
146
+ return Colors.BRIGHT_BLACK;
147
+ }
148
+ }
149
+ getLevelEmoji(level) {
150
+ switch (level) {
151
+ case 0 /* DEBUG */:
152
+ return "\u{1F50D}";
153
+ case 1 /* INFO */:
154
+ return "\u2139\uFE0F";
155
+ case 2 /* WARN */:
156
+ return "\u26A0\uFE0F";
157
+ case 3 /* ERROR */:
158
+ return "\u274C";
159
+ default:
160
+ return "\u{1F4DD}";
161
+ }
162
+ }
163
+ getCallSite() {
164
+ try {
165
+ const stack = new Error().stack;
166
+ if (!stack) return null;
167
+ const lines = stack.split("\n");
168
+ for (let i = 3; i < lines.length; i++) {
169
+ const line = lines[i];
170
+ if (line && !line.includes("logger.ts") && !line.includes("Logger")) {
171
+ return this.parseStackLine(line);
172
+ }
173
+ }
174
+ } catch {
175
+ }
176
+ return null;
177
+ }
178
+ parseStackLine(line) {
179
+ let match = line.match(/at\s+(.+?)\s+\((.+):(\d+):(\d+)\)/);
180
+ if (match) {
181
+ return {
182
+ functionName: match[1],
183
+ fileName: this.extractFileName(match[2]),
184
+ lineNumber: parseInt(match[3], 10)
185
+ };
186
+ }
187
+ match = line.match(/at\s+(.+):(\d+):(\d+)/);
188
+ if (match) {
189
+ return {
190
+ fileName: this.extractFileName(match[1]),
191
+ lineNumber: parseInt(match[2], 10)
192
+ };
193
+ }
194
+ return null;
195
+ }
196
+ extractFileName(filePath) {
197
+ const parts = filePath.split("/");
198
+ return parts[parts.length - 1] || filePath;
199
+ }
200
+ colorize(text, color) {
201
+ if (!this.config.useColors) return text;
202
+ return `${Colors[color]}${text}${Colors.RESET}`;
203
+ }
204
+ getColors() {
205
+ return Colors;
206
+ }
207
+ createPackageLogger(packageName, packageEmoji) {
208
+ return new _DevelopmentLogger({
209
+ ...this.config,
210
+ packageName,
211
+ packageEmoji: packageEmoji || "\u{1F4E6}"
212
+ });
213
+ }
214
+ getLogLevelFromEnv() {
215
+ switch (LOG_LEVEL_ENV.toUpperCase()) {
216
+ case "DEBUG":
217
+ return 0 /* DEBUG */;
218
+ case "INFO":
219
+ return 1 /* INFO */;
220
+ case "WARN":
221
+ return 2 /* WARN */;
222
+ case "ERROR":
223
+ return 3 /* ERROR */;
224
+ case "NONE":
225
+ return 4 /* NONE */;
226
+ default:
227
+ return 0 /* DEBUG */;
228
+ }
229
+ }
230
+ shouldEnableLogging() {
231
+ if (LOG_LEVEL_ENV === "NONE") return false;
232
+ return true;
233
+ }
234
+ shouldUseColors() {
235
+ if (IS_CI) return false;
236
+ if (typeof window !== "undefined") {
237
+ return true;
238
+ }
239
+ if (typeof process !== "undefined" && void 0) {
240
+ return false;
241
+ }
242
+ return IS_DEVELOPMENT;
243
+ }
244
+ };
245
+ var noop = () => {
246
+ };
247
+ var noopWithReturn = () => () => {
248
+ };
249
+ var logger = IS_PRODUCTION ? {
250
+ configure: noop,
251
+ debug: noop,
252
+ // ← This eliminates the call completely
253
+ info: noop,
254
+ // ← This eliminates the call completely
255
+ warn: (message, data) => {
256
+ console.warn(`\u{1F981} LionRapid Core \u26A0\uFE0F ${message}`, data || "");
257
+ },
258
+ error: (message, data) => {
259
+ console.error(`\u{1F981} LionRapid Core \u274C ${message}`, data || "");
260
+ },
261
+ time: noopWithReturn,
262
+ colorize: (text) => text,
263
+ getColors: () => Colors,
264
+ createPackageLogger: (packageName, packageEmoji) => ({
265
+ configure: noop,
266
+ debug: noop,
267
+ // ← Package logger debug also eliminated
268
+ info: noop,
269
+ // ← Package logger info also eliminated
270
+ warn: (message, data) => {
271
+ const prefix = `${packageEmoji || "\u{1F4E6}"} ${packageName}`;
272
+ console.warn(`${prefix} \u26A0\uFE0F ${message}`, data || "");
273
+ },
274
+ error: (message, data) => {
275
+ const prefix = `${packageEmoji || "\u{1F4E6}"} ${packageName}`;
276
+ console.error(`${prefix} \u274C ${message}`, data || "");
277
+ },
278
+ time: noopWithReturn,
279
+ colorize: (text) => text,
280
+ getColors: () => Colors,
281
+ createPackageLogger: noop
282
+ })
283
+ } : new DevelopmentLogger({
284
+ packageName: "LionRapid Core",
285
+ packageEmoji: "\u{1F981}"
286
+ });
287
+ var Logger = IS_PRODUCTION ? function Logger2() {
288
+ return logger;
289
+ } : DevelopmentLogger;
290
+ var createPackageLogger = (config) => {
291
+ if (IS_PRODUCTION) {
292
+ const prefix = `${config.packageEmoji || "\u{1F4E6}"} ${config.packageName || "LionRapid"}`;
293
+ return {
294
+ configure: noop,
295
+ debug: noop,
296
+ // ← All debug calls eliminated
297
+ info: noop,
298
+ // ← All info calls eliminated
299
+ warn: (message, data) => {
300
+ console.warn(`${prefix} \u26A0\uFE0F ${message}`, data || "");
301
+ },
302
+ error: (message, data) => {
303
+ console.error(`${prefix} \u274C ${message}`, data || "");
304
+ },
305
+ time: noopWithReturn,
306
+ colorize: (text) => text,
307
+ getColors: () => Colors,
308
+ createPackageLogger: noop
309
+ };
310
+ }
311
+ return new DevelopmentLogger(config);
312
+ };
313
+ var measure = (name) => {
314
+ if (IS_PRODUCTION) {
315
+ return {
316
+ start: noop,
317
+ end: noop,
318
+ elapsed: () => 0
319
+ };
320
+ }
321
+ let startTime;
322
+ return {
323
+ start: () => {
324
+ if (IS_DEVELOPMENT) {
325
+ startTime = performance.now();
326
+ logger.debug(`\u23F1\uFE0F ${name} - started`);
327
+ }
328
+ },
329
+ end: () => {
330
+ if (IS_DEVELOPMENT) {
331
+ const duration = performance.now() - startTime;
332
+ logger.debug(`\u23F1\uFE0F ${name} - completed in ${duration.toFixed(2)}ms`);
333
+ return duration;
334
+ }
335
+ return 0;
336
+ },
337
+ elapsed: () => IS_DEVELOPMENT ? performance.now() - startTime : 0
338
+ };
339
+ };
340
+
341
+ // src/base/event-emitter.ts
342
+ var EventEmitter = class {
343
+ constructor() {
344
+ // ✅ Properly typed event listeners without `any`
345
+ __publicField(this, "eventListeners", /* @__PURE__ */ new Map());
346
+ __publicField(this, "debugMode", false);
347
+ }
348
+ /**
349
+ * Subscribe to events (PUBLIC API) - Fully type-safe
350
+ */
351
+ on(event, listener) {
352
+ if (!this.eventListeners.has(event)) {
353
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
354
+ }
355
+ const listeners = this.eventListeners.get(event);
356
+ listeners.add(listener);
357
+ }
358
+ /**
359
+ * Unsubscribe from events (PUBLIC API) - Fully type-safe
360
+ */
361
+ off(event, listener) {
362
+ const listeners = this.eventListeners.get(event);
363
+ if (listeners) {
364
+ listeners.delete(listener);
365
+ }
366
+ }
367
+ /**
368
+ * ✅ Type-safe emit with proper generic constraint
369
+ */
370
+ emit(event, envelope) {
371
+ const listeners = this.eventListeners.get(event);
372
+ if (listeners) {
373
+ listeners.forEach((listener) => {
374
+ try {
375
+ listener(envelope);
376
+ } catch (error) {
377
+ console.error(`Error in event listener for ${String(event)}:`, error);
378
+ }
379
+ });
380
+ }
381
+ }
382
+ hasListeners(event) {
383
+ return this.eventListeners.has(event) && this.eventListeners.get(event).size > 0;
384
+ }
385
+ clearEvents() {
386
+ this.eventListeners.clear();
387
+ }
388
+ /**
389
+ * ✅ Create base context once per request
390
+ */
391
+ createContext(requestId, trace, extra) {
392
+ return {
393
+ requestId,
394
+ timestamp: performance.now(),
395
+ trace,
396
+ ...extra
397
+ };
398
+ }
399
+ /**
400
+ * ✅ Type-safe event emission helper
401
+ */
402
+ emitEvent(event, payload, requestId, trace) {
403
+ this.emit(event, {
404
+ context: this.createContext(requestId, trace),
405
+ payload
406
+ });
407
+ }
408
+ /**
409
+ * ✅ Translation event builder with proper typing
410
+ */
411
+ translation(request, handler) {
412
+ const baseTrace = ["translation"];
413
+ const handlerTrace = handler ? [...baseTrace, handler] : baseTrace;
414
+ return {
415
+ request: () => {
416
+ this.emit("translation:request", {
417
+ context: this.createContext(request.id, [...baseTrace, "request"]),
418
+ payload: { key: request.cleanKey }
419
+ });
420
+ },
421
+ success: (value) => {
422
+ this.emit("translation:success", {
423
+ context: this.createContext(request.id, [...handlerTrace, "success"]),
424
+ payload: { key: request.cleanKey, value }
425
+ });
426
+ },
427
+ fallback: (value) => {
428
+ this.emit("translation:fallback", {
429
+ context: this.createContext(request.id, [...baseTrace, "fallback"]),
430
+ payload: { key: request.cleanKey, value }
431
+ });
432
+ },
433
+ missing: (fallbackValue, locale, namespace) => {
434
+ this.emit("translation:missing", {
435
+ context: this.createContext(request.id, [...baseTrace, "missing"]),
436
+ payload: {
437
+ key: request.cleanKey,
438
+ fallbackValue,
439
+ locale,
440
+ namespace
441
+ }
442
+ });
443
+ },
444
+ notFound: (fallbackValue, locale, namespace) => {
445
+ this.emit("translation:not-found", {
446
+ context: this.createContext(request.id, [...baseTrace, "not-found"]),
447
+ payload: {
448
+ key: request.cleanKey,
449
+ fallbackValue,
450
+ locale,
451
+ namespace,
452
+ status: 404
453
+ }
454
+ });
455
+ },
456
+ locked: (fallbackValue, locale, namespace) => {
457
+ this.emit("translation:locked", {
458
+ context: this.createContext(request.id, [...baseTrace, "locked"]),
459
+ payload: {
460
+ key: request.cleanKey,
461
+ fallbackValue,
462
+ locale,
463
+ namespace,
464
+ status: 423
465
+ }
466
+ });
467
+ }
468
+ };
469
+ }
470
+ /**
471
+ * ✅ Repository event builder with better typing
472
+ */
473
+ repo(request, source) {
474
+ const baseTrace = ["repo", source];
475
+ return {
476
+ getStart: () => {
477
+ if (typeof request === "string") return;
478
+ this.emit("repo:get:start", {
479
+ context: this.createContext(request.id, [
480
+ ...baseTrace,
481
+ "get",
482
+ "start"
483
+ ]),
484
+ payload: { key: request.cleanKey, source }
485
+ });
486
+ },
487
+ getSuccess: (value) => {
488
+ if (typeof request === "string") return;
489
+ this.emit("repo:get:success", {
490
+ context: this.createContext(request.id, [
491
+ ...baseTrace,
492
+ "get",
493
+ "success"
494
+ ]),
495
+ payload: { key: request.cleanKey, value, source }
496
+ });
497
+ },
498
+ getMiss: () => {
499
+ if (typeof request === "string") return;
500
+ this.emit("repo:get:miss", {
501
+ context: this.createContext(request.id, [
502
+ ...baseTrace,
503
+ "get",
504
+ "miss"
505
+ ]),
506
+ payload: { key: request.cleanKey, source }
507
+ });
508
+ },
509
+ setStart: (value) => {
510
+ if (typeof request === "string") return;
511
+ this.emit("repo:set:start", {
512
+ context: this.createContext(request.id, [
513
+ ...baseTrace,
514
+ "set",
515
+ "start"
516
+ ]),
517
+ payload: { key: request.cleanKey, value, source }
518
+ });
519
+ },
520
+ setSuccess: (value) => {
521
+ const id = typeof request === "string" ? "system" : request.id;
522
+ const key = typeof request === "string" ? request : request.cleanKey;
523
+ this.emit("repo:set:success", {
524
+ context: this.createContext(id, [...baseTrace, "set", "success"]),
525
+ payload: { key, value, source }
526
+ });
527
+ },
528
+ error: (error) => {
529
+ if (typeof request === "string") return;
530
+ this.emit("repo:error", {
531
+ context: this.createContext(request.id, [...baseTrace, "error"]),
532
+ payload: { key: request.cleanKey, source, error }
533
+ });
534
+ }
535
+ };
536
+ }
537
+ /**
538
+ * ✅ Network event builder
539
+ */
540
+ network(request, method) {
541
+ const key = typeof request === "string" ? request : request.cleanKey;
542
+ const requestId = typeof request === "string" ? "network" : request.id;
543
+ const baseTrace = ["network", method || "request"];
544
+ return {
545
+ start: () => {
546
+ this.emit("network:request:start", {
547
+ context: this.createContext(requestId, [...baseTrace, "start"]),
548
+ payload: { key }
549
+ });
550
+ },
551
+ success: (value) => {
552
+ this.emit("network:request:success", {
553
+ context: this.createContext(requestId, [...baseTrace, "success"]),
554
+ payload: { key, value, method: method || "GET" }
555
+ });
556
+ },
557
+ error: (error, status) => {
558
+ this.emit("network:request:error", {
559
+ context: this.createContext(requestId, [...baseTrace, "error"]),
560
+ payload: {
561
+ key,
562
+ method: method || "GET",
563
+ error,
564
+ ...status && { status }
565
+ }
566
+ });
567
+ },
568
+ retry: (attempt, delayMs, reason) => {
569
+ this.emit("network:retry", {
570
+ context: this.createContext(requestId, [...baseTrace, "retry"]),
571
+ payload: { key, attempt, delayMs, ...reason && { reason } }
572
+ });
573
+ }
574
+ };
575
+ }
576
+ /**
577
+ * ✅ Builder event builder
578
+ */
579
+ builder() {
580
+ const baseTrace = ["builder"];
581
+ const systemId = `build-${Date.now()}`;
582
+ return {
583
+ use: (type) => {
584
+ this.emit("builder:use", {
585
+ context: this.createContext(systemId, [...baseTrace, "use"]),
586
+ payload: { type }
587
+ });
588
+ },
589
+ buildStart: (locale, namespace) => {
590
+ this.emit("builder:build:start", {
591
+ context: this.createContext(systemId, [
592
+ ...baseTrace,
593
+ "build",
594
+ "start"
595
+ ]),
596
+ payload: { locale, namespace }
597
+ });
598
+ },
599
+ buildDone: (repositories, plugins) => {
600
+ this.emit("builder:build:done", {
601
+ context: this.createContext(systemId, [
602
+ ...baseTrace,
603
+ "build",
604
+ "done"
605
+ ]),
606
+ payload: { repositories, plugins }
607
+ });
608
+ }
609
+ };
610
+ }
611
+ /**
612
+ * ✅ Language change event builder
613
+ */
614
+ language(oldLocale, newLocale) {
615
+ const requestId = `lang-change-${Date.now()}`;
616
+ const baseTrace = ["language", "change"];
617
+ return {
618
+ start: () => {
619
+ this.emit("language:change:start", {
620
+ context: this.createContext(requestId, [...baseTrace, "start"]),
621
+ payload: { oldLocale, newLocale }
622
+ });
623
+ },
624
+ complete: () => {
625
+ this.emit("language:change:success", {
626
+ context: this.createContext(requestId, [...baseTrace, "complete"]),
627
+ payload: { oldLocale, newLocale }
628
+ });
629
+ },
630
+ error: (error) => {
631
+ this.emit("language:change:error", {
632
+ context: this.createContext(requestId, [...baseTrace, "error"]),
633
+ payload: { oldLocale, newLocale, error }
634
+ });
635
+ }
636
+ };
637
+ }
638
+ /**
639
+ * ✅ Cache operations (debug only)
640
+ */
641
+ cache(operation, reason, stats) {
642
+ if (!this.debugMode) return { start: () => {
643
+ }, complete: () => {
644
+ } };
645
+ return {
646
+ start: () => {
647
+ logger.debug(`\u{1F9F9} Cache ${operation} starting: ${reason}`);
648
+ },
649
+ complete: () => {
650
+ logger.debug(
651
+ `\u2705 Cache ${operation} complete: ${stats?.clearedCount || 0} entries`
652
+ );
653
+ }
654
+ };
655
+ }
656
+ };
657
+
658
+ // src/handlers/handler-chain.ts
659
+ var HandlerChain = class {
660
+ constructor(eventEmitter) {
661
+ __publicField(this, "asyncHandlers", []);
662
+ __publicField(this, "syncHandlers", []);
663
+ __publicField(this, "bus");
664
+ this.bus = eventEmitter;
665
+ }
666
+ addSyncHandler(handler) {
667
+ if (!handler.enabled) return;
668
+ this.syncHandlers.push(handler);
669
+ this.sortHandlersIfNeeded(this.syncHandlers);
670
+ logger.debug(`Sync handler added: ${handler.name}`);
671
+ }
672
+ handleSync(request) {
673
+ for (const handler of this.syncHandlers) {
674
+ try {
675
+ const result = handler.lookup(request);
676
+ if (result !== null && result.value) {
677
+ logger.debug(`Sync hit: ${handler.name} (${result.duration}ms)`);
678
+ return result;
679
+ }
680
+ } catch (error) {
681
+ logger.warn(`Sync lookup failed in ${handler.name}:`, error);
682
+ const repoBus = this.bus.repo(request, handler.name);
683
+ repoBus.error(error);
684
+ continue;
685
+ }
686
+ }
687
+ return null;
688
+ }
689
+ saveToMemoryHandlers(request, result, _sourceHandler) {
690
+ const memoryHandlers = this.syncHandlers;
691
+ if (memoryHandlers.length === 0) return;
692
+ logger.debug(`Saving to ${memoryHandlers.length} memory handlers`);
693
+ memoryHandlers.forEach((handler) => {
694
+ try {
695
+ handler.save(request, result);
696
+ logger.debug(`Saved to memory: ${handler.name}`);
697
+ } catch (error) {
698
+ logger.warn(`Failed to save to memory ${handler.name}:`, error);
699
+ const repoBus = this.bus.repo(request, handler.name);
700
+ repoBus.error(error);
701
+ }
702
+ });
703
+ }
704
+ saveNamespaceToMemoryHandlers(translations, context) {
705
+ const memoryHandlers = this.syncHandlers.filter(
706
+ (h) => h.type === "memory" && h.saveNamespace
707
+ );
708
+ if (memoryHandlers.length === 0) return;
709
+ logger.debug(
710
+ `Saving namespace to ${memoryHandlers.length} memory handlers`
711
+ );
712
+ memoryHandlers.forEach((handler) => {
713
+ try {
714
+ handler.saveNamespace(translations, context);
715
+ logger.debug(`Namespace saved to memory: ${handler.name}`, {
716
+ locale: context.locale,
717
+ namespace: context.namespace
718
+ });
719
+ } catch (error) {
720
+ logger.warn(
721
+ `Failed to save namespace to memory ${handler.name}:`,
722
+ error
723
+ );
724
+ const repoBus = this.bus.repo(
725
+ `${context.locale}:${context.namespace}`,
726
+ handler.name
727
+ );
728
+ repoBus.error(error);
729
+ }
730
+ });
731
+ }
732
+ addAsyncHandler(handler) {
733
+ if (!handler.enabled) return;
734
+ this.asyncHandlers.push(handler);
735
+ this.sortHandlersIfNeeded(this.asyncHandlers);
736
+ logger.debug(`Async handler added: ${handler.name}`);
737
+ }
738
+ async handleAsync(request) {
739
+ for (const handler of this.asyncHandlers) {
740
+ try {
741
+ logger.debug(`Trying handler: ${handler.name}`);
742
+ const result = await handler.lookup(request);
743
+ if (result.value && result.status === 200) {
744
+ logger.debug(`Async hit: ${handler.name} (${result.duration}ms)`);
745
+ if (result.namespaceLoaded) {
746
+ logger.debug(
747
+ `Saving loaded namespace: ${result.namespaceLoaded.locale}:${result.namespaceLoaded.namespace}`
748
+ );
749
+ await this.saveNamespaceToHandlers(result.namespaceLoaded.data, {
750
+ locale: result.namespaceLoaded.locale,
751
+ namespace: result.namespaceLoaded.namespace
752
+ });
753
+ return result;
754
+ }
755
+ await this.saveToStorageHandlers(request, result, handler);
756
+ this.saveToMemoryHandlers(request, result, handler);
757
+ return result;
758
+ }
759
+ logger.debug(
760
+ `Handler ${handler.name} returned fallback (status: ${result.status})`
761
+ );
762
+ } catch (error) {
763
+ logger.warn(`Handler ${handler.name} failed:`, error);
764
+ const repoBus = this.bus.repo(request, handler.name);
765
+ repoBus.error(error);
766
+ continue;
767
+ }
768
+ }
769
+ logger.debug(`No async handlers found translation for ${request.cleanKey}`);
770
+ return null;
771
+ }
772
+ /**
773
+ * Save translation to all storage handlers that come before the source handler
774
+ */
775
+ async saveToStorageHandlers(request, result, sourceHandler) {
776
+ const sourceIndex = this.asyncHandlers.indexOf(sourceHandler);
777
+ const storageHandlers = this.asyncHandlers.slice(0, sourceIndex).filter((h) => h.type === "storage");
778
+ if (storageHandlers.length === 0) return;
779
+ logger.debug(`Saving to ${storageHandlers.length} storage handlers`);
780
+ const savePromises = storageHandlers.map(async (handler) => {
781
+ try {
782
+ await handler.save(request, result);
783
+ logger.debug(`Saved to storage: ${handler.name}`);
784
+ } catch (error) {
785
+ logger.warn(`Failed to save to storage ${handler.name}:`, error);
786
+ const repoBus = this.bus.repo(request, handler.name);
787
+ repoBus.error(error);
788
+ }
789
+ });
790
+ await Promise.allSettled(savePromises);
791
+ }
792
+ async saveNamespaceToStorageHandlers(translations, context) {
793
+ const storageHandlers = this.asyncHandlers.filter(
794
+ (h) => h.type === "storage" && h.saveNamespace
795
+ );
796
+ if (storageHandlers.length === 0) return;
797
+ logger.debug(
798
+ `Saving namespace to ${storageHandlers.length} storage handlers`
799
+ );
800
+ const savePromises = storageHandlers.map(async (handler) => {
801
+ try {
802
+ await handler.saveNamespace(translations, context);
803
+ logger.debug(`Namespace saved to storage: ${handler.name}`, {
804
+ locale: context.locale,
805
+ namespace: context.namespace
806
+ });
807
+ } catch (error) {
808
+ logger.warn(
809
+ `Failed to save namespace to storage ${handler.name}:`,
810
+ error
811
+ );
812
+ const repoBus = this.bus.repo(
813
+ `${context.locale}:${context.namespace}`,
814
+ handler.name
815
+ );
816
+ repoBus.error(error);
817
+ }
818
+ });
819
+ await Promise.allSettled(savePromises);
820
+ }
821
+ async saveNamespaceToHandlers(translations, context) {
822
+ this.saveNamespaceToMemoryHandlers(translations, context);
823
+ await this.saveNamespaceToStorageHandlers(translations, context);
824
+ }
825
+ getSyncHandlers() {
826
+ return [...this.syncHandlers];
827
+ }
828
+ getAsyncHandlers() {
829
+ return [...this.asyncHandlers];
830
+ }
831
+ /**
832
+ * Get chain info for debugging
833
+ */
834
+ getChainInfo() {
835
+ return {
836
+ length: this.syncHandlers.length + this.asyncHandlers.length,
837
+ handlers: [
838
+ ...this.syncHandlers.map((h) => ({
839
+ name: h.name,
840
+ priority: h.priority,
841
+ type: h.type,
842
+ enabled: h.enabled
843
+ })),
844
+ ...this.asyncHandlers.map((h) => ({
845
+ name: h.name,
846
+ priority: h.priority,
847
+ type: h.type,
848
+ enabled: h.enabled
849
+ }))
850
+ ]
851
+ };
852
+ }
853
+ /**
854
+ * Check if any handler has cached data
855
+ */
856
+ isCached(request) {
857
+ return this.syncHandlers.some((h) => h.isCached?.(request) === true) || this.asyncHandlers.some((h) => h.isCached?.(request) === true);
858
+ }
859
+ /**
860
+ * Get combined cache entries
861
+ */
862
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
863
+ getEntries() {
864
+ const entries = {};
865
+ const allHandlers = [...this.syncHandlers, ...this.asyncHandlers];
866
+ for (const handler of allHandlers) {
867
+ if (handler.getEntries) {
868
+ entries[handler.name] = handler.getEntries();
869
+ }
870
+ }
871
+ return entries;
872
+ }
873
+ /**
874
+ * Destroy all handlers
875
+ */
876
+ async destroy() {
877
+ const allHandlers = [...this.syncHandlers, ...this.asyncHandlers];
878
+ const destroyPromises = allHandlers.map(async (handler) => {
879
+ try {
880
+ await handler.destroy?.();
881
+ logger.debug(`Handler destroyed: ${handler.name}`);
882
+ } catch (error) {
883
+ logger.warn(`Failed to destroy handler: ${handler.name}`, error);
884
+ }
885
+ });
886
+ await Promise.allSettled(destroyPromises);
887
+ this.syncHandlers = [];
888
+ this.asyncHandlers = [];
889
+ }
890
+ sortHandlersIfNeeded(handlers) {
891
+ const hasExplicitPriorities = handlers.some((h) => h.priority > 0);
892
+ if (hasExplicitPriorities) {
893
+ handlers.sort((a, b) => {
894
+ const priorityA = a.priority || Number.MAX_SAFE_INTEGER;
895
+ const priorityB = b.priority || Number.MAX_SAFE_INTEGER;
896
+ return priorityA - priorityB;
897
+ });
898
+ }
899
+ }
900
+ async loadNamespace(locale, namespace) {
901
+ for (const handler of this.asyncHandlers) {
902
+ if (handler.type !== "network") continue;
903
+ try {
904
+ logger.debug(
905
+ `Trying to load namespace: ${locale}:${namespace} from ${handler.name}`
906
+ );
907
+ const result = await handler.loadNamespace?.(locale, namespace);
908
+ if (result && result.data && result.status === 200) {
909
+ logger.debug(
910
+ `Loaded namespace: ${locale}:${namespace} from ${handler.name}`
911
+ );
912
+ await this.saveNamespaceToStorageHandlers(result.data, {
913
+ namespace,
914
+ locale
915
+ });
916
+ this.saveNamespaceToMemoryHandlers(result.data, {
917
+ namespace,
918
+ locale
919
+ });
920
+ return result.data;
921
+ }
922
+ } catch (error) {
923
+ logger.warn(
924
+ `Failed to load namespace: ${locale}:${namespace} from ${handler.name}`,
925
+ error
926
+ );
927
+ const repoBus = this.bus.repo(`${locale}:${namespace}`, handler.name);
928
+ repoBus.error(error);
929
+ continue;
930
+ }
931
+ }
932
+ logger.debug(`No handlers found namespace: ${locale}:${namespace}`);
933
+ return null;
934
+ }
935
+ clearCache() {
936
+ const allHandlers = [...this.syncHandlers, ...this.asyncHandlers];
937
+ for (const handler of allHandlers) {
938
+ try {
939
+ handler.destroy?.();
940
+ logger.debug(`Cache cleared for handler: ${handler.name}`);
941
+ } catch (error) {
942
+ logger.warn(
943
+ `Failed to clear cache for handler: ${handler.name}`,
944
+ error
945
+ );
946
+ }
947
+ }
948
+ logger.debug("Cache cleared for all handlers");
949
+ }
950
+ /**
951
+ * Clear cache entries for a specific namespace
952
+ */
953
+ clearNamespace(locale, namespace) {
954
+ for (const handler of this.syncHandlers) {
955
+ try {
956
+ handler.clearNamespace?.(locale, namespace);
957
+ } catch (error) {
958
+ logger.warn(
959
+ `Failed to clear namespace for handler: ${handler.name}`,
960
+ error
961
+ );
962
+ }
963
+ }
964
+ logger.debug(`Cleared namespace: ${locale}:${namespace}`);
965
+ }
966
+ /**
967
+ * Set a value directly in the cache (for optimistic updates)
968
+ * Bypasses the normal handler flow to directly set values in memory handlers
969
+ * Stores as ContentUnit (text + styles) for consistency
970
+ *
971
+ * @param key - Full cache key (e.g., 'en:app:welcome')
972
+ * @param value - Translation value
973
+ * @param styles - Optional styles for indexed tags
974
+ */
975
+ setCache(key, value, styles) {
976
+ for (const handler of this.syncHandlers) {
977
+ if (handler.type === "memory" && handler.setCache) {
978
+ try {
979
+ handler.setCache(key, value, styles);
980
+ } catch (error) {
981
+ logger.warn(`Failed to set cache in handler: ${handler.name}`, error);
982
+ }
983
+ }
984
+ }
985
+ logger.debug(`Cache set: ${key}`);
986
+ }
987
+ /**
988
+ * Get namespace data from cache for ContentUnit style lookup
989
+ *
990
+ * Used by t() to retrieve styles for indexed tags at render time.
991
+ *
992
+ * @param namespaceCacheKey - Cache key in format @locale:namespace
993
+ * @returns Parsed namespace data or null if not found
994
+ */
995
+ getNamespaceData(namespaceCacheKey) {
996
+ for (const handler of this.syncHandlers) {
997
+ if (handler.type === "memory") {
998
+ try {
999
+ const entries = handler.getEntries?.();
1000
+ if (entries && entries[namespaceCacheKey]) {
1001
+ const data = entries[namespaceCacheKey];
1002
+ if (typeof data === "string") {
1003
+ return JSON.parse(data);
1004
+ }
1005
+ return data;
1006
+ }
1007
+ } catch (error) {
1008
+ logger.debug(`Error getting namespace data from ${handler.name}:`, error);
1009
+ }
1010
+ }
1011
+ }
1012
+ return null;
1013
+ }
1014
+ };
1015
+
1016
+ // src/utils/namespace-utils.ts
1017
+ function parseNamespaceKey(key) {
1018
+ const colonIndex = key.indexOf(":");
1019
+ if (colonIndex > 0 && colonIndex < key.length - 1) {
1020
+ const namespace = key.substring(0, colonIndex);
1021
+ const cleanKey = key.substring(colonIndex + 1);
1022
+ return {
1023
+ originalKey: key,
1024
+ namespace,
1025
+ cleanKey,
1026
+ hasExplicitNamespace: true
1027
+ };
1028
+ }
1029
+ return {
1030
+ originalKey: key,
1031
+ cleanKey: key,
1032
+ hasExplicitNamespace: false
1033
+ };
1034
+ }
1035
+
1036
+ // src/utils/context-utils.ts
1037
+ function createTranslationContext(parsedKey, currentLocale, defaultNamespace, fallbackLocale) {
1038
+ const effectiveNamespace = parsedKey.hasExplicitNamespace ? parsedKey.namespace : defaultNamespace;
1039
+ return {
1040
+ locale: currentLocale,
1041
+ namespace: effectiveNamespace,
1042
+ fallbackLocale: fallbackLocale || currentLocale
1043
+ };
1044
+ }
1045
+
1046
+ // src/utils/translation-value-utils.ts
1047
+ function resolveNestedKey(namespace, keyPath) {
1048
+ if (!namespace || !keyPath) {
1049
+ return void 0;
1050
+ }
1051
+ if (!keyPath.includes(".") && !keyPath.includes("[")) {
1052
+ return namespace[keyPath];
1053
+ }
1054
+ try {
1055
+ const keys = parseKeyPath(keyPath);
1056
+ let current = namespace;
1057
+ for (const key of keys) {
1058
+ if (current === null || current === void 0) {
1059
+ logger.debug(
1060
+ `\u{1F50D} Nested key resolution stopped at null/undefined for: ${keyPath}`
1061
+ );
1062
+ return void 0;
1063
+ }
1064
+ if (typeof key === "number") {
1065
+ if (!Array.isArray(current)) {
1066
+ logger.debug(
1067
+ `\u{1F50D} Expected array for index ${key} in path: ${keyPath}`
1068
+ );
1069
+ return void 0;
1070
+ }
1071
+ current = current[key];
1072
+ } else {
1073
+ if (typeof current !== "object") {
1074
+ logger.debug(
1075
+ `\u{1F50D} Expected object for key '${key}' in path: ${keyPath}`
1076
+ );
1077
+ return void 0;
1078
+ }
1079
+ current = current[key];
1080
+ }
1081
+ }
1082
+ logger.debug(
1083
+ `\u{1F50D} Nested key resolved: ${keyPath} \u2192 ${typeof current === "object" ? "[Object]" : current}`
1084
+ );
1085
+ return current;
1086
+ } catch (error) {
1087
+ logger.debug(`\u{1F50D} Nested key resolution error for '${keyPath}':`, error);
1088
+ return void 0;
1089
+ }
1090
+ }
1091
+ function parseKeyPath(keyPath) {
1092
+ const keys = [];
1093
+ let currentKey = "";
1094
+ let inBrackets = false;
1095
+ for (let i = 0; i < keyPath.length; i++) {
1096
+ const char = keyPath[i];
1097
+ if (char === "." && !inBrackets) {
1098
+ if (currentKey) {
1099
+ keys.push(currentKey);
1100
+ currentKey = "";
1101
+ }
1102
+ } else if (char === "[") {
1103
+ if (currentKey) {
1104
+ keys.push(currentKey);
1105
+ currentKey = "";
1106
+ }
1107
+ inBrackets = true;
1108
+ } else if (char === "]" && inBrackets) {
1109
+ if (currentKey) {
1110
+ const index = parseInt(currentKey, 10);
1111
+ if (!isNaN(index)) {
1112
+ keys.push(index);
1113
+ } else {
1114
+ keys.push(currentKey);
1115
+ }
1116
+ currentKey = "";
1117
+ }
1118
+ inBrackets = false;
1119
+ } else {
1120
+ currentKey += char;
1121
+ }
1122
+ }
1123
+ if (currentKey) {
1124
+ keys.push(currentKey);
1125
+ }
1126
+ return keys;
1127
+ }
1128
+ function createIndividualCacheKey(locale, namespace, key) {
1129
+ const parts = [locale];
1130
+ if (namespace) {
1131
+ parts.push(namespace);
1132
+ }
1133
+ parts.push(key);
1134
+ return parts.join(":");
1135
+ }
1136
+ function createNamespaceCacheKey(locale, namespace) {
1137
+ return `@${locale}:${namespace}`;
1138
+ }
1139
+ function flattenTranslations(translations, prefix = "", locale, namespace) {
1140
+ const flattened = {};
1141
+ for (const [key, value] of Object.entries(translations)) {
1142
+ const fullKey = prefix ? `${prefix}.${key}` : key;
1143
+ const cacheKey = createIndividualCacheKey(locale, namespace, fullKey);
1144
+ if (typeof value === "object" && value !== null && "text" in value && "styles" in value) {
1145
+ flattened[cacheKey] = JSON.stringify(value);
1146
+ } else if (typeof value === "object" && value !== null) {
1147
+ Object.assign(
1148
+ flattened,
1149
+ flattenTranslations(value, fullKey, locale, namespace)
1150
+ );
1151
+ } else {
1152
+ flattened[cacheKey] = JSON.stringify({ text: String(value), styles: {} });
1153
+ }
1154
+ }
1155
+ return flattened;
1156
+ }
1157
+
1158
+ // src/utils/translation-flow.ts
1159
+ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace, fallbackLocale) {
1160
+ const startTime = performance.now();
1161
+ const { defaultValue, params } = parseTranslationParams(
1162
+ defaultValueOrParams,
1163
+ paramsOrDefaultValue
1164
+ );
1165
+ const parsedKey = parseNamespaceKey(key);
1166
+ const context = createTranslationContext(
1167
+ parsedKey,
1168
+ currentLocale,
1169
+ defaultNamespace,
1170
+ fallbackLocale
1171
+ );
1172
+ const individualCacheKey = createIndividualCacheKey(
1173
+ context.locale,
1174
+ context.namespace,
1175
+ parsedKey.cleanKey
1176
+ );
1177
+ const namespaceCacheKey = context.namespace ? createNamespaceCacheKey(context.locale, context.namespace) : void 0;
1178
+ const id = `${individualCacheKey}-${startTime}`;
1179
+ return {
1180
+ id,
1181
+ // Unique request ID
1182
+ originalKey: key,
1183
+ originalParams: defaultValueOrParams,
1184
+ originalDefaultValue: paramsOrDefaultValue,
1185
+ cleanKey: parsedKey.cleanKey,
1186
+ hasExplicitNamespace: parsedKey.hasExplicitNamespace,
1187
+ parsedParams: params,
1188
+ parsedDefaultValue: defaultValue,
1189
+ locale: currentLocale,
1190
+ namespace: context.namespace,
1191
+ // ✅ Use effective namespace (explicit or default)
1192
+ fallbackLocale,
1193
+ individualCacheKey,
1194
+ // ✅ Ready-to-use cache key
1195
+ namespaceCacheKey,
1196
+ // ✅ Ready-to-use namespace key
1197
+ startTime
1198
+ };
1199
+ }
1200
+ function parseTranslationParams(secondParam, thirdParam) {
1201
+ let defaultValue;
1202
+ let params;
1203
+ if (typeof secondParam === "object" && typeof thirdParam === "object" && false) {
1204
+ console.warn(
1205
+ "[LionRapid] Warning: Both secondParam and thirdParam are objects. Expected pattern: t(key, params, defaultValue) or t(key, defaultValue, params). Second object will be ignored as it cannot be a defaultValue."
1206
+ );
1207
+ }
1208
+ if (typeof secondParam === "string") {
1209
+ defaultValue = secondParam;
1210
+ if (typeof thirdParam === "object") {
1211
+ params = thirdParam;
1212
+ }
1213
+ } else if (typeof secondParam === "object") {
1214
+ params = secondParam;
1215
+ if (typeof thirdParam === "string") {
1216
+ defaultValue = thirdParam;
1217
+ }
1218
+ }
1219
+ return { defaultValue, params };
1220
+ }
1221
+
1222
+ // src/handlers/sync-storage-handler.ts
1223
+ var SyncHandler = class {
1224
+ constructor(eventEmitter, repository) {
1225
+ __publicField(this, "name");
1226
+ __publicField(this, "version", "1.0.0");
1227
+ __publicField(this, "priority");
1228
+ __publicField(this, "type", "memory");
1229
+ __publicField(this, "enabled");
1230
+ __publicField(this, "ttlMap", /* @__PURE__ */ new Map());
1231
+ __publicField(this, "ttl");
1232
+ __publicField(this, "repository");
1233
+ __publicField(this, "bus");
1234
+ this.bus = eventEmitter;
1235
+ this.repository = repository;
1236
+ this.name = repository.name;
1237
+ const config = repository.getConfig();
1238
+ this.enabled = config.enabled ?? true;
1239
+ this.priority = config.priority ?? 0;
1240
+ this.ttl = config.options?.ttl ?? 30 * 60 * 1e3;
1241
+ }
1242
+ getSourceType() {
1243
+ return "memory";
1244
+ }
1245
+ /**
1246
+ * Simple lookup implementation
1247
+ * Returns ContentUnit with both text and styles
1248
+ */
1249
+ lookup(request) {
1250
+ const startTime = Date.now();
1251
+ logger.debug(`Sync lookup: ${request.cleanKey}`);
1252
+ const repoBus = this.bus.repo(request, this.name);
1253
+ repoBus.getStart();
1254
+ const directResult = this.tryDirectLookup(request);
1255
+ if (directResult !== null) {
1256
+ logger.debug(`Cache hit: ${request.cleanKey}`);
1257
+ repoBus.getSuccess(directResult.text);
1258
+ return this.createResult(directResult, "memory", 200, startTime, true);
1259
+ }
1260
+ if (request.cleanKey.includes(".") && request.namespace) {
1261
+ const namespaceResult = this.tryNamespaceLookup(request);
1262
+ if (namespaceResult !== null) {
1263
+ logger.debug(`Namespace hit: ${request.cleanKey}`);
1264
+ repoBus.getSuccess(namespaceResult.text);
1265
+ return this.createResult(
1266
+ namespaceResult,
1267
+ "memory",
1268
+ 200,
1269
+ startTime,
1270
+ true
1271
+ );
1272
+ }
1273
+ }
1274
+ logger.debug(`Cache miss: ${request.cleanKey}`);
1275
+ repoBus.getMiss();
1276
+ return this.createResult(
1277
+ { text: request.parsedDefaultValue || request.cleanKey, styles: {} },
1278
+ "fallback",
1279
+ 404,
1280
+ startTime
1281
+ );
1282
+ }
1283
+ /**
1284
+ * Simple save implementation
1285
+ * Stores ContentUnit (text + styles) as JSON
1286
+ */
1287
+ save(request, result) {
1288
+ if (!result.value) return;
1289
+ const repoBus = this.bus.repo(request, this.name);
1290
+ const contentUnit = {
1291
+ text: result.value,
1292
+ styles: result.styles || {}
1293
+ };
1294
+ this.setWithTTL(request.individualCacheKey, JSON.stringify(contentUnit));
1295
+ logger.debug(`Saved to cache: ${request.cleanKey}`);
1296
+ repoBus.setSuccess(result.value);
1297
+ }
1298
+ /**
1299
+ * Namespace saving implementation
1300
+ */
1301
+ saveNamespace(translations, context) {
1302
+ if (!context.namespace) return;
1303
+ const repoBus = this.bus.repo(
1304
+ context.locale + ":" + context.namespace,
1305
+ this.name
1306
+ );
1307
+ const namespaceCacheKey = createNamespaceCacheKey(
1308
+ context.locale,
1309
+ context.namespace
1310
+ );
1311
+ this.setWithTTL(namespaceCacheKey, JSON.stringify(translations));
1312
+ const flattened = flattenTranslations(
1313
+ translations,
1314
+ "",
1315
+ context.locale,
1316
+ context.namespace
1317
+ );
1318
+ for (const [flatKey, flatValue] of Object.entries(flattened)) {
1319
+ this.setWithTTL(flatKey, flatValue);
1320
+ }
1321
+ repoBus.setSuccess(JSON.stringify(translations));
1322
+ }
1323
+ /**
1324
+ * ✅ Strategy 1: Direct cache lookup using unified pattern
1325
+ * Returns parsed ContentUnit (text + styles) or null
1326
+ */
1327
+ tryDirectLookup(request) {
1328
+ if (this.checkAndExpireTTL(request.individualCacheKey)) {
1329
+ return null;
1330
+ }
1331
+ const cachedValue = this.repository.get(request.individualCacheKey);
1332
+ if (!cachedValue) return null;
1333
+ this.refreshTTL(request.individualCacheKey);
1334
+ try {
1335
+ const parsed = JSON.parse(cachedValue);
1336
+ if (typeof parsed === "object" && "text" in parsed) {
1337
+ return {
1338
+ text: String(parsed.text),
1339
+ styles: parsed.styles || {}
1340
+ };
1341
+ }
1342
+ return { text: cachedValue, styles: {} };
1343
+ } catch {
1344
+ return { text: cachedValue, styles: {} };
1345
+ }
1346
+ }
1347
+ /**
1348
+ * ✅ Strategy 2: Namespace lookup using unified pattern
1349
+ * Returns parsed ContentUnit (text + styles) or null
1350
+ */
1351
+ tryNamespaceLookup(request) {
1352
+ if (!request.namespace) return null;
1353
+ const namespaceCacheKey = createNamespaceCacheKey(
1354
+ request.locale,
1355
+ request.namespace
1356
+ );
1357
+ if (this.checkAndExpireTTL(namespaceCacheKey)) {
1358
+ return null;
1359
+ }
1360
+ const namespaceData = this.repository.get(namespaceCacheKey);
1361
+ if (!namespaceData) return null;
1362
+ this.refreshTTL(namespaceCacheKey);
1363
+ try {
1364
+ const translations = JSON.parse(namespaceData);
1365
+ const value = resolveNestedKey(translations, request.cleanKey);
1366
+ if (value !== null && value !== void 0) {
1367
+ let contentUnit;
1368
+ if (typeof value === "object" && value !== null && "text" in value) {
1369
+ contentUnit = {
1370
+ text: String(value.text),
1371
+ styles: value.styles || {}
1372
+ };
1373
+ } else {
1374
+ contentUnit = { text: String(value), styles: {} };
1375
+ }
1376
+ this.setWithTTL(request.individualCacheKey, JSON.stringify(contentUnit));
1377
+ logger.debug(`Cached from namespace: ${request.cleanKey}`);
1378
+ return contentUnit;
1379
+ }
1380
+ } catch (error) {
1381
+ logger.debug(
1382
+ `Invalid JSON in namespace cache: ${namespaceCacheKey}`,
1383
+ error
1384
+ );
1385
+ this.repository.remove(namespaceCacheKey);
1386
+ this.ttlMap.delete(namespaceCacheKey);
1387
+ }
1388
+ return null;
1389
+ }
1390
+ /**
1391
+ * Helper to create consistent TranslationResult objects
1392
+ * Now includes styles from ContentUnit
1393
+ */
1394
+ createResult(contentUnit, source, status, startTime, cached) {
1395
+ return {
1396
+ value: contentUnit.text,
1397
+ styles: contentUnit.styles,
1398
+ source,
1399
+ status,
1400
+ duration: Date.now() - startTime,
1401
+ ...cached !== void 0 && { cached }
1402
+ };
1403
+ }
1404
+ /**
1405
+ * Check TTL and expire if needed
1406
+ * Returns true if expired, false if still valid
1407
+ */
1408
+ checkAndExpireTTL(cacheKey) {
1409
+ const timestamp = this.ttlMap.get(cacheKey);
1410
+ if (timestamp && Date.now() - timestamp > this.ttl) {
1411
+ this.repository.remove(cacheKey);
1412
+ this.ttlMap.delete(cacheKey);
1413
+ logger.debug(`TTL expired: ${cacheKey}`);
1414
+ return true;
1415
+ }
1416
+ return false;
1417
+ }
1418
+ /**
1419
+ * Set value in repository and update TTL
1420
+ */
1421
+ setWithTTL(cacheKey, value) {
1422
+ this.repository.set(cacheKey, value);
1423
+ this.ttlMap.set(cacheKey, Date.now());
1424
+ }
1425
+ /**
1426
+ * Refresh TTL on access
1427
+ */
1428
+ refreshTTL(cacheKey) {
1429
+ this.ttlMap.set(cacheKey, Date.now());
1430
+ }
1431
+ /**
1432
+ * Get TTL information for debugging
1433
+ */
1434
+ getTTLInfo() {
1435
+ const ttlInfo = {};
1436
+ const now = Date.now();
1437
+ for (const [key, timestamp] of this.ttlMap.entries()) {
1438
+ const age = now - timestamp;
1439
+ const remaining = Math.max(0, this.ttl - age);
1440
+ ttlInfo[key] = { timestamp, age, remaining };
1441
+ }
1442
+ return ttlInfo;
1443
+ }
1444
+ getEntries() {
1445
+ if (this.repository.getEntries !== void 0) {
1446
+ return this.repository.getEntries();
1447
+ }
1448
+ }
1449
+ /**
1450
+ * Clear all entries for a specific namespace
1451
+ * Removes namespace cache entry, individual keys, and TTL entries
1452
+ */
1453
+ clearNamespace(locale, namespace) {
1454
+ const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
1455
+ const keyPrefix = `${locale}:${namespace}:`;
1456
+ this.repository.remove(namespaceCacheKey);
1457
+ this.ttlMap.delete(namespaceCacheKey);
1458
+ const entries = this.repository.getEntries?.();
1459
+ if (entries) {
1460
+ for (const key of Object.keys(entries)) {
1461
+ if (key.startsWith(keyPrefix)) {
1462
+ this.repository.remove(key);
1463
+ this.ttlMap.delete(key);
1464
+ }
1465
+ }
1466
+ }
1467
+ logger.debug(`Cleared namespace: ${locale}:${namespace}`);
1468
+ }
1469
+ destroy() {
1470
+ this.repository.clear();
1471
+ this.ttlMap.clear();
1472
+ }
1473
+ /**
1474
+ * Direct cache set (for optimistic updates)
1475
+ * Bypasses event emission for direct cache manipulation
1476
+ * Stores as ContentUnit JSON (text + styles)
1477
+ */
1478
+ setCache(key, value, styles) {
1479
+ const contentUnit = {
1480
+ text: value,
1481
+ styles: styles || {}
1482
+ };
1483
+ this.setWithTTL(key, JSON.stringify(contentUnit));
1484
+ logger.debug(`Direct cache set: ${key}`);
1485
+ }
1486
+ };
1487
+
1488
+ // src/handlers/async-storage-handler.ts
1489
+ var AsyncHandler = class {
1490
+ constructor(eventEmitter, repository) {
1491
+ __publicField(this, "name");
1492
+ __publicField(this, "version", "1.0.0");
1493
+ __publicField(this, "priority");
1494
+ __publicField(this, "type", "storage");
1495
+ __publicField(this, "enabled");
1496
+ __publicField(this, "ttlMap", /* @__PURE__ */ new Map());
1497
+ __publicField(this, "ttl");
1498
+ __publicField(this, "repository");
1499
+ __publicField(this, "bus");
1500
+ this.bus = eventEmitter;
1501
+ this.repository = repository;
1502
+ this.name = repository.name;
1503
+ const config = repository.getConfig();
1504
+ this.enabled = config.enabled ?? true;
1505
+ this.priority = config.priority ?? 0;
1506
+ this.ttl = config.options?.ttl ?? 30 * 60 * 1e3;
1507
+ }
1508
+ getSourceType() {
1509
+ return "memory";
1510
+ }
1511
+ /**
1512
+ * Simple lookup implementation
1513
+ * Returns ContentUnit with both text and styles
1514
+ */
1515
+ async lookup(request) {
1516
+ const startTime = Date.now();
1517
+ logger.debug(`Async lookup: ${request.cleanKey}`);
1518
+ const repoBus = this.bus.repo(request, this.name);
1519
+ const directResult = await this.tryDirectLookup(request);
1520
+ if (directResult !== null) {
1521
+ logger.debug(`Cache hit: ${request.cleanKey}`);
1522
+ repoBus.getSuccess(directResult.text);
1523
+ return this.createResult(directResult, "storage", 200, startTime, true);
1524
+ }
1525
+ if (request.cleanKey.includes(".") && request.namespace) {
1526
+ const namespaceResult = await this.tryNamespaceLookup(request);
1527
+ if (namespaceResult !== null) {
1528
+ logger.debug(`Namespace hit: ${request.cleanKey}`);
1529
+ repoBus.getSuccess(namespaceResult.text);
1530
+ return this.createResult(
1531
+ namespaceResult,
1532
+ "storage",
1533
+ 200,
1534
+ startTime,
1535
+ true
1536
+ );
1537
+ }
1538
+ }
1539
+ logger.debug(`Cache miss: ${request.cleanKey}`);
1540
+ repoBus.getMiss();
1541
+ return this.createResult(
1542
+ { text: request.parsedDefaultValue || request.cleanKey, styles: {} },
1543
+ "fallback",
1544
+ 404,
1545
+ startTime
1546
+ );
1547
+ }
1548
+ /**
1549
+ * Simple save implementation
1550
+ * Stores ContentUnit (text + styles) as JSON
1551
+ */
1552
+ async save(request, result) {
1553
+ if (!result.value) return;
1554
+ const repoBus = this.bus.repo(request, this.name);
1555
+ const contentUnit = {
1556
+ text: result.value,
1557
+ styles: result.styles || {}
1558
+ };
1559
+ await this.setWithTTL(request.individualCacheKey, JSON.stringify(contentUnit));
1560
+ logger.debug(`Saved to cache: ${request.cleanKey}`);
1561
+ repoBus.setSuccess(result.value);
1562
+ }
1563
+ /**
1564
+ * Namespace saving implementation
1565
+ */
1566
+ async saveNamespace(translations, context) {
1567
+ if (!context.namespace) return;
1568
+ const repoBus = this.bus.repo(
1569
+ context.locale + ":" + context.namespace,
1570
+ this.name
1571
+ );
1572
+ const keyCount = Object.keys(translations).length;
1573
+ logger.debug(
1574
+ `Saving namespace: ${context.locale}:${context.namespace} (${keyCount} keys)`
1575
+ );
1576
+ const namespaceCacheKey = createNamespaceCacheKey(
1577
+ context.locale,
1578
+ context.namespace
1579
+ );
1580
+ await this.setWithTTL(namespaceCacheKey, JSON.stringify(translations));
1581
+ const flattened = flattenTranslations(
1582
+ translations,
1583
+ "",
1584
+ context.locale,
1585
+ context.namespace
1586
+ );
1587
+ let savedCount = 0;
1588
+ for (const [flatKey, flatValue] of Object.entries(flattened)) {
1589
+ await this.setWithTTL(flatKey, flatValue);
1590
+ savedCount++;
1591
+ }
1592
+ logger.debug(`Saved: ${savedCount} keys + namespace blob`);
1593
+ repoBus.setSuccess(JSON.stringify(translations));
1594
+ }
1595
+ /**
1596
+ * ✅ Strategy 1: Direct cache lookup using unified pattern
1597
+ * Returns parsed ContentUnit (text + styles) or null
1598
+ */
1599
+ async tryDirectLookup(request) {
1600
+ if (await this.checkAndExpireTTL(request.individualCacheKey)) {
1601
+ return null;
1602
+ }
1603
+ const cachedValue = await this.repository.get(request.individualCacheKey);
1604
+ if (!cachedValue) return null;
1605
+ this.refreshTTL(request.individualCacheKey);
1606
+ try {
1607
+ const parsed = JSON.parse(cachedValue);
1608
+ if (typeof parsed === "object" && "text" in parsed) {
1609
+ return {
1610
+ text: String(parsed.text),
1611
+ styles: parsed.styles || {}
1612
+ };
1613
+ }
1614
+ return { text: cachedValue, styles: {} };
1615
+ } catch {
1616
+ return { text: cachedValue, styles: {} };
1617
+ }
1618
+ }
1619
+ /**
1620
+ * ✅ Strategy 2: Namespace lookup using unified pattern
1621
+ * Returns parsed ContentUnit (text + styles) or null
1622
+ */
1623
+ async tryNamespaceLookup(request) {
1624
+ if (!request.namespace) return null;
1625
+ const namespaceCacheKey = createNamespaceCacheKey(
1626
+ request.locale,
1627
+ request.namespace
1628
+ );
1629
+ if (await this.checkAndExpireTTL(namespaceCacheKey)) {
1630
+ return null;
1631
+ }
1632
+ const namespaceData = await this.repository.get(namespaceCacheKey);
1633
+ if (!namespaceData) return null;
1634
+ this.refreshTTL(namespaceCacheKey);
1635
+ try {
1636
+ const translations = JSON.parse(namespaceData);
1637
+ const value = resolveNestedKey(translations, request.cleanKey);
1638
+ if (value !== null && value !== void 0) {
1639
+ let contentUnit;
1640
+ if (typeof value === "object" && value !== null && "text" in value) {
1641
+ contentUnit = {
1642
+ text: String(value.text),
1643
+ styles: value.styles || {}
1644
+ };
1645
+ } else {
1646
+ contentUnit = { text: String(value), styles: {} };
1647
+ }
1648
+ await this.setWithTTL(request.individualCacheKey, JSON.stringify(contentUnit));
1649
+ logger.debug(`Cached from namespace: ${request.cleanKey}`);
1650
+ return contentUnit;
1651
+ }
1652
+ } catch (error) {
1653
+ logger.debug(
1654
+ `Invalid JSON in namespace cache: ${namespaceCacheKey}`,
1655
+ error
1656
+ );
1657
+ await this.repository.remove(namespaceCacheKey);
1658
+ this.ttlMap.delete(namespaceCacheKey);
1659
+ }
1660
+ return null;
1661
+ }
1662
+ /**
1663
+ * Helper to create consistent TranslationResult objects
1664
+ * Now includes styles from ContentUnit
1665
+ */
1666
+ createResult(contentUnit, source, status, startTime, cached) {
1667
+ return {
1668
+ value: contentUnit.text,
1669
+ styles: contentUnit.styles,
1670
+ source,
1671
+ status,
1672
+ duration: Date.now() - startTime,
1673
+ ...cached !== void 0 && { cached }
1674
+ };
1675
+ }
1676
+ /**
1677
+ * Check TTL and expire if needed (async version)
1678
+ * Returns true if expired, false if still valid
1679
+ */
1680
+ async checkAndExpireTTL(cacheKey) {
1681
+ const timestamp = this.ttlMap.get(cacheKey);
1682
+ if (timestamp && Date.now() - timestamp > this.ttl) {
1683
+ await this.repository.remove(cacheKey);
1684
+ this.ttlMap.delete(cacheKey);
1685
+ logger.debug(`TTL expired: ${cacheKey}`);
1686
+ return true;
1687
+ }
1688
+ return false;
1689
+ }
1690
+ /**
1691
+ * Set value in repository and update TTL (async version)
1692
+ */
1693
+ async setWithTTL(cacheKey, value) {
1694
+ await this.repository.set(cacheKey, value);
1695
+ this.ttlMap.set(cacheKey, Date.now());
1696
+ }
1697
+ /**
1698
+ * Refresh TTL on access
1699
+ */
1700
+ refreshTTL(cacheKey) {
1701
+ this.ttlMap.set(cacheKey, Date.now());
1702
+ }
1703
+ /**
1704
+ * Get TTL information for debugging
1705
+ */
1706
+ getTTLInfo() {
1707
+ const ttlInfo = {};
1708
+ const now = Date.now();
1709
+ for (const [key, timestamp] of this.ttlMap.entries()) {
1710
+ const age = now - timestamp;
1711
+ const remaining = Math.max(0, this.ttl - age);
1712
+ ttlInfo[key] = { timestamp, age, remaining };
1713
+ }
1714
+ return ttlInfo;
1715
+ }
1716
+ async destroy() {
1717
+ await this.repository.clear();
1718
+ this.ttlMap.clear();
1719
+ }
1720
+ getEntries() {
1721
+ if (this.repository.getEntries !== void 0) {
1722
+ return this.repository.getEntries();
1723
+ }
1724
+ }
1725
+ };
1726
+
1727
+ // src/errors/translation-errors.ts
1728
+ var TranslationError = class extends Error {
1729
+ constructor(key, status, message) {
1730
+ super(message);
1731
+ this.key = key;
1732
+ this.status = status;
1733
+ this.name = this.constructor.name;
1734
+ if (Error.captureStackTrace) {
1735
+ Error.captureStackTrace(this, this.constructor);
1736
+ }
1737
+ }
1738
+ };
1739
+ var TranslationNotFoundError = class extends TranslationError {
1740
+ constructor(key) {
1741
+ super(key, 404, `Translation not found: ${key}`);
1742
+ }
1743
+ };
1744
+ var TranslationLockedError = class extends TranslationError {
1745
+ constructor(key, retryAfter) {
1746
+ super(key, 423, `Translation locked: ${key}`);
1747
+ this.retryAfter = retryAfter;
1748
+ }
1749
+ };
1750
+ var ServerError = class extends TranslationError {
1751
+ constructor(key, status) {
1752
+ super(key, status, `Server error ${status}: ${key}`);
1753
+ }
1754
+ };
1755
+ var NetworkError = class extends Error {
1756
+ constructor(message, cause) {
1757
+ super(message);
1758
+ this.cause = cause;
1759
+ this.name = "NetworkError";
1760
+ if (Error.captureStackTrace) {
1761
+ Error.captureStackTrace(this, this.constructor);
1762
+ }
1763
+ }
1764
+ };
1765
+ function isTranslationError(error) {
1766
+ return error instanceof TranslationError;
1767
+ }
1768
+ function isRetryableError(error) {
1769
+ return error instanceof ServerError || error instanceof NetworkError;
1770
+ }
1771
+ function isClientError(error) {
1772
+ return error instanceof TranslationNotFoundError || error instanceof TranslationLockedError;
1773
+ }
1774
+
1775
+ // src/handlers/network-handler.ts
1776
+ var NetworkHandler = class {
1777
+ constructor(eventEmitter, repository) {
1778
+ __publicField(this, "repository");
1779
+ __publicField(this, "name");
1780
+ __publicField(this, "priority");
1781
+ __publicField(this, "version", "1.0.0");
1782
+ __publicField(this, "type", "network");
1783
+ __publicField(this, "enabled");
1784
+ __publicField(this, "bus");
1785
+ __publicField(this, "retryCount");
1786
+ __publicField(this, "retryDelay");
1787
+ this.bus = eventEmitter;
1788
+ this.repository = repository;
1789
+ this.name = repository.name;
1790
+ const config = repository.getConfig();
1791
+ this.enabled = config.enabled ?? false;
1792
+ this.priority = config.priority ?? 0;
1793
+ this.retryCount = config.options?.retries ?? 3;
1794
+ this.retryDelay = config.options?.retryDelay ?? 1e3;
1795
+ }
1796
+ initialize() {
1797
+ return Promise.resolve();
1798
+ }
1799
+ getSourceType() {
1800
+ return "network";
1801
+ }
1802
+ /**
1803
+ * ✅ Lookup translation with full error handling
1804
+ * Strategy: Key first, namespace as fallback
1805
+ * Returns TranslationResult with status, source, duration, styles, and optional error
1806
+ */
1807
+ async lookup(request) {
1808
+ const startTime = Date.now();
1809
+ const fallbackValue = request.parsedDefaultValue || request.cleanKey;
1810
+ if (!this.enabled) {
1811
+ return this.createTranslationResult(
1812
+ fallbackValue,
1813
+ "fallback",
1814
+ 0,
1815
+ startTime
1816
+ );
1817
+ }
1818
+ const networkBus = this.bus.network(request, this.name);
1819
+ networkBus.start();
1820
+ logger.debug(`Network lookup: ${request.cleanKey}`);
1821
+ try {
1822
+ const contentUnit = await this.lookupContentUnitWithRetry(
1823
+ request.individualCacheKey
1824
+ );
1825
+ logger.debug(`Network hit: ${request.cleanKey}`);
1826
+ networkBus.success(contentUnit.text);
1827
+ return this.createTranslationResult(
1828
+ contentUnit.text,
1829
+ "network",
1830
+ 200,
1831
+ startTime,
1832
+ void 0,
1833
+ false,
1834
+ void 0,
1835
+ contentUnit.styles
1836
+ );
1837
+ } catch (error) {
1838
+ if (error instanceof TranslationNotFoundError && request.namespace) {
1839
+ logger.debug(
1840
+ `Individual key not found, trying namespace: ${request.cleanKey}`
1841
+ );
1842
+ return this.lookupFromNamespace(request, fallbackValue, startTime, networkBus);
1843
+ }
1844
+ if (error instanceof TranslationLockedError) {
1845
+ logger.debug(`Translation locked (423): ${request.cleanKey}`);
1846
+ return this.handleTranslationLocked(
1847
+ request,
1848
+ fallbackValue,
1849
+ error,
1850
+ startTime,
1851
+ networkBus
1852
+ );
1853
+ }
1854
+ logger.debug(`Network error: ${request.cleanKey}`, error);
1855
+ networkBus.error(error);
1856
+ return this.createTranslationResult(
1857
+ fallbackValue,
1858
+ "fallback",
1859
+ 0,
1860
+ startTime,
1861
+ error
1862
+ );
1863
+ }
1864
+ }
1865
+ /**
1866
+ * Strategy 2: Namespace fallback when individual key not found
1867
+ */
1868
+ async lookupFromNamespace(request, fallbackValue, startTime, networkBus) {
1869
+ try {
1870
+ const namespaceResult = await this.loadNamespace(
1871
+ request.locale,
1872
+ request.namespace
1873
+ );
1874
+ if (namespaceResult && namespaceResult.data) {
1875
+ const nestedValue = resolveNestedKey(
1876
+ namespaceResult.data,
1877
+ request.cleanKey
1878
+ );
1879
+ if (nestedValue !== void 0 && nestedValue !== null) {
1880
+ let resolvedValue;
1881
+ let styles = {};
1882
+ if (typeof nestedValue === "object" && nestedValue !== null && "text" in nestedValue) {
1883
+ const contentUnit = nestedValue;
1884
+ resolvedValue = String(contentUnit.text);
1885
+ styles = contentUnit.styles || {};
1886
+ } else {
1887
+ resolvedValue = String(nestedValue);
1888
+ }
1889
+ logger.debug(`Network namespace hit: ${request.cleanKey}, hasStyles=${Object.keys(styles).length > 0}`);
1890
+ networkBus.success(resolvedValue);
1891
+ return this.createTranslationResult(
1892
+ resolvedValue,
1893
+ "network",
1894
+ 200,
1895
+ startTime,
1896
+ void 0,
1897
+ false,
1898
+ {
1899
+ locale: request.locale,
1900
+ namespace: request.namespace,
1901
+ data: namespaceResult.data
1902
+ },
1903
+ styles
1904
+ );
1905
+ }
1906
+ }
1907
+ const error = new TranslationNotFoundError(request.cleanKey);
1908
+ return this.handleTranslationNotFound(
1909
+ request,
1910
+ fallbackValue,
1911
+ error,
1912
+ startTime
1913
+ );
1914
+ } catch (nsError) {
1915
+ logger.debug(`Namespace load failed: ${request.cleanKey}`, nsError);
1916
+ if (nsError instanceof TranslationNotFoundError) {
1917
+ return this.handleTranslationNotFound(
1918
+ request,
1919
+ fallbackValue,
1920
+ nsError,
1921
+ startTime
1922
+ );
1923
+ }
1924
+ if (nsError instanceof TranslationLockedError) {
1925
+ return this.handleTranslationLocked(
1926
+ request,
1927
+ fallbackValue,
1928
+ nsError,
1929
+ startTime,
1930
+ networkBus
1931
+ );
1932
+ }
1933
+ networkBus.error(nsError);
1934
+ return this.createTranslationResult(
1935
+ fallbackValue,
1936
+ "fallback",
1937
+ 0,
1938
+ startTime,
1939
+ nsError
1940
+ );
1941
+ }
1942
+ }
1943
+ /**
1944
+ * ✅ Load entire namespace (used for preloading)
1945
+ * Returns NamespaceResult with status and error information
1946
+ */
1947
+ async loadNamespace(locale, namespace) {
1948
+ const startTime = Date.now();
1949
+ if (!this.enabled) {
1950
+ return this.createNamespaceResult(null, "fallback", 0, startTime);
1951
+ }
1952
+ try {
1953
+ const data = await this.loadNamespaceWithRetry(locale, namespace);
1954
+ if (data === null) {
1955
+ logger.debug(`Network namespace not modified: ${locale}:${namespace}`);
1956
+ return this.createNamespaceResult(null, "network", 304, startTime);
1957
+ }
1958
+ logger.debug(`Network namespace loaded: ${locale}:${namespace}`);
1959
+ return this.createNamespaceResult(data, "network", 200, startTime);
1960
+ } catch (error) {
1961
+ if (error instanceof TranslationLockedError) {
1962
+ logger.debug(`Namespace locked (423): ${locale}:${namespace}`);
1963
+ return this.createNamespaceResult(
1964
+ null,
1965
+ "fallback",
1966
+ 423,
1967
+ startTime,
1968
+ error
1969
+ );
1970
+ }
1971
+ if (error instanceof TranslationNotFoundError) {
1972
+ logger.debug(`Network namespace not found: ${locale}:${namespace}`);
1973
+ return this.createNamespaceResult(
1974
+ null,
1975
+ "fallback",
1976
+ 404,
1977
+ startTime,
1978
+ error
1979
+ );
1980
+ }
1981
+ logger.debug(`Network namespace error: ${locale}:${namespace}`, error);
1982
+ return this.createNamespaceResult(
1983
+ null,
1984
+ "fallback",
1985
+ 0,
1986
+ startTime,
1987
+ error
1988
+ );
1989
+ }
1990
+ }
1991
+ /**
1992
+ * ✅ Namespace lookup with retry logic
1993
+ * Throws custom errors (TranslationNotFoundError, TranslationLockedError, etc.)
1994
+ */
1995
+ async loadNamespaceWithRetry(locale, namespace) {
1996
+ return this.withRetry(
1997
+ () => this.repository.getNamespace(locale, namespace),
1998
+ `${locale}:${namespace}`,
1999
+ "namespace request"
2000
+ );
2001
+ }
2002
+ /**
2003
+ * Simple save implementation (API usually doesn't save)
2004
+ */
2005
+ async save(request, result) {
2006
+ void request;
2007
+ void result;
2008
+ }
2009
+ /**
2010
+ * ✅ Lookup with retry logic for individual keys
2011
+ * Throws custom errors (TranslationNotFoundError, TranslationLockedError, etc.)
2012
+ */
2013
+ async lookupWithRetry(cacheKey) {
2014
+ return this.withRetry(
2015
+ () => this.repository.get(cacheKey),
2016
+ cacheKey,
2017
+ "network request"
2018
+ );
2019
+ }
2020
+ /**
2021
+ * ✅ Lookup ContentUnit with retry logic for individual keys
2022
+ * Returns full ContentUnit with text and styles
2023
+ * Throws custom errors (TranslationNotFoundError, TranslationLockedError, etc.)
2024
+ */
2025
+ async lookupContentUnitWithRetry(cacheKey) {
2026
+ const repo = this.repository;
2027
+ if (typeof repo.getContentUnit !== "function") {
2028
+ const text = await this.lookupWithRetry(cacheKey);
2029
+ return { text, styles: {} };
2030
+ }
2031
+ return this.withRetry(
2032
+ () => repo.getContentUnit(cacheKey),
2033
+ cacheKey,
2034
+ "network content unit request"
2035
+ );
2036
+ }
2037
+ /**
2038
+ * Delay utility for retries
2039
+ */
2040
+ delay(ms) {
2041
+ return new Promise((resolve) => setTimeout(resolve, ms));
2042
+ }
2043
+ calculateBackoff(attempt) {
2044
+ const baseDelay = this.retryDelay;
2045
+ const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
2046
+ const maxDelay = 1e4;
2047
+ const jitter = Math.random() * 0.1 * exponentialDelay;
2048
+ return Math.min(exponentialDelay + jitter, maxDelay);
2049
+ }
2050
+ /**
2051
+ * Generic retry wrapper for repository operations
2052
+ * Handles retryable errors with exponential backoff
2053
+ */
2054
+ async withRetry(operation, contextKey, contextDescription, attempt = 1) {
2055
+ const networkBus = this.bus.network(contextKey, "withRetry");
2056
+ try {
2057
+ return await operation();
2058
+ } catch (error) {
2059
+ if (attempt <= this.retryCount && isRetryableError(error)) {
2060
+ const delay = this.calculateBackoff(attempt);
2061
+ networkBus.retry(attempt, delay, error.message);
2062
+ await this.delay(delay);
2063
+ logger.debug(
2064
+ `Retrying ${contextDescription} (${attempt}/${this.retryCount}) after ${delay}ms`
2065
+ );
2066
+ return this.withRetry(
2067
+ operation,
2068
+ contextKey,
2069
+ contextDescription,
2070
+ attempt + 1
2071
+ );
2072
+ }
2073
+ throw error;
2074
+ }
2075
+ }
2076
+ /**
2077
+ * Handle translation not found (after namespace fallback also failed)
2078
+ */
2079
+ handleTranslationNotFound(request, fallbackValue, error, startTime) {
2080
+ logger.debug(`Network namespace miss: ${request.cleanKey}`);
2081
+ const translateBus = this.bus.translation(request);
2082
+ translateBus.notFound(
2083
+ fallbackValue,
2084
+ request.locale,
2085
+ request.namespace || "default"
2086
+ );
2087
+ return this.createTranslationResult(
2088
+ fallbackValue,
2089
+ "fallback",
2090
+ 404,
2091
+ startTime,
2092
+ error
2093
+ );
2094
+ }
2095
+ /**
2096
+ * Handle TranslationLockedError for translation lookups
2097
+ */
2098
+ handleTranslationLocked(request, fallbackValue, error, startTime, networkBus) {
2099
+ const translateBus = this.bus.translation(request);
2100
+ translateBus.locked(
2101
+ fallbackValue,
2102
+ request.locale,
2103
+ request.namespace || "default"
2104
+ );
2105
+ networkBus.error(error, 423);
2106
+ return this.createTranslationResult(
2107
+ fallbackValue,
2108
+ "fallback",
2109
+ 423,
2110
+ startTime,
2111
+ error
2112
+ );
2113
+ }
2114
+ /**
2115
+ * Helper to create consistent TranslationResult objects
2116
+ */
2117
+ createTranslationResult(value, source, status, startTime, error, cached, namespaceLoaded, styles) {
2118
+ return {
2119
+ value,
2120
+ source,
2121
+ status,
2122
+ duration: Date.now() - startTime,
2123
+ ...error && { error },
2124
+ ...cached !== void 0 && { cached },
2125
+ ...namespaceLoaded && { namespaceLoaded },
2126
+ ...styles && Object.keys(styles).length > 0 && { styles }
2127
+ };
2128
+ }
2129
+ /**
2130
+ * Helper to create consistent NamespaceResult objects
2131
+ */
2132
+ createNamespaceResult(data, source, status, startTime, error) {
2133
+ return {
2134
+ data,
2135
+ source,
2136
+ status,
2137
+ duration: Date.now() - startTime,
2138
+ ...error && { error }
2139
+ };
2140
+ }
2141
+ getPreload() {
2142
+ const config = this.repository.getConfig();
2143
+ return config.options?.preloadNamespaces || [];
2144
+ }
2145
+ };
2146
+
2147
+ // src/content/style-mapper/DefaultStyleMapper.ts
2148
+ var DefaultStyleMapper = class {
2149
+ constructor() {
2150
+ /**
2151
+ * Element → Style mapping
2152
+ * Maps HTML element names to their style property
2153
+ */
2154
+ __publicField(this, "elementToStyles", {
2155
+ b: "bold",
2156
+ strong: "bold",
2157
+ i: "italic",
2158
+ em: "italic",
2159
+ u: "underline",
2160
+ s: "strikethrough",
2161
+ del: "strikethrough",
2162
+ strike: "strikethrough",
2163
+ code: "code",
2164
+ mark: "highlight",
2165
+ sub: "subscript",
2166
+ sup: "superscript"
2167
+ });
2168
+ /**
2169
+ * Style → Element mapping (reverse)
2170
+ * Uses simple, familiar elements for developer convenience
2171
+ * Also accepts element names directly (strong, em, etc.)
2172
+ */
2173
+ __publicField(this, "styleToElement", {
2174
+ // Semantic style names → simple elements
2175
+ bold: "b",
2176
+ italic: "i",
2177
+ underline: "u",
2178
+ strikethrough: "s",
2179
+ code: "code",
2180
+ highlight: "mark",
2181
+ subscript: "sub",
2182
+ superscript: "sup",
2183
+ // Element names (for servers that store styles as element names)
2184
+ strong: "b",
2185
+ b: "b",
2186
+ em: "i",
2187
+ i: "i",
2188
+ u: "u",
2189
+ s: "s",
2190
+ del: "s",
2191
+ strike: "s",
2192
+ mark: "mark",
2193
+ sub: "sub",
2194
+ sup: "sup"
2195
+ });
2196
+ }
2197
+ /**
2198
+ * Convert HTML element to style properties
2199
+ *
2200
+ * @param element - Tag name (e.g., "b", "a", "strong")
2201
+ * @param attrs - Element attributes (e.g., { href: "/url" })
2202
+ * @returns Array of style strings
2203
+ */
2204
+ mapElementToStyles(element, attrs) {
2205
+ const styles = [];
2206
+ const tag = element.toLowerCase();
2207
+ const mappedStyle = this.elementToStyles[tag];
2208
+ if (mappedStyle) {
2209
+ styles.push(mappedStyle);
2210
+ }
2211
+ if (tag === "a" && attrs.href) {
2212
+ styles.push(`hyperlink:${attrs.href}`);
2213
+ }
2214
+ return styles;
2215
+ }
2216
+ /**
2217
+ * Convert style properties to HTML element
2218
+ *
2219
+ * Priority order:
2220
+ * 1. Hyperlink (becomes wrapper element with href)
2221
+ * 2. First recognized style property
2222
+ * 3. Fallback to span
2223
+ *
2224
+ * @param styles - Array of style strings
2225
+ * @returns Element info for serialization
2226
+ */
2227
+ mapStylesToElement(styles) {
2228
+ for (const style of styles) {
2229
+ if (style.startsWith("hyperlink:")) {
2230
+ return {
2231
+ element: "a",
2232
+ attrs: { href: style.slice("hyperlink:".length) }
2233
+ };
2234
+ }
2235
+ }
2236
+ for (const style of styles) {
2237
+ const element = this.styleToElement[style];
2238
+ if (element) {
2239
+ return {
2240
+ element,
2241
+ attrs: {}
2242
+ };
2243
+ }
2244
+ }
2245
+ return { element: "span", attrs: {} };
2246
+ }
2247
+ };
2248
+
2249
+ // src/content/parser/MarkupParser.ts
2250
+ var MarkupParser = class {
2251
+ constructor(options = {}) {
2252
+ __publicField(this, "styleMapper");
2253
+ this.styleMapper = options.styleMapper ?? new DefaultStyleMapper();
2254
+ }
2255
+ /**
2256
+ * Parse HTML string into ContentUnit
2257
+ *
2258
+ * Handles two input formats:
2259
+ * 1. HTML tags: `<b>text</b>` → converts to indexed `<1>text</1>` with styles
2260
+ * 2. Already indexed: `<1>text</1>` → passes through as-is (from server)
2261
+ *
2262
+ * @param input - HTML string with inline elements or already indexed
2263
+ * @returns ContentUnit with indexed tags and styles
2264
+ */
2265
+ parse(input) {
2266
+ const hasIndexedTags = /<\/?[0-9]+>/.test(input);
2267
+ if (hasIndexedTags) {
2268
+ return {
2269
+ text: input,
2270
+ styles: {}
2271
+ // Empty - styles determined by children
2272
+ };
2273
+ }
2274
+ const styles = {};
2275
+ let tagIndex = 0;
2276
+ const tagStack = [];
2277
+ let result = "";
2278
+ let pos = 0;
2279
+ while (pos < input.length) {
2280
+ const tagMatch = this.matchTag(input, pos);
2281
+ if (!tagMatch) {
2282
+ result += input.slice(pos);
2283
+ break;
2284
+ }
2285
+ result += input.slice(pos, tagMatch.startIndex);
2286
+ if (tagMatch.isClosing) {
2287
+ const matchingOpen = this.findMatchingOpenTag(
2288
+ tagStack,
2289
+ tagMatch.tag.toLowerCase()
2290
+ );
2291
+ if (matchingOpen !== null) {
2292
+ result += `</${matchingOpen}>`;
2293
+ const stackIndex = tagStack.findIndex(
2294
+ (t) => t.index === matchingOpen
2295
+ );
2296
+ if (stackIndex !== -1) {
2297
+ tagStack.splice(stackIndex, 1);
2298
+ }
2299
+ } else {
2300
+ result += input.slice(tagMatch.startIndex, tagMatch.endIndex);
2301
+ }
2302
+ } else if (tagMatch.isSelfClosing) {
2303
+ tagIndex++;
2304
+ const styleList = this.styleMapper.mapElementToStyles(
2305
+ tagMatch.tag,
2306
+ tagMatch.attrs
2307
+ );
2308
+ if (styleList.length > 0) {
2309
+ styles[String(tagIndex)] = styleList;
2310
+ result += `<${tagIndex}/>`;
2311
+ }
2312
+ } else {
2313
+ const styleList = this.styleMapper.mapElementToStyles(
2314
+ tagMatch.tag,
2315
+ tagMatch.attrs
2316
+ );
2317
+ if (styleList.length > 0) {
2318
+ tagIndex++;
2319
+ styles[String(tagIndex)] = styleList;
2320
+ result += `<${tagIndex}>`;
2321
+ tagStack.push({ tag: tagMatch.tag.toLowerCase(), index: tagIndex });
2322
+ } else {
2323
+ const closingIndex = this.findClosingTagIndex(
2324
+ input,
2325
+ tagMatch.endIndex,
2326
+ tagMatch.tag
2327
+ );
2328
+ if (closingIndex !== -1) {
2329
+ const innerContent = input.slice(tagMatch.endIndex, closingIndex);
2330
+ const innerUnit = this.parse(innerContent);
2331
+ const baseIndex = tagIndex;
2332
+ for (const [key, value] of Object.entries(innerUnit.styles)) {
2333
+ const adjustedKey = String(baseIndex + parseInt(key, 10));
2334
+ styles[adjustedKey] = value;
2335
+ }
2336
+ result += innerUnit.text.replace(/<(\/?)\d+>/g, (match, slash) => {
2337
+ const num = parseInt(match.replace(/[</>]/g, ""), 10);
2338
+ return `<${slash}${baseIndex + num}>`;
2339
+ });
2340
+ const closingMatch = this.matchTag(input, closingIndex);
2341
+ pos = closingMatch ? closingMatch.endIndex : closingIndex;
2342
+ const maxInnerIndex = Object.keys(innerUnit.styles).length > 0 ? Math.max(...Object.keys(innerUnit.styles).map(Number)) : 0;
2343
+ tagIndex = baseIndex + maxInnerIndex;
2344
+ continue;
2345
+ }
2346
+ }
2347
+ }
2348
+ pos = tagMatch.endIndex;
2349
+ }
2350
+ while (tagStack.length > 0) {
2351
+ const unclosed = tagStack.pop();
2352
+ result += `</${unclosed.index}>`;
2353
+ }
2354
+ return {
2355
+ text: result,
2356
+ styles
2357
+ };
2358
+ }
2359
+ /**
2360
+ * Match an HTML tag at the given position
2361
+ */
2362
+ matchTag(input, startPos) {
2363
+ const tagStart = input.indexOf("<", startPos);
2364
+ if (tagStart === -1) return null;
2365
+ const tagPattern = /^<(\/?)([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z][a-zA-Z0-9-]*(?:=(?:"[^"]*"|'[^']*'|[^\s>]*))?)*)\s*(\/?)>/;
2366
+ const remaining = input.slice(tagStart);
2367
+ const match = remaining.match(tagPattern);
2368
+ if (!match) {
2369
+ if (tagStart + 1 < input.length) {
2370
+ return this.matchTag(input, tagStart + 1);
2371
+ }
2372
+ return null;
2373
+ }
2374
+ const [fullMatch, closingSlash, tagName, attrsString, selfClosingSlash] = match;
2375
+ return {
2376
+ tag: tagName,
2377
+ attrs: this.parseAttributes(attrsString),
2378
+ isClosing: closingSlash === "/",
2379
+ isSelfClosing: selfClosingSlash === "/",
2380
+ startIndex: tagStart,
2381
+ endIndex: tagStart + fullMatch.length
2382
+ };
2383
+ }
2384
+ /**
2385
+ * Parse HTML attributes string into key-value pairs
2386
+ */
2387
+ parseAttributes(attrsString) {
2388
+ const attrs = {};
2389
+ if (!attrsString.trim()) return attrs;
2390
+ const attrPattern = /([a-zA-Z][a-zA-Z0-9-]*)(?:=(?:"([^"]*)"|'([^']*)'|([^\s>]*)))?/g;
2391
+ let match;
2392
+ while ((match = attrPattern.exec(attrsString)) !== null) {
2393
+ const [, name, doubleQuoted, singleQuoted, unquoted] = match;
2394
+ const value = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
2395
+ attrs[name.toLowerCase()] = value;
2396
+ }
2397
+ return attrs;
2398
+ }
2399
+ /**
2400
+ * Find matching opening tag in the stack
2401
+ */
2402
+ findMatchingOpenTag(stack, tagName) {
2403
+ for (let i = stack.length - 1; i >= 0; i--) {
2404
+ if (stack[i].tag === tagName) {
2405
+ return stack[i].index;
2406
+ }
2407
+ }
2408
+ return null;
2409
+ }
2410
+ /**
2411
+ * Find the closing tag index for a given opening tag
2412
+ */
2413
+ findClosingTagIndex(input, startPos, tagName) {
2414
+ const lowerTag = tagName.toLowerCase();
2415
+ let depth = 1;
2416
+ let pos = startPos;
2417
+ while (pos < input.length && depth > 0) {
2418
+ const tagMatch = this.matchTag(input, pos);
2419
+ if (!tagMatch) break;
2420
+ if (tagMatch.tag.toLowerCase() === lowerTag) {
2421
+ if (tagMatch.isClosing) {
2422
+ depth--;
2423
+ if (depth === 0) {
2424
+ return tagMatch.startIndex;
2425
+ }
2426
+ } else if (!tagMatch.isSelfClosing) {
2427
+ depth++;
2428
+ }
2429
+ }
2430
+ pos = tagMatch.endIndex;
2431
+ }
2432
+ return -1;
2433
+ }
2434
+ };
2435
+
2436
+ // src/content/serializer/HTMLSerializer.ts
2437
+ var HTMLSerializer = class {
2438
+ constructor(options = {}) {
2439
+ __publicField(this, "styleMapper");
2440
+ this.styleMapper = options.styleMapper ?? new DefaultStyleMapper();
2441
+ }
2442
+ /**
2443
+ * Serialize ContentUnit to HTML string
2444
+ *
2445
+ * @param unit - ContentUnit with indexed tags and styles
2446
+ * @returns HTML string with semantic elements
2447
+ */
2448
+ serialize(unit) {
2449
+ const { text, styles } = unit;
2450
+ const tagPattern = /<(\/?)?(\d+)(\/)?>/g;
2451
+ let result = "";
2452
+ let lastIndex = 0;
2453
+ let match;
2454
+ while ((match = tagPattern.exec(text)) !== null) {
2455
+ result += text.slice(lastIndex, match.index);
2456
+ const [fullMatch, closingSlash, indexStr, selfClosingSlash] = match;
2457
+ const tagIndex = indexStr;
2458
+ const styleList = styles[tagIndex] || [];
2459
+ if (closingSlash) {
2460
+ const mapping = this.styleMapper.mapStylesToElement(styleList);
2461
+ result += `</${mapping.element}>`;
2462
+ } else if (selfClosingSlash) {
2463
+ const mapping = this.styleMapper.mapStylesToElement(styleList);
2464
+ const attrs = this.formatAttributes(mapping.attrs);
2465
+ result += `<${mapping.element}${attrs} />`;
2466
+ } else {
2467
+ const mapping = this.styleMapper.mapStylesToElement(styleList);
2468
+ const attrs = this.formatAttributes(mapping.attrs);
2469
+ result += `<${mapping.element}${attrs}>`;
2470
+ }
2471
+ lastIndex = match.index + fullMatch.length;
2472
+ }
2473
+ result += text.slice(lastIndex);
2474
+ return result;
2475
+ }
2476
+ /**
2477
+ * Format attributes object into HTML attribute string
2478
+ */
2479
+ formatAttributes(attrs) {
2480
+ const entries = Object.entries(attrs);
2481
+ if (entries.length === 0) return "";
2482
+ return " " + entries.map(([key, value]) => `${key}="${this.escapeAttributeValue(value)}"`).join(" ");
2483
+ }
2484
+ /**
2485
+ * Escape special characters in attribute values
2486
+ */
2487
+ escapeAttributeValue(value) {
2488
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2489
+ }
2490
+ };
2491
+
2492
+ // src/cache/CacheManager.ts
2493
+ var CacheManager = class {
2494
+ constructor(handlerChain, bus, getCurrentLocale, loadNamespaces) {
2495
+ __publicField(this, "handlerChain");
2496
+ __publicField(this, "bus");
2497
+ __publicField(this, "currentLocale");
2498
+ __publicField(this, "loadNamespaces");
2499
+ this.handlerChain = handlerChain;
2500
+ this.bus = bus;
2501
+ this.currentLocale = getCurrentLocale;
2502
+ this.loadNamespaces = loadNamespaces;
2503
+ }
2504
+ /**
2505
+ * Get a cached translation entry
2506
+ *
2507
+ * @param key - Full cache key (e.g., 'en:app:welcome')
2508
+ * @returns Cache entry with value and styles, or null if not found
2509
+ */
2510
+ get(key) {
2511
+ const entries = this.handlerChain.getEntries();
2512
+ for (const handlerName in entries) {
2513
+ const handlerEntries = entries[handlerName];
2514
+ if (handlerEntries && typeof handlerEntries === "object") {
2515
+ if (key in handlerEntries) {
2516
+ const rawValue = handlerEntries[key];
2517
+ try {
2518
+ if (typeof rawValue === "string") {
2519
+ const parsed = JSON.parse(rawValue);
2520
+ if (typeof parsed === "object" && "text" in parsed) {
2521
+ return {
2522
+ value: String(parsed.text),
2523
+ styles: parsed.styles || void 0
2524
+ };
2525
+ }
2526
+ return { value: rawValue, styles: void 0 };
2527
+ }
2528
+ return {
2529
+ value: JSON.stringify(rawValue),
2530
+ styles: void 0
2531
+ };
2532
+ } catch {
2533
+ return {
2534
+ value: typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue),
2535
+ styles: void 0
2536
+ };
2537
+ }
2538
+ }
2539
+ }
2540
+ }
2541
+ return null;
2542
+ }
2543
+ /**
2544
+ * Set a cache entry (for optimistic updates)
2545
+ *
2546
+ * @param key - Full cache key (e.g., 'en:app:welcome')
2547
+ * @param value - Translation value
2548
+ * @param styles - Optional styles for indexed tags
2549
+ */
2550
+ set(key, value, styles) {
2551
+ const [locale, namespace, ...keyParts] = key.split(":");
2552
+ const translationKey = keyParts.join(":");
2553
+ if (!locale || !namespace || !translationKey) {
2554
+ throw new Error(
2555
+ `Invalid cache key format: "${key}". Expected format: "locale:namespace:key"`
2556
+ );
2557
+ }
2558
+ this.handlerChain.setCache(key, value, styles);
2559
+ this.bus.emit("cache:set", {
2560
+ payload: {
2561
+ key,
2562
+ value,
2563
+ styles,
2564
+ optimistic: true
2565
+ }
2566
+ });
2567
+ }
2568
+ /**
2569
+ * Check if a key exists in cache
2570
+ *
2571
+ * @param key - Full cache key (e.g., 'en:app:welcome')
2572
+ * @returns true if key is cached
2573
+ */
2574
+ has(key) {
2575
+ return this.get(key) !== null;
2576
+ }
2577
+ /**
2578
+ * Invalidate cache entries and optionally refetch
2579
+ *
2580
+ * Like TanStack Query's invalidateQueries, this clears matching cache entries
2581
+ * and triggers background refetch by default.
2582
+ *
2583
+ * @param options - Filter options for invalidation
2584
+ *
2585
+ * @example
2586
+ * ```typescript
2587
+ * // Invalidate all
2588
+ * await cache.invalidate();
2589
+ *
2590
+ * // Invalidate by namespace
2591
+ * await cache.invalidate({ namespace: 'app' });
2592
+ *
2593
+ * // Invalidate by locale
2594
+ * await cache.invalidate({ locale: 'es' });
2595
+ *
2596
+ * // Invalidate specific key
2597
+ * await cache.invalidate({ key: 'en:app:welcome' });
2598
+ *
2599
+ * // Invalidate with predicate
2600
+ * await cache.invalidate({ predicate: (k) => k.includes('common:') });
2601
+ *
2602
+ * // Invalidate without refetch
2603
+ * await cache.invalidate({ namespace: 'app', refetch: false });
2604
+ * ```
2605
+ */
2606
+ async invalidate(options = {}) {
2607
+ const { namespace, locale, key, predicate, refetch = true } = options;
2608
+ const currentLocale = this.currentLocale();
2609
+ let keysCleared = 0;
2610
+ if (key) {
2611
+ const [keyLocale, keyNamespace] = key.split(":");
2612
+ if (keyLocale && keyNamespace) {
2613
+ this.handlerChain.clearNamespace(keyLocale, keyNamespace);
2614
+ keysCleared = 1;
2615
+ }
2616
+ } else if (namespace && locale) {
2617
+ this.handlerChain.clearNamespace(locale, namespace);
2618
+ keysCleared = this.countClearedKeys(locale, namespace);
2619
+ } else if (namespace) {
2620
+ this.handlerChain.clearNamespace(currentLocale, namespace);
2621
+ keysCleared = this.countClearedKeys(currentLocale, namespace);
2622
+ } else if (locale) {
2623
+ const entries = this.handlerChain.getEntries();
2624
+ const namespacesToClear = /* @__PURE__ */ new Set();
2625
+ for (const handlerName in entries) {
2626
+ const handlerEntries = entries[handlerName];
2627
+ if (handlerEntries && typeof handlerEntries === "object") {
2628
+ for (const entryKey of Object.keys(handlerEntries)) {
2629
+ if (entryKey.startsWith(`${locale}:`) || entryKey.startsWith(`@${locale}:`)) {
2630
+ const parts = entryKey.replace("@", "").split(":");
2631
+ if (parts[1]) {
2632
+ namespacesToClear.add(parts[1]);
2633
+ }
2634
+ }
2635
+ }
2636
+ }
2637
+ }
2638
+ for (const ns of namespacesToClear) {
2639
+ this.handlerChain.clearNamespace(locale, ns);
2640
+ keysCleared++;
2641
+ }
2642
+ } else if (predicate) {
2643
+ const entries = this.handlerChain.getEntries();
2644
+ const toClear = /* @__PURE__ */ new Map();
2645
+ for (const handlerName in entries) {
2646
+ const handlerEntries = entries[handlerName];
2647
+ if (handlerEntries && typeof handlerEntries === "object") {
2648
+ for (const entryKey of Object.keys(handlerEntries)) {
2649
+ if (predicate(entryKey)) {
2650
+ const parts = entryKey.replace("@", "").split(":");
2651
+ const entryLocale = parts[0];
2652
+ const entryNs = parts[1];
2653
+ if (entryLocale && entryNs) {
2654
+ if (!toClear.has(entryLocale)) {
2655
+ toClear.set(entryLocale, /* @__PURE__ */ new Set());
2656
+ }
2657
+ toClear.get(entryLocale).add(entryNs);
2658
+ keysCleared++;
2659
+ }
2660
+ }
2661
+ }
2662
+ }
2663
+ }
2664
+ for (const [loc, namespaces] of toClear) {
2665
+ for (const ns of namespaces) {
2666
+ this.handlerChain.clearNamespace(loc, ns);
2667
+ }
2668
+ }
2669
+ } else {
2670
+ this.handlerChain.clearCache();
2671
+ keysCleared = -1;
2672
+ }
2673
+ this.bus.emit("cache:invalidate", {
2674
+ payload: {
2675
+ filter: options,
2676
+ keysCleared,
2677
+ refetch
2678
+ }
2679
+ });
2680
+ if (refetch) {
2681
+ await this.refetchAfterInvalidate(options);
2682
+ }
2683
+ }
2684
+ /**
2685
+ * Count keys that would be cleared (for event reporting)
2686
+ */
2687
+ countClearedKeys(locale, namespace) {
2688
+ const entries = this.handlerChain.getEntries();
2689
+ let count = 0;
2690
+ const prefix = `${locale}:${namespace}:`;
2691
+ const namespaceKey = `@${locale}:${namespace}`;
2692
+ for (const handlerName in entries) {
2693
+ const handlerEntries = entries[handlerName];
2694
+ if (handlerEntries && typeof handlerEntries === "object") {
2695
+ for (const key of Object.keys(handlerEntries)) {
2696
+ if (key.startsWith(prefix) || key === namespaceKey) {
2697
+ count++;
2698
+ }
2699
+ }
2700
+ }
2701
+ }
2702
+ return count;
2703
+ }
2704
+ /**
2705
+ * Refetch data after invalidation
2706
+ */
2707
+ async refetchAfterInvalidate(options) {
2708
+ const { namespace, locale, key } = options;
2709
+ const currentLocale = this.currentLocale();
2710
+ if (key) {
2711
+ const [keyLocale, keyNamespace] = key.split(":");
2712
+ if (keyLocale && keyNamespace) {
2713
+ await this.loadNamespaces(keyLocale, [keyNamespace]);
2714
+ }
2715
+ } else if (namespace) {
2716
+ await this.loadNamespaces(locale || currentLocale, [namespace]);
2717
+ } else if (locale) {
2718
+ }
2719
+ }
2720
+ };
2721
+
2722
+ // src/lionRapid.ts
2723
+ var INDEXED_TAG_REGEX = /<\/?[0-9]+>/;
2724
+ var HTML_TAG_REGEX = /<[a-z][a-z0-9]*(?:\s+[^>]*)?\/?>/i;
2725
+ var LionRapidCore = class {
2726
+ constructor(eventEmitter, handlerChain, defaultLocale = "en", fallbackLocale = "en", defaultNamespace) {
2727
+ __publicField(this, "handlerChain");
2728
+ __publicField(this, "currentLocale");
2729
+ __publicField(this, "fallbackLocale");
2730
+ __publicField(this, "defaultNamespace");
2731
+ __publicField(this, "pendingTranslations", /* @__PURE__ */ new Map());
2732
+ __publicField(this, "bus");
2733
+ __publicField(this, "plugins", /* @__PURE__ */ new Map());
2734
+ // 🔥 EMBEDDED: Core integration maps
2735
+ __publicField(this, "formatters", /* @__PURE__ */ new Map());
2736
+ __publicField(this, "processors", /* @__PURE__ */ new Map());
2737
+ // 📡 EVENT-DRIVEN: Just track for cleanup
2738
+ __publicField(this, "eventPlugins", /* @__PURE__ */ new Set());
2739
+ // 📦 CONTENT: Parser and serializer for ContentUnit processing
2740
+ __publicField(this, "markupParser", new MarkupParser());
2741
+ __publicField(this, "htmlSerializer", new HTMLSerializer());
2742
+ // 🔄 CACHE: TanStack Query-inspired cache management
2743
+ __publicField(this, "cache");
2744
+ this.bus = eventEmitter;
2745
+ this.handlerChain = handlerChain;
2746
+ this.currentLocale = defaultLocale;
2747
+ this.fallbackLocale = fallbackLocale;
2748
+ this.defaultNamespace = defaultNamespace;
2749
+ this.cache = new CacheManager(
2750
+ this.handlerChain,
2751
+ this.bus,
2752
+ () => this.currentLocale,
2753
+ (locale, namespaces) => this.loadNamespaces(locale, namespaces)
2754
+ );
2755
+ }
2756
+ /**
2757
+ * ✅ Register plugin (called by builder)
2758
+ */
2759
+ registerPlugin(plugin) {
2760
+ this.plugins.set(plugin.name, plugin);
2761
+ if (this.isEmbeddedPlugin(plugin)) {
2762
+ this.integrateEmbeddedPlugin(plugin);
2763
+ }
2764
+ if (this.isEventPlugin(plugin)) {
2765
+ this.eventPlugins.add(plugin);
2766
+ }
2767
+ }
2768
+ /**
2769
+ * 🔥 EMBEDDED: Integrate plugin functionality into core
2770
+ */
2771
+ integrateEmbeddedPlugin(plugin) {
2772
+ if (plugin.getFormatters) {
2773
+ const pluginFormatters = plugin.getFormatters();
2774
+ Object.entries(pluginFormatters).forEach(([name, formatter]) => {
2775
+ this.formatters.set(name, formatter);
2776
+ logger.debug(`Embedded formatter: ${name} from ${plugin.name}`);
2777
+ });
2778
+ }
2779
+ if (plugin.getProcessors) {
2780
+ const pluginProcessors = plugin.getProcessors();
2781
+ Object.entries(pluginProcessors).forEach(([name, processor]) => {
2782
+ this.processors.set(name, processor);
2783
+ logger.debug(`Embedded processor: ${name} from ${plugin.name}`);
2784
+ });
2785
+ }
2786
+ }
2787
+ /**
2788
+ * 📦 TRANSLATION API - Returns PLAIN TEXT
2789
+ *
2790
+ * Strips indexed tags and returns safe plain text output.
2791
+ * For HTML output with styles, use tHtml() instead.
2792
+ *
2793
+ * @param key - Translation key
2794
+ * @param params - Optional interpolation parameters
2795
+ * @param defaultValue - Optional default value
2796
+ * @returns Plain text string (safe, no XSS risk)
2797
+ *
2798
+ * @example
2799
+ * ```typescript
2800
+ * t('welcome') // → "Hello World"
2801
+ * t('greeting', { name: 'John' }) // → "Hello John"
2802
+ * t('missing', 'Default text') // → "Default text" (if key missing)
2803
+ * ```
2804
+ */
2805
+ t(key, ...args) {
2806
+ const [firstArg, secondArg] = args;
2807
+ const request = prepareTranslationRequest(
2808
+ key,
2809
+ this.currentLocale,
2810
+ firstArg,
2811
+ secondArg,
2812
+ this.defaultNamespace,
2813
+ this.fallbackLocale
2814
+ );
2815
+ logger.debug(`Translation requested: ${request.cleanKey}`);
2816
+ const translateBus = this.bus.translation(request);
2817
+ translateBus.request();
2818
+ const syncResult = this.handlerChain.handleSync(request);
2819
+ if (syncResult && syncResult.status === 200) {
2820
+ const result = this.processValue(syncResult.value, request);
2821
+ logger.debug(
2822
+ `Translation found: ${request.cleanKey} from ${syncResult.source} (${syncResult.duration}ms)`
2823
+ );
2824
+ translateBus.success(result);
2825
+ return result;
2826
+ }
2827
+ this.triggerBackgroundLoad(request);
2828
+ const fallbackValue = request.parsedDefaultValue || request.cleanKey;
2829
+ const fallbackResult = this.processValue(fallbackValue, request);
2830
+ logger.debug(`Translation fallback: ${request.cleanKey}`);
2831
+ translateBus.fallback(fallbackResult);
2832
+ return fallbackResult;
2833
+ }
2834
+ /**
2835
+ * 📦 HTML TRANSLATION API
2836
+ * Returns translation as HTML string with styles applied.
2837
+ *
2838
+ * Use for innerHTML, dangerouslySetInnerHTML, or server-side rendering.
2839
+ * For React components, prefer using <Trans> instead.
2840
+ *
2841
+ * @param key - Translation key
2842
+ * @param params - Optional interpolation parameters
2843
+ * @param defaultValue - Optional default value (can contain HTML)
2844
+ * @returns HTML string with styles applied
2845
+ *
2846
+ * @example
2847
+ * ```typescript
2848
+ * // Vanilla JS
2849
+ * element.innerHTML = i18n.tHtml('welcome');
2850
+ * // → "Hello <b>World</b>"
2851
+ *
2852
+ * // React (use sparingly)
2853
+ * <span dangerouslySetInnerHTML={{ __html: tHtml('welcome') }} />
2854
+ * ```
2855
+ */
2856
+ tHtml(key, ...args) {
2857
+ const [firstArg, secondArg] = args;
2858
+ const request = prepareTranslationRequest(
2859
+ key,
2860
+ this.currentLocale,
2861
+ firstArg,
2862
+ secondArg,
2863
+ this.defaultNamespace,
2864
+ this.fallbackLocale
2865
+ );
2866
+ logger.debug(`HTML translation requested: ${request.cleanKey}`);
2867
+ const translateBus = this.bus.translation(request);
2868
+ translateBus.request();
2869
+ const syncResult = this.handlerChain.handleSync(request);
2870
+ if (syncResult && syncResult.status === 200) {
2871
+ const result = this.processValueAsHtml(syncResult.value, request, syncResult.styles);
2872
+ logger.debug(
2873
+ `HTML translation found: ${request.cleanKey} from ${syncResult.source}`
2874
+ );
2875
+ translateBus.success(result);
2876
+ return result;
2877
+ }
2878
+ this.triggerBackgroundLoad(request);
2879
+ const fallbackValue = request.parsedDefaultValue || request.cleanKey;
2880
+ const fallbackResult = this.processValueAsHtml(fallbackValue, request);
2881
+ logger.debug(`HTML translation fallback: ${request.cleanKey}`);
2882
+ translateBus.fallback(fallbackResult);
2883
+ return fallbackResult;
2884
+ }
2885
+ /**
2886
+ * 📦 Process value as HTML (for tHtml)
2887
+ *
2888
+ * Converts indexed tags to HTML using styles from result or namespace cache.
2889
+ *
2890
+ * @internal
2891
+ */
2892
+ processValueAsHtml(value, request, resultStyles) {
2893
+ const processedText = this.processTranslationValue(value, request);
2894
+ if (this.containsIndexedTags(processedText)) {
2895
+ const styles = resultStyles || this.getStylesFromNamespace(request);
2896
+ const unit = {
2897
+ text: processedText,
2898
+ styles
2899
+ };
2900
+ return this.htmlSerializer.serialize(unit);
2901
+ }
2902
+ if (this.containsHtml(processedText)) {
2903
+ const unit = this.markupParser.parse(processedText);
2904
+ return this.htmlSerializer.serialize(unit);
2905
+ }
2906
+ return processedText;
2907
+ }
2908
+ /**
2909
+ * 📦 Process a value through ContentUnit or ICU pipeline
2910
+ *
2911
+ * Processing priority:
2912
+ * 1. Indexed tags (<1>...</1>) from server ContentUnit → convert to HTML at render time
2913
+ * 2. HTML tags (from defaultValue or legacy) → MarkupParser → ContentUnit → HTML
2914
+ * 3. Plain text → standard ICU processing
2915
+ *
2916
+ * @internal
2917
+ */
2918
+ processValue(value, request) {
2919
+ if (this.containsIndexedTags(value)) {
2920
+ return this.processIndexedValue(value, request);
2921
+ }
2922
+ if (this.containsHtml(value)) {
2923
+ return this.processHtmlValue(value, request);
2924
+ }
2925
+ return this.processTranslationValue(value, request);
2926
+ }
2927
+ /**
2928
+ * 📦 Check if a string contains indexed tags
2929
+ *
2930
+ * Used for auto-detection of server ContentUnit format.
2931
+ *
2932
+ * @param value - String to check
2933
+ * @returns true if indexed tags are detected
2934
+ */
2935
+ containsIndexedTags(value) {
2936
+ return INDEXED_TAG_REGEX.test(value);
2937
+ }
2938
+ /**
2939
+ * 📦 Process indexed value - returns PLAIN TEXT
2940
+ *
2941
+ * Strips indexed tags (<1>text</1>) → "text"
2942
+ * For HTML output, use tHtml() instead.
2943
+ *
2944
+ * @internal
2945
+ */
2946
+ processIndexedValue(value, request) {
2947
+ const processedText = this.processTranslationValue(value, request);
2948
+ return processedText.replace(/<\/?[0-9]+>/g, "");
2949
+ }
2950
+ /**
2951
+ * 📦 Get styles for a translation key from namespace cache (fallback)
2952
+ *
2953
+ * Used when styles are not available in the result (e.g., for fallback values).
2954
+ * Looks up the full ContentUnit from the namespace cache to get styles.
2955
+ *
2956
+ * @internal
2957
+ */
2958
+ getStylesFromNamespace(request) {
2959
+ if (!request.namespace) {
2960
+ return {};
2961
+ }
2962
+ try {
2963
+ const namespaceCacheKey = createNamespaceCacheKey(
2964
+ request.locale,
2965
+ request.namespace
2966
+ );
2967
+ const namespaceData = this.handlerChain.getNamespaceData(namespaceCacheKey);
2968
+ if (!namespaceData) {
2969
+ return {};
2970
+ }
2971
+ const resolved = resolveNestedKey(namespaceData, request.cleanKey);
2972
+ if (resolved && typeof resolved === "object" && "styles" in resolved) {
2973
+ const contentUnit = resolved;
2974
+ return contentUnit.styles;
2975
+ }
2976
+ return {};
2977
+ } catch (error) {
2978
+ logger.debug(`Error getting styles for key ${request.cleanKey}:`, error);
2979
+ return {};
2980
+ }
2981
+ }
2982
+ /**
2983
+ * 📦 Process HTML value - returns PLAIN TEXT
2984
+ *
2985
+ * Parses HTML to ContentUnit, processes ICU, strips indexed tags.
2986
+ * For HTML output, use tHtml() instead.
2987
+ *
2988
+ * @internal
2989
+ */
2990
+ processHtmlValue(value, request) {
2991
+ try {
2992
+ const unit = this.markupParser.parse(value);
2993
+ const processedText = this.processTranslationValue(unit.text, request);
2994
+ return processedText.replace(/<\/?[0-9]+>/g, "");
2995
+ } catch (error) {
2996
+ logger.warn(`ContentUnit processing failed for "${value}":`, error);
2997
+ return this.processTranslationValue(value, request);
2998
+ }
2999
+ }
3000
+ /**
3001
+ * Implementation (handles both overloads)
3002
+ */
3003
+ processTranslationValue(value, paramsOrRequest, locale, key) {
3004
+ let params;
3005
+ let resolvedLocale;
3006
+ let resolvedKey;
3007
+ if (paramsOrRequest && "cleanKey" in paramsOrRequest) {
3008
+ const request = paramsOrRequest;
3009
+ params = request.parsedParams;
3010
+ resolvedLocale = request.locale;
3011
+ resolvedKey = request.cleanKey;
3012
+ } else {
3013
+ params = paramsOrRequest;
3014
+ resolvedLocale = locale || this.currentLocale;
3015
+ resolvedKey = key;
3016
+ }
3017
+ let processedValue = value;
3018
+ try {
3019
+ processedValue = this.applyStructuralFormatters(
3020
+ processedValue,
3021
+ params,
3022
+ resolvedKey
3023
+ );
3024
+ if (this.formatters.has("interpolate")) {
3025
+ const interpolateFormatter = this.formatters.get("interpolate");
3026
+ processedValue = interpolateFormatter(
3027
+ processedValue,
3028
+ params,
3029
+ resolvedLocale,
3030
+ resolvedKey
3031
+ );
3032
+ }
3033
+ if (this.processors.size > 0) {
3034
+ for (const [name, processor] of this.processors.entries()) {
3035
+ try {
3036
+ processedValue = processor(processedValue, params, resolvedKey);
3037
+ } catch (error) {
3038
+ logger.warn(`Processor ${name} failed:`, error);
3039
+ }
3040
+ }
3041
+ }
3042
+ return processedValue;
3043
+ } catch (error) {
3044
+ logger.warn(`Translation processing failed for "${value}":`, error);
3045
+ return value;
3046
+ }
3047
+ }
3048
+ /**
3049
+ * 📦 CONTENT UNIT API
3050
+ * Returns translation as ContentUnit for JSX rendering (Trans component)
3051
+ *
3052
+ * This is the preferred method for components that need to render
3053
+ * styled content with JSX interpolation.
3054
+ *
3055
+ * @param key - Translation key
3056
+ * @param params - Optional interpolation parameters
3057
+ * @param defaultValue - Optional default value (can contain HTML)
3058
+ * @returns ContentUnit with indexed tags and styles
3059
+ *
3060
+ * @example
3061
+ * ```typescript
3062
+ * const unit = i18n.getContentUnit('terms', {}, 'Read our <b>Terms</b>.');
3063
+ * // → { text: "Read our <1>Terms</1>.", styles: {"1": ["bold"]} }
3064
+ * ```
3065
+ */
3066
+ getContentUnit(key, params, defaultValue) {
3067
+ const request = prepareTranslationRequest(
3068
+ key,
3069
+ this.currentLocale,
3070
+ params,
3071
+ defaultValue,
3072
+ this.defaultNamespace,
3073
+ this.fallbackLocale
3074
+ );
3075
+ const syncResult = this.handlerChain.handleSync(request);
3076
+ const rawValue = syncResult && syncResult.status === 200 ? syncResult.value : request.parsedDefaultValue || request.cleanKey;
3077
+ if (!syncResult || syncResult.status !== 200) {
3078
+ this.triggerBackgroundLoad(request);
3079
+ }
3080
+ return this.markupParser.parse(rawValue);
3081
+ }
3082
+ /**
3083
+ * 📦 Process ContentUnit text through ICU formatters
3084
+ *
3085
+ * Used by JSX serializers to process individual text segments.
3086
+ *
3087
+ * @param text - Text segment (may contain ICU expressions like {name})
3088
+ * @param params - Interpolation parameters
3089
+ * @returns Processed text with ICU expressions resolved
3090
+ */
3091
+ processContentText(text, params) {
3092
+ return this.processTranslationValue(text, params, this.currentLocale);
3093
+ }
3094
+ /**
3095
+ * 📦 Check if a string contains HTML tags
3096
+ *
3097
+ * Used for auto-detection of HTML in default values.
3098
+ *
3099
+ * @param value - String to check
3100
+ * @returns true if HTML tags are detected
3101
+ */
3102
+ containsHtml(value) {
3103
+ return HTML_TAG_REGEX.test(value);
3104
+ }
3105
+ /**
3106
+ * ✅ Handle structured formatters (plural, context) - embedded in core
3107
+ */
3108
+ applyStructuralFormatters(value, params, translationKey) {
3109
+ const translationData = value;
3110
+ const hasCount = (p) => typeof p?.count === "number";
3111
+ const hasContext = (p) => typeof p?.context === "string";
3112
+ if (this.formatters.has("plural") && hasCount(params)) {
3113
+ const pluralFormatter = this.formatters.get("plural");
3114
+ try {
3115
+ const pluralResult = pluralFormatter(
3116
+ translationData,
3117
+ params,
3118
+ this.currentLocale,
3119
+ translationKey
3120
+ );
3121
+ if (pluralResult !== String(translationData)) {
3122
+ return pluralResult;
3123
+ }
3124
+ } catch (error) {
3125
+ logger.warn("Plural formatting failed:", error);
3126
+ }
3127
+ }
3128
+ if (this.formatters.has("context") && hasContext(params)) {
3129
+ const contextFormatter = this.formatters.get("context");
3130
+ try {
3131
+ const contextResult = contextFormatter(
3132
+ translationData,
3133
+ params,
3134
+ this.currentLocale,
3135
+ translationKey
3136
+ );
3137
+ if (contextResult !== String(translationData)) {
3138
+ return contextResult;
3139
+ }
3140
+ } catch (error) {
3141
+ logger.warn("Context formatting failed:", error);
3142
+ }
3143
+ }
3144
+ return String(translationData);
3145
+ }
3146
+ /**
3147
+ * Trigger background load via handler chain
3148
+ */
3149
+ triggerBackgroundLoad(request) {
3150
+ logger.debug(`Background load: ${request.cleanKey}`);
3151
+ const cacheKey = request.individualCacheKey;
3152
+ if (!this.pendingTranslations.has(cacheKey)) {
3153
+ const promise = this.handlerChain.handleAsync(request).then((result) => {
3154
+ if (result && result.value) {
3155
+ logger.debug(`Cached: ${request.cleanKey} = "${result.value}"`);
3156
+ } else {
3157
+ logger.debug(`Not found: ${request.cleanKey}`);
3158
+ }
3159
+ }).catch((error) => {
3160
+ logger.warn(`Background load failed: ${request.cleanKey}`, error);
3161
+ });
3162
+ this.pendingTranslations.set(cacheKey, promise);
3163
+ promise.finally(() => this.pendingTranslations.delete(cacheKey));
3164
+ }
3165
+ }
3166
+ /**
3167
+ * Get the current locale
3168
+ */
3169
+ getCurrentLocale() {
3170
+ return this.currentLocale;
3171
+ }
3172
+ /**
3173
+ * Set default namespace (useful for scoped translations)
3174
+ */
3175
+ setDefaultNamespace(namespace) {
3176
+ this.defaultNamespace = namespace;
3177
+ }
3178
+ /**
3179
+ * Get current default namespace
3180
+ */
3181
+ getDefaultNamespace() {
3182
+ return this.defaultNamespace;
3183
+ }
3184
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3185
+ getEntries() {
3186
+ return this.handlerChain.getEntries();
3187
+ }
3188
+ isCached(key) {
3189
+ const request = prepareTranslationRequest(
3190
+ key,
3191
+ this.currentLocale,
3192
+ void 0,
3193
+ void 0,
3194
+ this.defaultNamespace,
3195
+ this.fallbackLocale
3196
+ );
3197
+ return this.handlerChain.isCached(request);
3198
+ }
3199
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3200
+ getHandlerChainInfo() {
3201
+ return this.handlerChain.getChainInfo();
3202
+ }
3203
+ /**
3204
+ * ✅ Smart language change with selective cache clearing
3205
+ */
3206
+ async changeLanguage(newLocale, options) {
3207
+ const oldLocale = this.currentLocale;
3208
+ if (oldLocale === newLocale) {
3209
+ logger.debug(`Language already set to ${newLocale}`);
3210
+ return;
3211
+ }
3212
+ const clear = options?.clear || false;
3213
+ const languageBus = this.bus.language(oldLocale, newLocale);
3214
+ try {
3215
+ logger.info(`Language change: ${oldLocale} \u2192 ${newLocale}`);
3216
+ logger.debug("Language change options", {
3217
+ clear,
3218
+ preloadNamespaces: options?.preloadNamespaces
3219
+ });
3220
+ languageBus.start();
3221
+ this.currentLocale = newLocale;
3222
+ if (clear) {
3223
+ await this.clearCache();
3224
+ this.clearOldLocalePendingTranslations(oldLocale);
3225
+ }
3226
+ if (options?.preloadNamespaces?.length) {
3227
+ await this.loadNamespaces(newLocale, options.preloadNamespaces);
3228
+ }
3229
+ languageBus.complete();
3230
+ logger.info(`Language changed: ${newLocale}`);
3231
+ } catch (error) {
3232
+ logger.error("Language change failed:", error);
3233
+ languageBus.error(error);
3234
+ throw error;
3235
+ }
3236
+ }
3237
+ async clearCache() {
3238
+ this.handlerChain.clearCache();
3239
+ logger.debug(`Cache cleared for locale: ${this.currentLocale}`);
3240
+ }
3241
+ clearOldLocalePendingTranslations(oldLocale) {
3242
+ let clearedCount = 0;
3243
+ for (const key of this.pendingTranslations.keys()) {
3244
+ if (key.includes(`${oldLocale}:`)) {
3245
+ this.pendingTranslations.delete(key);
3246
+ clearedCount++;
3247
+ }
3248
+ }
3249
+ if (clearedCount > 0) {
3250
+ logger.debug(
3251
+ `Cleared ${clearedCount} pending translations for ${oldLocale}`
3252
+ );
3253
+ }
3254
+ }
3255
+ async loadNamespaces(locale, namespaces) {
3256
+ logger.debug(`Preloading ${namespaces.length} namespaces for ${locale}`);
3257
+ const preloadPromises = namespaces.map(async (namespace) => {
3258
+ try {
3259
+ await this.handlerChain.loadNamespace?.(locale, namespace);
3260
+ logger.debug(`Preloaded: ${locale}:${namespace}`);
3261
+ } catch (error) {
3262
+ logger.warn(`Failed to preload ${locale}:${namespace}:`, error);
3263
+ }
3264
+ });
3265
+ await Promise.allSettled(preloadPromises);
3266
+ }
3267
+ /**
3268
+ * Reload a namespace by clearing cache and fetching fresh data from server
3269
+ * Useful after syncing new translations to ensure fresh data
3270
+ *
3271
+ * @param namespace - The namespace to reload
3272
+ * @param locale - Optional locale (defaults to current locale)
3273
+ * @deprecated Use cache.invalidate({ namespace, refetch: true }) instead
3274
+ */
3275
+ async reloadNamespace(namespace, locale) {
3276
+ const targetLocale = locale || this.currentLocale;
3277
+ logger.debug(`Reloading namespace: ${targetLocale}:${namespace}`);
3278
+ await this.cache.invalidate({
3279
+ namespace,
3280
+ locale: targetLocale,
3281
+ refetch: true
3282
+ });
3283
+ logger.info(`Namespace reloaded: ${targetLocale}:${namespace}`);
3284
+ }
3285
+ // Helper methods
3286
+ isEmbeddedPlugin(plugin) {
3287
+ return plugin.capabilities.some(
3288
+ (cap) => ["formatters", "processors", "pluralization", "context"].includes(cap)
3289
+ );
3290
+ }
3291
+ isEventPlugin(plugin) {
3292
+ return plugin.capabilities.some(
3293
+ (cap) => [
3294
+ "analytics",
3295
+ "logging",
3296
+ "react",
3297
+ "validation",
3298
+ "dev-tools",
3299
+ "monitoring"
3300
+ ].includes(cap)
3301
+ );
3302
+ }
3303
+ };
3304
+
3305
+ // src/sync/missing-translation-queue.ts
3306
+ var COOLDOWN_MS = 6e4;
3307
+ var MissingTranslationQueue = class {
3308
+ constructor(repository, config, eventEmitter) {
3309
+ __publicField(this, "queue", /* @__PURE__ */ new Map());
3310
+ __publicField(this, "debounceTimer", null);
3311
+ __publicField(this, "repository");
3312
+ __publicField(this, "config");
3313
+ __publicField(this, "eventEmitter");
3314
+ __publicField(this, "recentlySynced", /* @__PURE__ */ new Map());
3315
+ this.repository = repository;
3316
+ this.config = config;
3317
+ this.eventEmitter = eventEmitter ?? null;
3318
+ if (!config.enabled) {
3319
+ logger.debug("MissingTranslationQueue is disabled");
3320
+ } else {
3321
+ logger.debug(
3322
+ `MissingTranslationQueue: debounce=${config.debounceMs}ms, batchSize=${config.batchSize}`
3323
+ );
3324
+ }
3325
+ }
3326
+ /**
3327
+ * Add a missing translation to the queue
3328
+ * Triggers debounce timer or immediate flush if batch size reached
3329
+ */
3330
+ add(item) {
3331
+ if (!this.config.enabled) return;
3332
+ const cacheKey = `${item.locale}:${item.namespace}:${item.key}`;
3333
+ const syncedAt = this.recentlySynced.get(cacheKey);
3334
+ if (syncedAt && Date.now() - syncedAt < COOLDOWN_MS) {
3335
+ logger.debug(`Skipping recently synced key: ${cacheKey}`);
3336
+ return;
3337
+ }
3338
+ this.queue.set(cacheKey, item);
3339
+ logger.debug(
3340
+ `Missing translation queued: ${cacheKey} (queue size: ${this.queue.size})`
3341
+ );
3342
+ if (this.debounceTimer) {
3343
+ clearTimeout(this.debounceTimer);
3344
+ this.debounceTimer = null;
3345
+ }
3346
+ if (this.queue.size >= this.config.batchSize) {
3347
+ logger.debug(`Batch size reached (${this.config.batchSize}), flushing`);
3348
+ this.flush();
3349
+ return;
3350
+ }
3351
+ this.debounceTimer = setTimeout(() => {
3352
+ this.flush();
3353
+ }, this.config.debounceMs);
3354
+ }
3355
+ /**
3356
+ * Flush the queue - send all queued items to the backend
3357
+ * Fire-and-forget pattern (async but not awaited)
3358
+ */
3359
+ flush() {
3360
+ if (this.queue.size === 0) return;
3361
+ const now = Date.now();
3362
+ for (const [key, ts] of this.recentlySynced) {
3363
+ if (now - ts >= COOLDOWN_MS) this.recentlySynced.delete(key);
3364
+ }
3365
+ const batch = Array.from(this.queue.entries()).map(([cacheKey, item]) => ({
3366
+ key: cacheKey,
3367
+ value: item.fallbackValue,
3368
+ styles: item.styles
3369
+ }));
3370
+ const batchSize = batch.length;
3371
+ this.queue.clear();
3372
+ if (this.debounceTimer) {
3373
+ clearTimeout(this.debounceTimer);
3374
+ this.debounceTimer = null;
3375
+ }
3376
+ logger.debug(`Flushing ${batchSize} missing translations`);
3377
+ this.syncBatch(batch).catch((error) => {
3378
+ logger.warn(`Failed to sync ${batchSize} missing translations:`, error);
3379
+ });
3380
+ }
3381
+ /**
3382
+ * Send batch to backend via repository
3383
+ * Uses repository's retry logic from makeRequest()
3384
+ */
3385
+ async syncBatch(batch) {
3386
+ try {
3387
+ const hasStyles = batch.some((item) => item.styles && Object.keys(item.styles).length > 0);
3388
+ if (hasStyles && "setBatchContentUnits" in this.repository) {
3389
+ const contentUnitBatch = batch.map((item) => ({
3390
+ key: item.key,
3391
+ value: { text: item.value, styles: item.styles ?? {} }
3392
+ }));
3393
+ await this.repository.setBatchContentUnits(contentUnitBatch);
3394
+ logger.debug(`Synced ${batch.length} missing translations (ContentUnit)`);
3395
+ } else if ("setBatch" in this.repository) {
3396
+ const plainBatch = batch.map((item) => ({ key: item.key, value: item.value }));
3397
+ await this.repository.setBatch(plainBatch);
3398
+ logger.debug(`Synced ${batch.length} missing translations`);
3399
+ } else {
3400
+ logger.warn(
3401
+ "Repository does not support setBatch, syncing individually"
3402
+ );
3403
+ await this.syncIndividually(batch);
3404
+ }
3405
+ const now = Date.now();
3406
+ const syncedKeys = [];
3407
+ for (const item of batch) {
3408
+ this.recentlySynced.set(item.key, now);
3409
+ syncedKeys.push(item.key);
3410
+ }
3411
+ if (this.eventEmitter) {
3412
+ this.eventEmitter.emit("sync:batch:complete", {
3413
+ payload: { count: batch.length, keys: syncedKeys }
3414
+ });
3415
+ }
3416
+ } catch (error) {
3417
+ logger.error("Batch sync failed:", error);
3418
+ throw error;
3419
+ }
3420
+ }
3421
+ /**
3422
+ * Fallback: sync items individually in parallel
3423
+ */
3424
+ async syncIndividually(batch) {
3425
+ const syncPromises = batch.map(async (item) => {
3426
+ try {
3427
+ await this.repository.set(item.key, item.value);
3428
+ logger.debug(`Synced: ${item.key}`);
3429
+ } catch (error) {
3430
+ logger.warn(`Failed to sync ${item.key}:`, error);
3431
+ }
3432
+ });
3433
+ await Promise.allSettled(syncPromises);
3434
+ }
3435
+ /**
3436
+ * Get current queue size (for debugging)
3437
+ */
3438
+ getQueueSize() {
3439
+ return this.queue.size;
3440
+ }
3441
+ /**
3442
+ * Get queue stats (for debugging)
3443
+ */
3444
+ getStats() {
3445
+ return {
3446
+ queueSize: this.queue.size,
3447
+ enabled: this.config.enabled,
3448
+ debounceMs: this.config.debounceMs,
3449
+ batchSize: this.config.batchSize
3450
+ };
3451
+ }
3452
+ /**
3453
+ * Cleanup - clear timer and queue
3454
+ */
3455
+ destroy() {
3456
+ if (this.debounceTimer) {
3457
+ clearTimeout(this.debounceTimer);
3458
+ this.debounceTimer = null;
3459
+ }
3460
+ this.queue.clear();
3461
+ this.recentlySynced.clear();
3462
+ logger.debug("MissingTranslationQueue destroyed");
3463
+ }
3464
+ };
3465
+
3466
+ // src/builder.ts
3467
+ var LionRapidBuilder = class _LionRapidBuilder {
3468
+ constructor(config) {
3469
+ __publicField(this, "config");
3470
+ __publicField(this, "plugins", []);
3471
+ __publicField(this, "syncStorageRepos", []);
3472
+ __publicField(this, "asyncStorageRepos", []);
3473
+ __publicField(this, "networkRepos", []);
3474
+ __publicField(this, "syncQueue", null);
3475
+ this.config = {
3476
+ defaultLocale: config.defaultLocale || "en",
3477
+ fallbackLocale: config.fallbackLocale || "en",
3478
+ namespace: config.namespace || "app"
3479
+ };
3480
+ }
3481
+ static init(config = {}) {
3482
+ return new _LionRapidBuilder(config);
3483
+ }
3484
+ useSyncStorage(memory) {
3485
+ this.syncStorageRepos.push(memory);
3486
+ return this;
3487
+ }
3488
+ useAsyncStorage(storage) {
3489
+ this.asyncStorageRepos.push(storage);
3490
+ return this;
3491
+ }
3492
+ useNetwork(network) {
3493
+ this.networkRepos.push(network);
3494
+ return this;
3495
+ }
3496
+ use(instance) {
3497
+ switch (instance.type) {
3498
+ case "plugin":
3499
+ this.plugins.push(instance);
3500
+ break;
3501
+ case "memory":
3502
+ this.useSyncStorage(instance);
3503
+ break;
3504
+ case "storage":
3505
+ this.useAsyncStorage(instance);
3506
+ break;
3507
+ case "network":
3508
+ this.useNetwork(instance);
3509
+ break;
3510
+ }
3511
+ return this;
3512
+ }
3513
+ async build() {
3514
+ const eventEmitter = new EventEmitter();
3515
+ const buildBus = eventEmitter.builder();
3516
+ logger.info("Builder started");
3517
+ logger.debug("Builder config", this.config);
3518
+ buildBus.buildStart(this.config.defaultLocale, this.config.namespace);
3519
+ const handlerChain = new HandlerChain(eventEmitter);
3520
+ for (const syncRepo of this.syncStorageRepos) {
3521
+ if (!syncRepo.getConfig().enabled) continue;
3522
+ const syncHandler = new SyncHandler(eventEmitter, syncRepo);
3523
+ buildBus.use("sync");
3524
+ handlerChain.addSyncHandler(syncHandler);
3525
+ }
3526
+ for (const asyncRepo of this.asyncStorageRepos) {
3527
+ if (!asyncRepo.getConfig().enabled) continue;
3528
+ const asyncHandler = new AsyncHandler(eventEmitter, asyncRepo);
3529
+ buildBus.use("async");
3530
+ handlerChain.addAsyncHandler(asyncHandler);
3531
+ }
3532
+ let networkFirst = null;
3533
+ for (const networkRepo of this.networkRepos) {
3534
+ if (!networkRepo.getConfig().enabled || !networkRepo.getConfig().options?.baseUrl)
3535
+ continue;
3536
+ const networkHandler = new NetworkHandler(eventEmitter, networkRepo);
3537
+ if (!networkFirst) {
3538
+ networkFirst = networkHandler;
3539
+ }
3540
+ buildBus.use("network");
3541
+ handlerChain.addAsyncHandler(networkHandler);
3542
+ const config = networkRepo.getConfig();
3543
+ if (config.options?.syncMissing?.enabled) {
3544
+ this.syncQueue = new MissingTranslationQueue(networkRepo, {
3545
+ enabled: config.options.syncMissing.enabled,
3546
+ debounceMs: config.options.syncMissing.debounceMs || 500,
3547
+ batchSize: config.options.syncMissing.batchSize || 50
3548
+ }, eventEmitter);
3549
+ eventEmitter.on("translation:not-found", (envelope) => {
3550
+ if (this.syncQueue) {
3551
+ this.syncQueue.add({
3552
+ key: envelope.payload.key,
3553
+ fallbackValue: envelope.payload.fallbackValue,
3554
+ styles: envelope.payload.styles,
3555
+ locale: envelope.payload.locale,
3556
+ namespace: envelope.payload.namespace
3557
+ });
3558
+ }
3559
+ });
3560
+ logger.info("Missing translation sync enabled");
3561
+ logger.debug("Sync queue config", config.options.syncMissing);
3562
+ }
3563
+ }
3564
+ logger.debug("Handler chain constructed");
3565
+ const facade = new LionRapidCore(
3566
+ eventEmitter,
3567
+ handlerChain,
3568
+ this.config.defaultLocale,
3569
+ this.config.fallbackLocale,
3570
+ this.config.namespace
3571
+ );
3572
+ for (const plugin of this.plugins) {
3573
+ buildBus.use("plugin");
3574
+ await plugin.initialize({ core: facade, bus: eventEmitter });
3575
+ facade.registerPlugin(plugin);
3576
+ logger.info(`Plugin initialized: ${plugin.name}`);
3577
+ logger.debug(`Plugin version: ${plugin.version}`, plugin.capabilities);
3578
+ }
3579
+ const chainInfo = handlerChain.getChainInfo();
3580
+ if (networkFirst) {
3581
+ await this.executePreloading(networkFirst, handlerChain);
3582
+ }
3583
+ logger.info("Builder completed");
3584
+ buildBus.buildDone(
3585
+ chainInfo.handlers.map((r) => r.name),
3586
+ this.plugins.map((p) => p.name)
3587
+ );
3588
+ return facade;
3589
+ }
3590
+ // ✅ CLEAN: Preloading logic
3591
+ async executePreloading(networkHandler, handlerChain) {
3592
+ const eagerPreloads = networkHandler.getPreload().filter((config) => config.eager !== false);
3593
+ if (!eagerPreloads || eagerPreloads.length === 0) {
3594
+ return;
3595
+ }
3596
+ logger.info(`Preloading ${eagerPreloads.length} namespaces`);
3597
+ logger.debug("Preload targets", {
3598
+ namespaces: eagerPreloads.map((c) => `${c.locale}:${c.namespace}`)
3599
+ });
3600
+ const promises = eagerPreloads.map(async (preloadConfig) => {
3601
+ try {
3602
+ const result = await networkHandler.loadNamespace(
3603
+ preloadConfig.locale,
3604
+ preloadConfig.namespace
3605
+ );
3606
+ if (result && result.data && result.status === 200) {
3607
+ const context = {
3608
+ locale: preloadConfig.locale,
3609
+ namespace: preloadConfig.namespace,
3610
+ fallbackLocale: this.config.fallbackLocale
3611
+ };
3612
+ await handlerChain.saveNamespaceToHandlers(result.data, context);
3613
+ logger.debug(
3614
+ `Preloaded: ${preloadConfig.locale}:${preloadConfig.namespace}`
3615
+ );
3616
+ }
3617
+ } catch (error) {
3618
+ logger.warn(
3619
+ `Preload failed: ${preloadConfig.locale}:${preloadConfig.namespace}`,
3620
+ error
3621
+ );
3622
+ }
3623
+ });
3624
+ await Promise.allSettled(promises);
3625
+ logger.info("Preloading completed");
3626
+ }
3627
+ };
3628
+
3629
+ // src/repositories/network-repository.ts
3630
+ var _NetworkRepository = class _NetworkRepository {
3631
+ constructor(config = {}) {
3632
+ __publicField(this, "name", "NetworkRepository");
3633
+ __publicField(this, "type", "network");
3634
+ __publicField(this, "config", {});
3635
+ /** Cached version (opaque string from server) per namespace key "locale:namespace" */
3636
+ __publicField(this, "namespaceVersions", /* @__PURE__ */ new Map());
3637
+ /**
3638
+ * Default endpoint templates with placeholder support
3639
+ * Placeholders: :key, :locale, :namespace
3640
+ */
3641
+ __publicField(this, "DEFAULT_ENDPOINTS", {
3642
+ getTranslation: "/api/integrations/translations/:key",
3643
+ getNamespace: "/api/integrations/translations/:locale/:namespace",
3644
+ setTranslation: "/api/integrations/sync/:key",
3645
+ setNamespace: "/api/integrations/sync/:locale/:namespace",
3646
+ setBatch: "/api/integrations/sync/batch",
3647
+ removeTranslation: "/api/integrations/sync/:key",
3648
+ clearTranslations: "/translations"
3649
+ });
3650
+ this.config = {
3651
+ options: {
3652
+ timeout: config.options?.timeout ?? 5e3,
3653
+ baseUrl: config.options?.baseUrl ?? "",
3654
+ headers: config.options?.headers ?? {}
3655
+ },
3656
+ ...config
3657
+ };
3658
+ }
3659
+ /**
3660
+ * Get single translation from API
3661
+ * Uses platform-specific integration endpoint
3662
+ * @throws {TranslationNotFoundError} When translation doesn't exist (404)
3663
+ * @throws {TranslationLockedError} When translation is locked/not ready (423)
3664
+ * @throws {ServerError} When server returns 5xx error
3665
+ * @throws {NetworkError} When network request fails
3666
+ */
3667
+ async get(key) {
3668
+ if (!this.config?.options?.baseUrl) {
3669
+ logger.debug("API base URL is not set");
3670
+ throw new NetworkError("API base URL is not set");
3671
+ }
3672
+ const url = this.buildUrl("getTranslation", { key });
3673
+ logger.debug(`API GET: ${url}`);
3674
+ try {
3675
+ const response = await this.makeRequest(url, "GET");
3676
+ if (!response.ok) {
3677
+ this.handleHttpError(response, key);
3678
+ }
3679
+ const data = await response.json();
3680
+ logger.debug("API response data:", data);
3681
+ const translation = data.translations[data.meta?.key] || data.translations[key] || Object.values(data.translations)[0];
3682
+ logger.debug(`Fetched translation for key "${key}":`, translation);
3683
+ if (!translation) {
3684
+ throw new TranslationNotFoundError(key);
3685
+ }
3686
+ if (typeof translation === "object" && "text" in translation) {
3687
+ logger.debug(`ContentUnit text: "${translation.text}"`);
3688
+ return translation.text;
3689
+ }
3690
+ return String(translation);
3691
+ } catch (error) {
3692
+ this.handleCaughtError(error);
3693
+ }
3694
+ }
3695
+ /**
3696
+ * Get single translation as full ContentUnit (with styles)
3697
+ * Used by NetworkHandler for individual key lookups that need styles
3698
+ * @throws {TranslationNotFoundError} When translation doesn't exist (404)
3699
+ * @throws {TranslationLockedError} When translation is locked/not ready (423)
3700
+ * @throws {ServerError} When server returns 5xx error
3701
+ * @throws {NetworkError} When network request fails
3702
+ */
3703
+ async getContentUnit(key) {
3704
+ if (!this.config?.options?.baseUrl) {
3705
+ logger.debug("API base URL is not set");
3706
+ throw new NetworkError("API base URL is not set");
3707
+ }
3708
+ const url = this.buildUrl("getTranslation", { key });
3709
+ logger.debug(`API GET ContentUnit: ${url}`);
3710
+ try {
3711
+ const response = await this.makeRequest(url, "GET");
3712
+ if (!response.ok) {
3713
+ this.handleHttpError(response, key);
3714
+ }
3715
+ const data = await response.json();
3716
+ logger.debug("API response data:", data);
3717
+ const translation = data.translations[data.meta?.key] || data.translations[key] || Object.values(data.translations)[0];
3718
+ if (!translation) {
3719
+ throw new TranslationNotFoundError(key);
3720
+ }
3721
+ if (typeof translation === "object" && "text" in translation) {
3722
+ logger.debug(`ContentUnit for "${key}": text="${translation.text}", styles=`, translation.styles);
3723
+ return {
3724
+ text: translation.text,
3725
+ styles: translation.styles || {}
3726
+ };
3727
+ }
3728
+ logger.debug(`Plain string for "${key}": "${translation}" (no styles)`);
3729
+ return {
3730
+ text: String(translation),
3731
+ styles: {}
3732
+ };
3733
+ } catch (error) {
3734
+ this.handleCaughtError(error);
3735
+ }
3736
+ }
3737
+ /**
3738
+ * Get namespace with all translations
3739
+ * @throws {TranslationNotFoundError} When namespace doesn't exist (404)
3740
+ * @throws {TranslationLockedError} When namespace is locked/not ready (423)
3741
+ * @throws {ServerError} When server returns 5xx error
3742
+ * @throws {NetworkError} When network request fails
3743
+ */
3744
+ async getNamespace(locale, namespace) {
3745
+ if (!this.config?.options?.baseUrl) {
3746
+ logger.debug("API base URL is not set");
3747
+ throw new NetworkError("API base URL is not set");
3748
+ }
3749
+ const url = this.buildUrl("getNamespace", { locale, namespace });
3750
+ logger.debug(`API GET Namespace: ${url}`);
3751
+ const namespaceKey = `${locale}:${namespace}`;
3752
+ try {
3753
+ const extraHeaders = {};
3754
+ const cachedVersion = this.getVersion(namespaceKey);
3755
+ if (cachedVersion) {
3756
+ extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
3757
+ }
3758
+ const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
3759
+ if (response.status === 304) {
3760
+ logger.debug(`Namespace ${namespaceKey}: 304 Not Modified`);
3761
+ return null;
3762
+ }
3763
+ if (!response.ok) {
3764
+ this.handleHttpError(response, namespaceKey);
3765
+ }
3766
+ const data = await response.json();
3767
+ logger.debug("API response data:", data);
3768
+ if (!data.translations) {
3769
+ throw new TranslationNotFoundError(namespaceKey);
3770
+ }
3771
+ const version = data.meta?.version != null ? String(data.meta.version) : null;
3772
+ if (version) {
3773
+ this.setVersion(namespaceKey, version);
3774
+ }
3775
+ return data.translations;
3776
+ } catch (error) {
3777
+ this.handleCaughtError(error);
3778
+ }
3779
+ }
3780
+ /**
3781
+ * Save translation to API (if supported)
3782
+ */
3783
+ async set(key, value) {
3784
+ if (!this.config?.options?.baseUrl) {
3785
+ logger.debug("API base URL is not set");
3786
+ return Promise.reject(new Error("API base URL is not set"));
3787
+ }
3788
+ try {
3789
+ const url = this.buildUrl("setTranslation", { key });
3790
+ const response = await this.makeRequest(url, "PUT", {
3791
+ translation: value
3792
+ });
3793
+ if (!response.ok) {
3794
+ throw new Error(`Failed to save translation: ${response.status}`);
3795
+ }
3796
+ } catch (error) {
3797
+ logger.debug(`API set failed for key "${key}":`, error);
3798
+ throw error;
3799
+ }
3800
+ }
3801
+ async setNamespace(locale, namespace, translations) {
3802
+ if (!this.config?.options?.baseUrl) {
3803
+ logger.debug("API base URL is not set");
3804
+ return Promise.reject(new Error("API base URL is not set"));
3805
+ }
3806
+ try {
3807
+ const url = this.buildUrl("setNamespace", { locale, namespace });
3808
+ const response = await this.makeRequest(url, "PUT", {
3809
+ translations
3810
+ });
3811
+ if (!response.ok) {
3812
+ throw new Error(`Failed to save namespace: ${response.status}`);
3813
+ }
3814
+ } catch (error) {
3815
+ logger.debug(
3816
+ `API setNamespace failed for ${locale}:${namespace}:`,
3817
+ error
3818
+ );
3819
+ throw error;
3820
+ }
3821
+ }
3822
+ /**
3823
+ * Batch sync multiple missing translations
3824
+ * Uses new batch endpoint for efficiency
3825
+ */
3826
+ async setBatch(items) {
3827
+ if (!this.config?.options?.baseUrl) {
3828
+ logger.debug("API base URL is not set");
3829
+ return Promise.reject(new Error("API base URL is not set"));
3830
+ }
3831
+ try {
3832
+ const url = this.buildUrl("setBatch");
3833
+ logger.debug(`API POST Batch: ${url} (${items.length} items)`);
3834
+ const response = await this.makeRequest(url, "POST", {
3835
+ translations: items
3836
+ });
3837
+ if (!response.ok) {
3838
+ throw new Error(`Failed to sync batch: ${response.status}`);
3839
+ }
3840
+ logger.debug(`Batch sync successful: ${items.length} items`);
3841
+ } catch (error) {
3842
+ logger.debug(`API setBatch failed for ${items.length} items:`, error);
3843
+ throw error;
3844
+ }
3845
+ }
3846
+ // ════════════════════════════════════════════════════════════════════════════
3847
+ // ContentUnit API Methods
3848
+ // ════════════════════════════════════════════════════════════════════════════
3849
+ /**
3850
+ * Get namespace with all translations as ContentUnit data
3851
+ *
3852
+ * @returns ContentUnitData (Record<string, ContentUnit>)
3853
+ * @throws {TranslationNotFoundError} When namespace doesn't exist (404)
3854
+ * @throws {TranslationLockedError} When namespace is locked/not ready (423)
3855
+ * @throws {ServerError} When server returns 5xx error
3856
+ * @throws {NetworkError} When network request fails
3857
+ */
3858
+ async getNamespaceContentUnits(locale, namespace) {
3859
+ if (!this.config?.options?.baseUrl) {
3860
+ logger.debug("API base URL is not set");
3861
+ throw new NetworkError("API base URL is not set");
3862
+ }
3863
+ const url = this.buildUrl("getNamespace", { locale, namespace });
3864
+ logger.debug(`API GET Namespace ContentUnits: ${url}`);
3865
+ const namespaceKey = `${locale}:${namespace}`;
3866
+ try {
3867
+ const extraHeaders = {};
3868
+ const cachedVersion = this.getVersion(namespaceKey);
3869
+ if (cachedVersion) {
3870
+ extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
3871
+ }
3872
+ const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
3873
+ if (response.status === 304) {
3874
+ logger.debug(`Namespace ContentUnits ${namespaceKey}: 304 Not Modified`);
3875
+ return null;
3876
+ }
3877
+ if (!response.ok) {
3878
+ this.handleHttpError(response, namespaceKey);
3879
+ }
3880
+ const data = await response.json();
3881
+ logger.debug("API response data:", data);
3882
+ if (!data.translations) {
3883
+ throw new TranslationNotFoundError(namespaceKey);
3884
+ }
3885
+ const version = data.meta?.version != null ? String(data.meta.version) : null;
3886
+ if (version) {
3887
+ this.setVersion(namespaceKey, version);
3888
+ }
3889
+ return data.translations;
3890
+ } catch (error) {
3891
+ this.handleCaughtError(error);
3892
+ }
3893
+ }
3894
+ /**
3895
+ * Save ContentUnit to API
3896
+ *
3897
+ * Sends the ContentUnit format to server for storage.
3898
+ * Server should store in ContentUnit format for efficient retrieval.
3899
+ *
3900
+ * @param key - Translation key
3901
+ * @param unit - ContentUnit to save
3902
+ */
3903
+ async setContentUnit(key, unit) {
3904
+ if (!this.config?.options?.baseUrl) {
3905
+ logger.debug("API base URL is not set");
3906
+ return Promise.reject(new Error("API base URL is not set"));
3907
+ }
3908
+ try {
3909
+ const url = this.buildUrl("setTranslation", { key });
3910
+ const response = await this.makeRequest(url, "PUT", {
3911
+ translation: unit,
3912
+ format: "contentunit"
3913
+ // Signal to server that this is ContentUnit format
3914
+ });
3915
+ if (!response.ok) {
3916
+ throw new Error(`Failed to save ContentUnit: ${response.status}`);
3917
+ }
3918
+ logger.debug(`Saved ContentUnit for key "${key}"`);
3919
+ } catch (error) {
3920
+ logger.debug(`API setContentUnit failed for key "${key}":`, error);
3921
+ throw error;
3922
+ }
3923
+ }
3924
+ /**
3925
+ * Batch sync multiple ContentUnits
3926
+ *
3927
+ * Sends ContentUnit format to server for batch storage.
3928
+ *
3929
+ * @param items - Array of key-ContentUnit pairs
3930
+ */
3931
+ async setBatchContentUnits(items) {
3932
+ if (!this.config?.options?.baseUrl) {
3933
+ logger.debug("API base URL is not set");
3934
+ return Promise.reject(new Error("API base URL is not set"));
3935
+ }
3936
+ try {
3937
+ const url = this.buildUrl("setBatch");
3938
+ logger.debug(
3939
+ `API POST Batch ContentUnits: ${url} (${items.length} items)`
3940
+ );
3941
+ const response = await this.makeRequest(url, "POST", {
3942
+ translations: items,
3943
+ format: "contentunit"
3944
+ // Signal to server that this is ContentUnit format
3945
+ });
3946
+ if (!response.ok) {
3947
+ throw new Error(`Failed to sync batch ContentUnits: ${response.status}`);
3948
+ }
3949
+ logger.debug(`Batch ContentUnit sync successful: ${items.length} items`);
3950
+ } catch (error) {
3951
+ logger.debug(
3952
+ `API setBatchContentUnits failed for ${items.length} items:`,
3953
+ error
3954
+ );
3955
+ throw error;
3956
+ }
3957
+ }
3958
+ /**
3959
+ * Remove translation from API (if supported)
3960
+ */
3961
+ async remove(key) {
3962
+ if (!this.config?.options?.baseUrl) {
3963
+ logger.debug("API base URL is not set");
3964
+ return Promise.reject(new Error("API base URL is not set"));
3965
+ }
3966
+ try {
3967
+ const url = this.buildUrl("removeTranslation", { key });
3968
+ const response = await this.makeRequest(url, "DELETE");
3969
+ if (!response.ok) {
3970
+ throw new Error(`Failed to remove translation: ${response.status}`);
3971
+ }
3972
+ } catch (error) {
3973
+ logger.debug(`API remove failed for key "${key}":`, error);
3974
+ throw error;
3975
+ }
3976
+ }
3977
+ /**
3978
+ * Clear all translations (if supported)
3979
+ */
3980
+ async clear() {
3981
+ if (!this.config?.options?.baseUrl) {
3982
+ logger.debug("API base URL is not set");
3983
+ return Promise.reject(new Error("API base URL is not set"));
3984
+ }
3985
+ try {
3986
+ const url = this.buildUrl("clearTranslations");
3987
+ const response = await this.makeRequest(url, "DELETE");
3988
+ if (!response.ok) {
3989
+ throw new Error(`Failed to clear translations: ${response.status}`);
3990
+ }
3991
+ } catch (error) {
3992
+ logger.debug("API clear failed:", error);
3993
+ throw error;
3994
+ }
3995
+ }
3996
+ getConfig() {
3997
+ return this.config;
3998
+ }
3999
+ /** Read persisted version from localStorage, falling back to in-memory map */
4000
+ getVersion(namespaceKey) {
4001
+ const mem = this.namespaceVersions.get(namespaceKey);
4002
+ if (mem) return mem;
4003
+ try {
4004
+ const stored = localStorage.getItem(_NetworkRepository.VERSION_PREFIX + namespaceKey);
4005
+ if (stored) {
4006
+ this.namespaceVersions.set(namespaceKey, stored);
4007
+ return stored;
4008
+ }
4009
+ } catch {
4010
+ }
4011
+ return void 0;
4012
+ }
4013
+ /** Persist version to both in-memory map and localStorage */
4014
+ setVersion(namespaceKey, version) {
4015
+ this.namespaceVersions.set(namespaceKey, version);
4016
+ try {
4017
+ localStorage.setItem(_NetworkRepository.VERSION_PREFIX + namespaceKey, version);
4018
+ } catch {
4019
+ }
4020
+ }
4021
+ /**
4022
+ * Handle HTTP error responses by mapping to domain errors
4023
+ * @throws {TranslationNotFoundError} When status is 404
4024
+ * @throws {TranslationLockedError} When status is 423
4025
+ * @throws {ServerError} When status is 5xx
4026
+ * @throws {NetworkError} For other client errors
4027
+ */
4028
+ handleHttpError(response, key) {
4029
+ logger.debug(`API response: ${response.status} ${response.statusText}`);
4030
+ if (response.status === 404) {
4031
+ throw new TranslationNotFoundError(key);
4032
+ }
4033
+ if (response.status === 423) {
4034
+ const retryAfter = response.headers.get("Retry-After");
4035
+ throw new TranslationLockedError(
4036
+ key,
4037
+ retryAfter ? parseInt(retryAfter, 10) * 1e3 : void 0
4038
+ );
4039
+ }
4040
+ if (response.status >= 500 && response.status < 600) {
4041
+ throw new ServerError(key, response.status);
4042
+ }
4043
+ throw new NetworkError(`HTTP ${response.status}: ${response.statusText}`);
4044
+ }
4045
+ /**
4046
+ * Handle caught errors by wrapping non-domain errors
4047
+ * @throws {TranslationError} Always throws - propagates domain errors or wraps unexpected ones
4048
+ */
4049
+ handleCaughtError(error) {
4050
+ if (error instanceof TranslationNotFoundError || error instanceof TranslationLockedError || error instanceof ServerError || error instanceof NetworkError) {
4051
+ throw error;
4052
+ }
4053
+ throw new NetworkError("Network request failed", error);
4054
+ }
4055
+ /**
4056
+ * Build URL from endpoint template and parameters
4057
+ * Supports placeholders: :key, :locale, :namespace
4058
+ * @param endpoint - Endpoint key from DEFAULT_ENDPOINTS
4059
+ * @param params - Parameters to replace placeholders with
4060
+ * @returns Complete URL with baseUrl + endpoint path
4061
+ */
4062
+ buildUrl(endpoint, params = {}) {
4063
+ const baseUrl = this.config.options?.baseUrl || "";
4064
+ let endpointTemplate = this.config.options?.endpoints?.[endpoint] || this.DEFAULT_ENDPOINTS[endpoint];
4065
+ if (!endpointTemplate.startsWith("/")) {
4066
+ endpointTemplate = `/${endpointTemplate}`;
4067
+ }
4068
+ let path = endpointTemplate;
4069
+ for (const [key, value] of Object.entries(params)) {
4070
+ const placeholder = new RegExp(`:${key}`, "g");
4071
+ path = path.replace(placeholder, encodeURIComponent(value));
4072
+ }
4073
+ return `${baseUrl}${path}`;
4074
+ }
4075
+ /**
4076
+ * Make HTTP request with timeout and headers
4077
+ */
4078
+ async makeRequest(url, method, body, extraHeaders) {
4079
+ const controller = new AbortController();
4080
+ const timeout = this.config.options?.timeout ?? 1e4;
4081
+ const timeoutId = setTimeout(
4082
+ () => controller.abort(),
4083
+ timeout
4084
+ );
4085
+ try {
4086
+ const headers = {
4087
+ "Content-Type": "application/json",
4088
+ ...this.config.options?.headers,
4089
+ ...extraHeaders
4090
+ };
4091
+ if (this.config.options?.apiKey) {
4092
+ headers["Authorization"] = `Bearer ${this.config.options.apiKey}`;
4093
+ }
4094
+ const response = await fetch(url, {
4095
+ method,
4096
+ headers,
4097
+ body: body ? JSON.stringify(body) : void 0,
4098
+ signal: controller.signal
4099
+ });
4100
+ return response;
4101
+ } finally {
4102
+ clearTimeout(timeoutId);
4103
+ }
4104
+ }
4105
+ };
4106
+ /** localStorage key prefix for persisted versions */
4107
+ __publicField(_NetworkRepository, "VERSION_PREFIX", "lionrapid:v:");
4108
+ var NetworkRepository = _NetworkRepository;
4109
+
4110
+ // src/repositories/memory-repository.ts
4111
+ var MemoryRepository = class {
4112
+ constructor(config) {
4113
+ __publicField(this, "name", "MemoryRepository");
4114
+ __publicField(this, "type", "memory");
4115
+ __publicField(this, "config", {});
4116
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
4117
+ __publicField(this, "accessOrder", /* @__PURE__ */ new Map());
4118
+ __publicField(this, "maxSize", 1e3);
4119
+ this.config = config;
4120
+ this.maxSize = this.config.options?.maxSize || 1e3;
4121
+ }
4122
+ get(key) {
4123
+ const value = this.cache.get(key);
4124
+ if (value !== void 0) {
4125
+ this.accessOrder.set(key, Date.now());
4126
+ return value;
4127
+ }
4128
+ return null;
4129
+ }
4130
+ set(key, value) {
4131
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
4132
+ this.evictOldest();
4133
+ }
4134
+ this.cache.set(key, value);
4135
+ this.accessOrder.set(key, Date.now());
4136
+ }
4137
+ remove(key) {
4138
+ this.cache.delete(key);
4139
+ this.accessOrder.delete(key);
4140
+ }
4141
+ clear() {
4142
+ this.cache.clear();
4143
+ this.accessOrder.clear();
4144
+ }
4145
+ /**
4146
+ * Clear all entries for a specific namespace
4147
+ * Removes both the namespace cache entry and all individual translation keys
4148
+ */
4149
+ clearNamespace(locale, namespace) {
4150
+ const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
4151
+ const keyPrefix = `${locale}:${namespace}:`;
4152
+ this.cache.delete(namespaceCacheKey);
4153
+ this.accessOrder.delete(namespaceCacheKey);
4154
+ for (const key of this.cache.keys()) {
4155
+ if (key.startsWith(keyPrefix)) {
4156
+ this.cache.delete(key);
4157
+ this.accessOrder.delete(key);
4158
+ }
4159
+ }
4160
+ }
4161
+ getNamespace(locale, namespace) {
4162
+ const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
4163
+ const namespaceData = this.cache.get(namespaceCacheKey);
4164
+ if (!namespaceData) return null;
4165
+ try {
4166
+ return JSON.parse(namespaceData);
4167
+ } catch (error) {
4168
+ this.cache.delete(namespaceCacheKey);
4169
+ return null;
4170
+ }
4171
+ }
4172
+ setNamespace(locale, namespace, translations) {
4173
+ const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
4174
+ this.cache.set(namespaceCacheKey, JSON.stringify(translations));
4175
+ }
4176
+ evictOldest() {
4177
+ let oldestKey = "";
4178
+ let oldestTime = Date.now();
4179
+ for (const [key, time] of this.accessOrder.entries()) {
4180
+ if (time < oldestTime) {
4181
+ oldestTime = time;
4182
+ oldestKey = key;
4183
+ }
4184
+ }
4185
+ if (oldestKey) {
4186
+ this.cache.delete(oldestKey);
4187
+ this.accessOrder.delete(oldestKey);
4188
+ }
4189
+ }
4190
+ /**
4191
+ * Get all cache entries for debugging
4192
+ */
4193
+ getEntries() {
4194
+ const entries = {};
4195
+ for (const [key, value] of this.cache.entries()) {
4196
+ entries[key] = value;
4197
+ }
4198
+ return entries;
4199
+ }
4200
+ getConfig() {
4201
+ return this.config;
4202
+ }
4203
+ };
4204
+
4205
+ // src/content/validator/ContentValidator.ts
4206
+ var ContentValidator = class {
4207
+ /**
4208
+ * Validate a ContentUnit for structural integrity
4209
+ *
4210
+ * @param unit - ContentUnit to validate
4211
+ * @returns Validation result with errors and warnings
4212
+ */
4213
+ validate(unit) {
4214
+ const errors = [];
4215
+ const warnings = [];
4216
+ this.validateTagBalance(unit.text, errors);
4217
+ this.validateStyleReferences(unit.text, unit.styles, errors, warnings);
4218
+ this.validateICUBraces(unit.text, errors);
4219
+ return {
4220
+ valid: errors.length === 0,
4221
+ errors,
4222
+ warnings
4223
+ };
4224
+ }
4225
+ /**
4226
+ * Validate that all indexed tags are properly balanced and correctly nested
4227
+ */
4228
+ validateTagBalance(text, errors) {
4229
+ const tagPattern = /<(\/?)?(\d+)(\/)?>/g;
4230
+ const tagStack = [];
4231
+ let match;
4232
+ while ((match = tagPattern.exec(text)) !== null) {
4233
+ const [, closingSlash, indexStr, selfClosingSlash] = match;
4234
+ if (selfClosingSlash) {
4235
+ continue;
4236
+ }
4237
+ if (closingSlash) {
4238
+ if (tagStack.length === 0) {
4239
+ errors.push({
4240
+ code: "UNBALANCED_TAGS",
4241
+ message: `Closing tag </${indexStr}> has no matching opening tag`,
4242
+ path: `text[${match.index}]`
4243
+ });
4244
+ } else {
4245
+ const lastOpen = tagStack[tagStack.length - 1];
4246
+ if (lastOpen.index === indexStr) {
4247
+ tagStack.pop();
4248
+ } else {
4249
+ const foundIndex = tagStack.findIndex((t) => t.index === indexStr);
4250
+ if (foundIndex >= 0) {
4251
+ errors.push({
4252
+ code: "UNBALANCED_TAGS",
4253
+ message: `Improperly nested: </${indexStr}> should close before </${lastOpen.index}>`,
4254
+ path: `text[${match.index}]`
4255
+ });
4256
+ tagStack.splice(foundIndex, 1);
4257
+ } else {
4258
+ errors.push({
4259
+ code: "UNBALANCED_TAGS",
4260
+ message: `Closing tag </${indexStr}> has no matching opening tag`,
4261
+ path: `text[${match.index}]`
4262
+ });
4263
+ }
4264
+ }
4265
+ }
4266
+ } else {
4267
+ tagStack.push({ index: indexStr, position: match.index });
4268
+ }
4269
+ }
4270
+ for (const { index } of tagStack) {
4271
+ errors.push({
4272
+ code: "UNBALANCED_TAGS",
4273
+ message: `Opening tag <${index}> has no matching closing tag`,
4274
+ path: "text"
4275
+ });
4276
+ }
4277
+ }
4278
+ /**
4279
+ * Validate style references - check that used tags have styles and vice versa
4280
+ */
4281
+ validateStyleReferences(text, styles, errors, warnings) {
4282
+ const usedIndices = /* @__PURE__ */ new Set();
4283
+ const tagPattern = /<\/?(\d+)(?:\/)?>/g;
4284
+ let match;
4285
+ while ((match = tagPattern.exec(text)) !== null) {
4286
+ usedIndices.add(match[1]);
4287
+ }
4288
+ for (const index of usedIndices) {
4289
+ if (!styles[index] || styles[index].length === 0) {
4290
+ warnings.push({
4291
+ code: "EMPTY_STYLE",
4292
+ message: `Tag <${index}> has no style definition, will render as <span>`
4293
+ });
4294
+ }
4295
+ }
4296
+ for (const index of Object.keys(styles)) {
4297
+ if (!usedIndices.has(index)) {
4298
+ warnings.push({
4299
+ code: "UNUSED_TAG",
4300
+ message: `Style for tag <${index}> is defined but tag is not used in text`
4301
+ });
4302
+ }
4303
+ }
4304
+ }
4305
+ /**
4306
+ * Validate ICU MessageFormat brace balance
4307
+ */
4308
+ validateICUBraces(text, errors) {
4309
+ let depth = 0;
4310
+ let inString = false;
4311
+ let stringChar = "";
4312
+ for (let i = 0; i < text.length; i++) {
4313
+ const char = text[i];
4314
+ const prevChar = i > 0 ? text[i - 1] : "";
4315
+ if ((char === '"' || char === "'") && prevChar !== "\\") {
4316
+ if (!inString) {
4317
+ inString = true;
4318
+ stringChar = char;
4319
+ } else if (char === stringChar) {
4320
+ inString = false;
4321
+ stringChar = "";
4322
+ }
4323
+ continue;
4324
+ }
4325
+ if (inString) continue;
4326
+ if (char === "{") {
4327
+ depth++;
4328
+ } else if (char === "}") {
4329
+ depth--;
4330
+ if (depth < 0) {
4331
+ errors.push({
4332
+ code: "INVALID_ICU",
4333
+ message: `Unbalanced ICU braces: unexpected '}' at position ${i}`,
4334
+ path: `text[${i}]`
4335
+ });
4336
+ depth = 0;
4337
+ }
4338
+ }
4339
+ }
4340
+ if (depth > 0) {
4341
+ errors.push({
4342
+ code: "INVALID_ICU",
4343
+ message: `Unbalanced ICU braces: ${depth} unclosed '{'`,
4344
+ path: "text"
4345
+ });
4346
+ }
4347
+ const emptyVarPattern = /\{\s*[,}]/;
4348
+ if (emptyVarPattern.test(text)) {
4349
+ errors.push({
4350
+ code: "INVALID_ICU",
4351
+ message: "ICU variable name is empty or missing",
4352
+ path: "text"
4353
+ });
4354
+ }
4355
+ }
4356
+ };
4357
+
4358
+ // src/browser/storage/localstorage-repository.ts
4359
+ var STORAGE_PREFIX = "lionrapid:";
4360
+ var VERSION_KEY = `${STORAGE_PREFIX}version`;
4361
+ var CURRENT_VERSION = "1";
4362
+ var LocalStorageRepository = class {
4363
+ constructor(config = {}) {
4364
+ __publicField(this, "name", "LocalStorageRepository");
4365
+ __publicField(this, "type", "memory");
4366
+ __publicField(this, "config");
4367
+ __publicField(this, "available");
4368
+ __publicField(this, "prefix");
4369
+ __publicField(this, "defaultTTL");
4370
+ __publicField(this, "maxSize");
4371
+ this.config = {
4372
+ enabled: config.enabled ?? true,
4373
+ priority: config.priority ?? 0,
4374
+ options: {
4375
+ prefix: config.options?.prefix ?? STORAGE_PREFIX,
4376
+ ttl: config.options?.ttl ?? 24 * 60 * 60 * 1e3,
4377
+ maxSize: config.options?.maxSize ?? 500,
4378
+ ...config.options
4379
+ }
4380
+ };
4381
+ this.prefix = this.config.options?.prefix ?? STORAGE_PREFIX;
4382
+ this.defaultTTL = this.config.options?.ttl ?? 24 * 60 * 60 * 1e3;
4383
+ this.maxSize = this.config.options?.maxSize ?? 500;
4384
+ this.available = this.checkAvailability();
4385
+ if (this.available) {
4386
+ this.migrateIfNeeded();
4387
+ }
4388
+ }
4389
+ checkAvailability() {
4390
+ if (typeof window === "undefined") {
4391
+ return false;
4392
+ }
4393
+ try {
4394
+ const testKey = `${this.prefix}__test__`;
4395
+ window.localStorage.setItem(testKey, "test");
4396
+ window.localStorage.removeItem(testKey);
4397
+ return true;
4398
+ } catch {
4399
+ return false;
4400
+ }
4401
+ }
4402
+ migrateIfNeeded() {
4403
+ try {
4404
+ const storedVersion = window.localStorage.getItem(VERSION_KEY);
4405
+ if (storedVersion !== CURRENT_VERSION) {
4406
+ this.clear();
4407
+ window.localStorage.setItem(VERSION_KEY, CURRENT_VERSION);
4408
+ }
4409
+ } catch {
4410
+ }
4411
+ }
4412
+ get(key) {
4413
+ if (!this.available) return null;
4414
+ try {
4415
+ const fullKey = this.prefix + key;
4416
+ const itemStr = window.localStorage.getItem(fullKey);
4417
+ if (!itemStr) return null;
4418
+ const item = JSON.parse(itemStr);
4419
+ if (item.expiry !== null && Date.now() > item.expiry) {
4420
+ window.localStorage.removeItem(fullKey);
4421
+ return null;
4422
+ }
4423
+ return item.value;
4424
+ } catch {
4425
+ return null;
4426
+ }
4427
+ }
4428
+ set(key, value) {
4429
+ if (!this.available) return;
4430
+ const fullKey = this.prefix + key;
4431
+ const item = {
4432
+ value,
4433
+ expiry: this.defaultTTL ? Date.now() + this.defaultTTL : null,
4434
+ timestamp: Date.now()
4435
+ };
4436
+ try {
4437
+ const entryCount = this.getEntryCount();
4438
+ if (entryCount >= this.maxSize) {
4439
+ this.evictOldest();
4440
+ }
4441
+ window.localStorage.setItem(fullKey, JSON.stringify(item));
4442
+ } catch (error) {
4443
+ if (this.isQuotaError(error)) {
4444
+ this.evictOldest();
4445
+ try {
4446
+ window.localStorage.setItem(fullKey, JSON.stringify(item));
4447
+ } catch {
4448
+ }
4449
+ }
4450
+ }
4451
+ }
4452
+ remove(key) {
4453
+ if (!this.available) return;
4454
+ try {
4455
+ window.localStorage.removeItem(this.prefix + key);
4456
+ } catch {
4457
+ }
4458
+ }
4459
+ clear() {
4460
+ if (!this.available) return;
4461
+ try {
4462
+ const keysToRemove = [];
4463
+ for (let i = 0; i < window.localStorage.length; i++) {
4464
+ const key = window.localStorage.key(i);
4465
+ if (key?.startsWith(this.prefix) && key !== VERSION_KEY) {
4466
+ keysToRemove.push(key);
4467
+ }
4468
+ }
4469
+ keysToRemove.forEach((k) => window.localStorage.removeItem(k));
4470
+ } catch {
4471
+ }
4472
+ }
4473
+ clearNamespace(locale, namespace) {
4474
+ if (!this.available) return;
4475
+ const namespaceCacheKey = `@${locale}:${namespace}`;
4476
+ const keyPrefix = `${locale}:${namespace}:`;
4477
+ try {
4478
+ const keysToRemove = [];
4479
+ for (let i = 0; i < window.localStorage.length; i++) {
4480
+ const key = window.localStorage.key(i);
4481
+ if (!key?.startsWith(this.prefix)) continue;
4482
+ const innerKey = key.slice(this.prefix.length);
4483
+ if (innerKey === namespaceCacheKey || innerKey.startsWith(keyPrefix)) {
4484
+ keysToRemove.push(key);
4485
+ }
4486
+ }
4487
+ keysToRemove.forEach((k) => window.localStorage.removeItem(k));
4488
+ } catch {
4489
+ }
4490
+ }
4491
+ getNamespace(locale, namespace) {
4492
+ const namespaceCacheKey = `@${locale}:${namespace}`;
4493
+ const value = this.get(namespaceCacheKey);
4494
+ if (!value) return null;
4495
+ try {
4496
+ return JSON.parse(value);
4497
+ } catch {
4498
+ this.remove(namespaceCacheKey);
4499
+ return null;
4500
+ }
4501
+ }
4502
+ setNamespace(locale, namespace, translations) {
4503
+ const namespaceCacheKey = `@${locale}:${namespace}`;
4504
+ this.set(namespaceCacheKey, JSON.stringify(translations));
4505
+ }
4506
+ getEntries() {
4507
+ const entries = {};
4508
+ if (!this.available) return entries;
4509
+ try {
4510
+ for (let i = 0; i < window.localStorage.length; i++) {
4511
+ const fullKey = window.localStorage.key(i);
4512
+ if (!fullKey?.startsWith(this.prefix) || fullKey === VERSION_KEY) {
4513
+ continue;
4514
+ }
4515
+ const key = fullKey.slice(this.prefix.length);
4516
+ const value = this.get(key);
4517
+ if (value !== null) {
4518
+ entries[key] = value;
4519
+ }
4520
+ }
4521
+ } catch {
4522
+ }
4523
+ return entries;
4524
+ }
4525
+ getConfig() {
4526
+ return this.config;
4527
+ }
4528
+ isAvailable() {
4529
+ return this.available;
4530
+ }
4531
+ getEntryCount() {
4532
+ if (!this.available) return 0;
4533
+ let count = 0;
4534
+ try {
4535
+ for (let i = 0; i < window.localStorage.length; i++) {
4536
+ const key = window.localStorage.key(i);
4537
+ if (key?.startsWith(this.prefix) && key !== VERSION_KEY) {
4538
+ count++;
4539
+ }
4540
+ }
4541
+ } catch {
4542
+ }
4543
+ return count;
4544
+ }
4545
+ isQuotaError(error) {
4546
+ if (error instanceof DOMException) {
4547
+ return error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED";
4548
+ }
4549
+ return false;
4550
+ }
4551
+ evictOldest() {
4552
+ if (!this.available) return;
4553
+ try {
4554
+ const entries = [];
4555
+ for (let i = 0; i < window.localStorage.length; i++) {
4556
+ const fullKey = window.localStorage.key(i);
4557
+ if (!fullKey?.startsWith(this.prefix) || fullKey === VERSION_KEY) {
4558
+ continue;
4559
+ }
4560
+ try {
4561
+ const itemStr = window.localStorage.getItem(fullKey);
4562
+ if (itemStr) {
4563
+ const item = JSON.parse(itemStr);
4564
+ entries.push({ key: fullKey, timestamp: item.timestamp });
4565
+ }
4566
+ } catch {
4567
+ window.localStorage.removeItem(fullKey);
4568
+ }
4569
+ }
4570
+ entries.sort((a, b) => a.timestamp - b.timestamp);
4571
+ const removeCount = Math.max(1, Math.floor(entries.length * 0.25));
4572
+ for (let i = 0; i < removeCount && i < entries.length; i++) {
4573
+ window.localStorage.removeItem(entries[i].key);
4574
+ }
4575
+ } catch {
4576
+ }
4577
+ }
4578
+ getCacheStats() {
4579
+ const size = this.getEntryCount();
4580
+ const usage = this.maxSize > 0 ? `${Math.round(size / this.maxSize * 100)}%` : "0%";
4581
+ return {
4582
+ size,
4583
+ maxSize: this.maxSize,
4584
+ usage
4585
+ };
4586
+ }
4587
+ };
4588
+
4589
+ // src/browser/storage/cache-storage-repository.ts
4590
+ var CACHE_NAME = "lionrapid-v1";
4591
+ var CACHE_PREFIX = "lionrapid:";
4592
+ var CacheStorageRepository = class {
4593
+ constructor(config = {}) {
4594
+ __publicField(this, "name", "CacheStorageRepository");
4595
+ __publicField(this, "type", "storage");
4596
+ __publicField(this, "config");
4597
+ __publicField(this, "cacheName");
4598
+ __publicField(this, "defaultTTL");
4599
+ __publicField(this, "available");
4600
+ this.config = {
4601
+ enabled: config.enabled ?? true,
4602
+ priority: config.priority ?? 0,
4603
+ options: {
4604
+ cacheName: config.options?.cacheName ?? CACHE_NAME,
4605
+ ttl: config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3,
4606
+ ...config.options
4607
+ }
4608
+ };
4609
+ this.cacheName = this.config.options?.cacheName ?? CACHE_NAME;
4610
+ this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
4611
+ this.available = this.checkAvailability();
4612
+ }
4613
+ checkAvailability() {
4614
+ return typeof caches !== "undefined";
4615
+ }
4616
+ async get(key) {
4617
+ if (!this.available) {
4618
+ throw new TranslationNotFoundError(key);
4619
+ }
4620
+ try {
4621
+ const cache = await caches.open(this.cacheName);
4622
+ const response = await cache.match(CACHE_PREFIX + key);
4623
+ if (!response) {
4624
+ throw new TranslationNotFoundError(key);
4625
+ }
4626
+ const data = await response.json();
4627
+ if (data.expiry !== null && Date.now() > data.expiry) {
4628
+ await cache.delete(CACHE_PREFIX + key);
4629
+ throw new TranslationNotFoundError(key);
4630
+ }
4631
+ return data.value;
4632
+ } catch (error) {
4633
+ if (error instanceof TranslationNotFoundError) {
4634
+ throw error;
4635
+ }
4636
+ throw new TranslationNotFoundError(key);
4637
+ }
4638
+ }
4639
+ async set(key, value) {
4640
+ if (!this.available) return;
4641
+ try {
4642
+ const cache = await caches.open(this.cacheName);
4643
+ const data = {
4644
+ value,
4645
+ expiry: this.defaultTTL ? Date.now() + this.defaultTTL : null,
4646
+ timestamp: Date.now()
4647
+ };
4648
+ await cache.put(
4649
+ CACHE_PREFIX + key,
4650
+ new Response(JSON.stringify(data), {
4651
+ headers: { "Content-Type": "application/json" }
4652
+ })
4653
+ );
4654
+ } catch {
4655
+ }
4656
+ }
4657
+ async remove(key) {
4658
+ if (!this.available) return;
4659
+ try {
4660
+ const cache = await caches.open(this.cacheName);
4661
+ await cache.delete(CACHE_PREFIX + key);
4662
+ } catch {
4663
+ }
4664
+ }
4665
+ async clear() {
4666
+ if (!this.available) return;
4667
+ try {
4668
+ const cache = await caches.open(this.cacheName);
4669
+ const keys = await cache.keys();
4670
+ await Promise.all(
4671
+ keys.filter((request) => request.url.includes(CACHE_PREFIX)).map((request) => cache.delete(request))
4672
+ );
4673
+ } catch {
4674
+ }
4675
+ }
4676
+ async clearNamespace(locale, namespace) {
4677
+ if (!this.available) return;
4678
+ const namespaceCacheKey = `@${locale}:${namespace}`;
4679
+ const keyPrefix = `${locale}:${namespace}:`;
4680
+ try {
4681
+ const cache = await caches.open(this.cacheName);
4682
+ const keys = await cache.keys();
4683
+ await Promise.all(
4684
+ keys.filter((request) => {
4685
+ const url = request.url;
4686
+ const key = url.includes(CACHE_PREFIX) ? url.substring(url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length) : "";
4687
+ return key === namespaceCacheKey || key.startsWith(keyPrefix);
4688
+ }).map((request) => cache.delete(request))
4689
+ );
4690
+ } catch {
4691
+ }
4692
+ }
4693
+ async getNamespace(locale, namespace) {
4694
+ const namespaceCacheKey = `@${locale}:${namespace}`;
4695
+ const value = await this.get(namespaceCacheKey);
4696
+ try {
4697
+ return JSON.parse(value);
4698
+ } catch {
4699
+ await this.remove(namespaceCacheKey);
4700
+ throw new TranslationNotFoundError(namespaceCacheKey);
4701
+ }
4702
+ }
4703
+ async setNamespace(locale, namespace, translations) {
4704
+ const namespaceCacheKey = `@${locale}:${namespace}`;
4705
+ await this.set(namespaceCacheKey, JSON.stringify(translations));
4706
+ }
4707
+ getConfig() {
4708
+ return this.config;
4709
+ }
4710
+ isAvailable() {
4711
+ return this.available;
4712
+ }
4713
+ async getEntries() {
4714
+ const entries = {};
4715
+ if (!this.available) return entries;
4716
+ try {
4717
+ const cache = await caches.open(this.cacheName);
4718
+ const keys = await cache.keys();
4719
+ for (const request of keys) {
4720
+ const url = request.url;
4721
+ if (!url.includes(CACHE_PREFIX)) continue;
4722
+ const key = url.substring(
4723
+ url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length
4724
+ );
4725
+ try {
4726
+ const value = await this.get(key);
4727
+ entries[key] = value;
4728
+ } catch {
4729
+ }
4730
+ }
4731
+ } catch {
4732
+ }
4733
+ return entries;
4734
+ }
4735
+ async getCacheStats() {
4736
+ if (!this.available) {
4737
+ return { size: 0, cacheName: this.cacheName };
4738
+ }
4739
+ try {
4740
+ const cache = await caches.open(this.cacheName);
4741
+ const keys = await cache.keys();
4742
+ const size = keys.filter((r) => r.url.includes(CACHE_PREFIX)).length;
4743
+ return { size, cacheName: this.cacheName };
4744
+ } catch {
4745
+ return { size: 0, cacheName: this.cacheName };
4746
+ }
4747
+ }
4748
+ };
4749
+
4750
+ // src/index.ts
4751
+ var VERSION = "1.0.0";
4752
+ export {
4753
+ CacheManager,
4754
+ CacheStorageRepository,
4755
+ Colors,
4756
+ ContentValidator,
4757
+ DefaultStyleMapper,
4758
+ EventEmitter,
4759
+ HTMLSerializer,
4760
+ HandlerChain,
4761
+ LionRapidBuilder,
4762
+ LionRapidCore,
4763
+ LocalStorageRepository,
4764
+ LogLevel,
4765
+ Logger,
4766
+ MarkupParser,
4767
+ MemoryRepository,
4768
+ MissingTranslationQueue,
4769
+ NetworkError,
4770
+ NetworkRepository,
4771
+ ServerError,
4772
+ TranslationError,
4773
+ TranslationLockedError,
4774
+ TranslationNotFoundError,
4775
+ VERSION,
4776
+ createPackageLogger,
4777
+ isClientError,
4778
+ isRetryableError,
4779
+ isTranslationError,
4780
+ logger,
4781
+ measure,
4782
+ parseKeyPath,
4783
+ prepareTranslationRequest,
4784
+ resolveNestedKey
4785
+ };
4786
+ //# sourceMappingURL=lionrapid.esm.js.map