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