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