documentation-hub 5.7.2

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 (271) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +64 -0
  3. package/.github/workflows/ci.yml +39 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/Current.md +97 -0
  6. package/DocHub_Image.png +0 -0
  7. package/README.md +666 -0
  8. package/USER_GUIDE.md +1173 -0
  9. package/Updater.md +311 -0
  10. package/build/256x256.png +0 -0
  11. package/build/512x512.png +0 -0
  12. package/build/app-update.yml +4 -0
  13. package/build/create-icon.js +208 -0
  14. package/build/icon.ico +0 -0
  15. package/build/icon.png +0 -0
  16. package/build/icon_1024x1024.png +0 -0
  17. package/dist/assets/Analytics-BpsG9895.js +1 -0
  18. package/dist/assets/Card-IAZin8kp.js +1 -0
  19. package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
  20. package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
  21. package/dist/assets/Documents-CqZ25axS.js +1 -0
  22. package/dist/assets/Input-l89xwXBi.js +1 -0
  23. package/dist/assets/Reporting-DqdHJY_a.js +1 -0
  24. package/dist/assets/Search-XNbu5z_3.js +1 -0
  25. package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
  26. package/dist/assets/Sessions-ClZOPYNc.js +1 -0
  27. package/dist/assets/Settings-DUEHGURa.js +11 -0
  28. package/dist/assets/index-8xUe8ptc.js +24 -0
  29. package/dist/assets/index-RYyJqF7O.css +1 -0
  30. package/dist/assets/path-BkOl0AGO.js +1 -0
  31. package/dist/assets/promises-ID_B9S-h.js +1 -0
  32. package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
  33. package/dist/assets/useToast-yRSO1dkm.js +1 -0
  34. package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
  35. package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
  36. package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
  37. package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
  38. package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
  39. package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
  40. package/dist/electron/main-CXkNtyv-.js +19789 -0
  41. package/dist/electron/main.js +5 -0
  42. package/dist/electron/preload.js +1 -0
  43. package/dist/icon.png +0 -0
  44. package/dist/index.html +27 -0
  45. package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
  46. package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
  47. package/docs/README.md +115 -0
  48. package/docs/TOC_WIRING_GUIDE.md +344 -0
  49. package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
  50. package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
  51. package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
  52. package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
  53. package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
  54. package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
  55. package/docs/analysis/List_Implementation.md +206 -0
  56. package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
  57. package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
  58. package/docs/analysis/RefactorStyles.md +852 -0
  59. package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
  60. package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
  61. package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
  62. package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
  63. package/docs/analysis/github-issues-to-create.md +237 -0
  64. package/docs/api/API_README.md +538 -0
  65. package/docs/api/API_REFERENCE.md +751 -0
  66. package/docs/api/TYPE_DEFINITIONS.md +869 -0
  67. package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
  68. package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
  69. package/docs/docxmlater-readme.md +1341 -0
  70. package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
  71. package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
  72. package/docs/fixes/README.md +37 -0
  73. package/docs/github-issues/issue-1-body.md +125 -0
  74. package/docs/github-issues/issue-10-body.md +850 -0
  75. package/docs/github-issues/issue-2-body.md +200 -0
  76. package/docs/github-issues/issue-3-body.md +270 -0
  77. package/docs/github-issues/issue-4-body.md +169 -0
  78. package/docs/github-issues/issue-5-body.md +173 -0
  79. package/docs/github-issues/issue-6-body.md +158 -0
  80. package/docs/github-issues/issue-7-body.md +171 -0
  81. package/docs/github-issues/issue-8-body.md +407 -0
  82. package/docs/github-issues/issue-9-body.md +515 -0
  83. package/docs/github-issues/issue-tracker.md +274 -0
  84. package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
  85. package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
  86. package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
  87. package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
  88. package/docs/implementation/RefactorStyles.md +588 -0
  89. package/docs/implementation/implement-plan.md +489 -0
  90. package/docs/implementation/missing-helpers-implementation.md +391 -0
  91. package/docs/implementation/refactor-plan.md +520 -0
  92. package/docs/implementation/session-implementation-complete.md +233 -0
  93. package/docs/implementation/session-management-plan.md +250 -0
  94. package/docs/setup-checklist.md +77 -0
  95. package/docs/versions/changelog.md +345 -0
  96. package/electron/customUpdater.ts +656 -0
  97. package/electron/main.ts +2441 -0
  98. package/electron/memoryConfig.ts +187 -0
  99. package/electron/preload.ts +394 -0
  100. package/electron/proxyConfig.ts +340 -0
  101. package/electron/services/BackupService.ts +452 -0
  102. package/electron/services/DictionaryService.ts +402 -0
  103. package/electron/services/LocalDictionaryLookupService.ts +147 -0
  104. package/electron/services/PowerAutomateApiService.ts +231 -0
  105. package/electron/services/SharePointSyncService.ts +474 -0
  106. package/electron/windowsCertStore.ts +427 -0
  107. package/electron/zscalerConfig.ts +381 -0
  108. package/eslint.config.js +92 -0
  109. package/jest.config.js +52 -0
  110. package/package.json +214 -0
  111. package/postcss.config.mjs +6 -0
  112. package/public/icon.png +0 -0
  113. package/publish-release.ps1 +5 -0
  114. package/renovate.json +30 -0
  115. package/src/App.tsx +216 -0
  116. package/src/__mocks__/p-limit.js +12 -0
  117. package/src/__mocks__/styleMock.js +1 -0
  118. package/src/components/common/BugReportButton.tsx +44 -0
  119. package/src/components/common/BugReportDialog.tsx +193 -0
  120. package/src/components/common/Button.tsx +153 -0
  121. package/src/components/common/Card.tsx +86 -0
  122. package/src/components/common/ColorPickerDialog.tsx +177 -0
  123. package/src/components/common/ConfirmDialog.tsx +96 -0
  124. package/src/components/common/DebugConsole.tsx +275 -0
  125. package/src/components/common/EmptyState.tsx +183 -0
  126. package/src/components/common/ErrorBoundary.tsx +98 -0
  127. package/src/components/common/ErrorDetailsDialog.tsx +153 -0
  128. package/src/components/common/ErrorFallback.tsx +218 -0
  129. package/src/components/common/Input.tsx +109 -0
  130. package/src/components/common/Skeleton.tsx +184 -0
  131. package/src/components/common/SplashScreen.tsx +81 -0
  132. package/src/components/common/Toast.tsx +155 -0
  133. package/src/components/common/Tooltip.tsx +79 -0
  134. package/src/components/common/UpdateNotification.tsx +320 -0
  135. package/src/components/comparison/ComparisonWindow.tsx +374 -0
  136. package/src/components/comparison/SideBySideDiff.tsx +486 -0
  137. package/src/components/comparison/index.ts +8 -0
  138. package/src/components/document/DocumentUploader.tsx +288 -0
  139. package/src/components/document/HyperlinkPreview.tsx +430 -0
  140. package/src/components/document/HyperlinkService.md +1484 -0
  141. package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
  142. package/src/components/document/InlineChangesView.tsx +707 -0
  143. package/src/components/document/ProcessingProgress.tsx +303 -0
  144. package/src/components/document/ProcessingResults.tsx +256 -0
  145. package/src/components/document/TrackedChangesDetail.tsx +530 -0
  146. package/src/components/document/TrackedChangesPanel.tsx +546 -0
  147. package/src/components/document/VirtualDocumentList.tsx +240 -0
  148. package/src/components/editor/DocumentEditor.tsx +723 -0
  149. package/src/components/editor/DocumentEditorModal.tsx +640 -0
  150. package/src/components/editor/EditorQuickActions.tsx +502 -0
  151. package/src/components/editor/EditorToolbar.tsx +312 -0
  152. package/src/components/editor/TableEditor.tsx +926 -0
  153. package/src/components/editor/index.ts +18 -0
  154. package/src/components/layout/Header.tsx +190 -0
  155. package/src/components/layout/Sidebar.tsx +313 -0
  156. package/src/components/layout/TitleBar.tsx +190 -0
  157. package/src/components/navigation/CommandPalette.tsx +233 -0
  158. package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
  159. package/src/components/sessions/ChangeItem.tsx +408 -0
  160. package/src/components/sessions/ChangeViewer.tsx +1155 -0
  161. package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
  162. package/src/components/sessions/ProcessingOptions.tsx +297 -0
  163. package/src/components/sessions/ReplacementsTab.tsx +438 -0
  164. package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
  165. package/src/components/sessions/SessionManager.tsx +188 -0
  166. package/src/components/sessions/StylesEditor.tsx +1335 -0
  167. package/src/components/sessions/TabContainer.tsx +151 -0
  168. package/src/components/sessions/VirtualSessionList.tsx +157 -0
  169. package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
  170. package/src/components/settings/CertificateManager.tsx +410 -0
  171. package/src/components/settings/SegmentedControl.tsx +88 -0
  172. package/src/components/settings/SettingRow.tsx +52 -0
  173. package/src/contexts/GlobalStatsContext.tsx +396 -0
  174. package/src/contexts/SessionContext.tsx +2129 -0
  175. package/src/contexts/ThemeContext.tsx +428 -0
  176. package/src/contexts/UserSettingsContext.tsx +290 -0
  177. package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
  178. package/src/global.d.ts +273 -0
  179. package/src/hooks/useDocumentQueue.tsx +210 -0
  180. package/src/hooks/useToast.tsx +55 -0
  181. package/src/main.tsx +10 -0
  182. package/src/pages/Analytics.tsx +386 -0
  183. package/src/pages/CurrentSession.tsx +1174 -0
  184. package/src/pages/Dashboard.tsx +319 -0
  185. package/src/pages/Documents.tsx +317 -0
  186. package/src/pages/Projects.tsx +250 -0
  187. package/src/pages/Reporting.tsx +386 -0
  188. package/src/pages/Search.tsx +349 -0
  189. package/src/pages/Sessions.tsx +285 -0
  190. package/src/pages/Settings.tsx +2662 -0
  191. package/src/services/HyperlinkService.ts +1085 -0
  192. package/src/services/document/DocXMLaterProcessor.ts +617 -0
  193. package/src/services/document/DocumentProcessingComparison.ts +856 -0
  194. package/src/services/document/DocumentSnapshotService.ts +575 -0
  195. package/src/services/document/WordDocumentProcessor.ts +10509 -0
  196. package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
  197. package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
  198. package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
  199. package/src/services/document/blanklines/BlankLineManager.ts +658 -0
  200. package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
  201. package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
  202. package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
  203. package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
  204. package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
  205. package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
  206. package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
  207. package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
  208. package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
  209. package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
  210. package/src/services/document/blanklines/index.ts +67 -0
  211. package/src/services/document/blanklines/rules/additionRules.ts +337 -0
  212. package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
  213. package/src/services/document/blanklines/rules/removalRules.ts +362 -0
  214. package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
  215. package/src/services/document/blanklines/types.ts +29 -0
  216. package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
  217. package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
  218. package/src/services/document/helpers/whitespace.ts +117 -0
  219. package/src/services/document/list/ListNormalizer.ts +947 -0
  220. package/src/services/document/list/index.ts +45 -0
  221. package/src/services/document/list/list-detection.ts +275 -0
  222. package/src/services/document/list/list-types.ts +162 -0
  223. package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
  224. package/src/services/document/processors/ListProcessor.ts +257 -0
  225. package/src/services/document/processors/StructureProcessor.ts +176 -0
  226. package/src/services/document/processors/StyleProcessor.ts +389 -0
  227. package/src/services/document/processors/TableProcessor.ts +2238 -0
  228. package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
  229. package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
  230. package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
  231. package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
  232. package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
  233. package/src/services/document/processors/index.ts +28 -0
  234. package/src/services/document/types/docx-processing.ts +310 -0
  235. package/src/services/editor/EditorActionHandlers.ts +901 -0
  236. package/src/services/editor/index.ts +13 -0
  237. package/src/setupTests.ts +47 -0
  238. package/src/styles/global.css +782 -0
  239. package/src/types/backup.ts +132 -0
  240. package/src/types/dictionary.ts +125 -0
  241. package/src/types/document-processing.ts +331 -0
  242. package/src/types/docxmlater-augments.d.ts +142 -0
  243. package/src/types/editor.ts +280 -0
  244. package/src/types/electron.ts +340 -0
  245. package/src/types/globalStats.ts +155 -0
  246. package/src/types/hyperlink.ts +471 -0
  247. package/src/types/operations.ts +354 -0
  248. package/src/types/session.ts +427 -0
  249. package/src/types/settings.ts +112 -0
  250. package/src/utils/MemoryMonitor.ts +248 -0
  251. package/src/utils/cn.ts +6 -0
  252. package/src/utils/colorConvert.ts +306 -0
  253. package/src/utils/diffUtils.ts +347 -0
  254. package/src/utils/documentUtils.ts +202 -0
  255. package/src/utils/electronGuard.ts +62 -0
  256. package/src/utils/indexedDB.ts +915 -0
  257. package/src/utils/logger.ts +717 -0
  258. package/src/utils/pathSecurity.ts +232 -0
  259. package/src/utils/pathValidator.ts +236 -0
  260. package/src/utils/processingTimeEstimator.ts +153 -0
  261. package/src/utils/safeJsonParse.ts +62 -0
  262. package/src/utils/textSanitizer.ts +162 -0
  263. package/src/utils/urlHelpers.ts +304 -0
  264. package/src/utils/urlPatterns.ts +198 -0
  265. package/src/utils/urlSanitizer.ts +152 -0
  266. package/src/vite-env.d.ts +11 -0
  267. package/tsconfig.electron.json +19 -0
  268. package/tsconfig.json +36 -0
  269. package/tsconfig.node.json +12 -0
  270. package/typedoc.json +45 -0
  271. package/vite.config.ts +152 -0
@@ -0,0 +1,915 @@
1
+ /**
2
+ * IndexedDB wrapper for session persistence
3
+ * Provides a simple interface for storing and retrieving session data
4
+ */
5
+
6
+ import logger from './logger';
7
+ import { safeJsonParse } from './safeJsonParse';
8
+ import type { Session, Document as SessionDocument } from '@/types/session';
9
+
10
+ const DB_NAME = 'DocHubDB';
11
+ const DB_VERSION = 1;
12
+ const SESSIONS_STORE = 'sessions';
13
+
14
+ interface DBConfig {
15
+ dbName: string;
16
+ version: number;
17
+ }
18
+
19
+ // Serialized session type for IndexedDB (dates as ISO strings)
20
+ type SerializedDocument = Omit<SessionDocument, 'processedAt'> & {
21
+ processedAt?: string;
22
+ };
23
+
24
+ type SerializedSession = Omit<Session, 'createdAt' | 'lastModified' | 'closedAt' | 'documents'> & {
25
+ createdAt: string;
26
+ lastModified: string;
27
+ closedAt?: string;
28
+ documents: SerializedDocument[];
29
+ };
30
+
31
+ /**
32
+ * Connection Pool Manager for IndexedDB
33
+ * Maintains a single connection throughout the app lifecycle
34
+ * Provides automatic reconnection on failure
35
+ */
36
+ class IndexedDBConnectionPool {
37
+ private db: IDBDatabase | null = null;
38
+ private isConnecting = false;
39
+ private connectionPromise: Promise<IDBDatabase> | null = null;
40
+ private lastError: Error | null = null;
41
+ private reconnectAttempts = 0;
42
+ private readonly MAX_RECONNECT_ATTEMPTS = 3;
43
+ private readonly RECONNECT_DELAY = 1000; // 1 second
44
+
45
+ /**
46
+ * Get database connection (creates if not exists)
47
+ * Uses singleton pattern to ensure only one connection
48
+ */
49
+ async getConnection(): Promise<IDBDatabase> {
50
+ // If we have a valid connection, return it
51
+ if (this.db && this.db.objectStoreNames.length > 0) {
52
+ // Check if connection is still valid
53
+ try {
54
+ // Simple health check - access object store names
55
+ const _objectStoreNames = this.db.objectStoreNames;
56
+ return this.db;
57
+ } catch (_error) {
58
+ logger.warn('[IndexedDB Pool] Connection invalid, reconnecting...');
59
+ this.db = null;
60
+ }
61
+ }
62
+
63
+ // If already connecting, wait for that connection
64
+ if (this.isConnecting && this.connectionPromise) {
65
+ return this.connectionPromise;
66
+ }
67
+
68
+ // Create new connection
69
+ this.isConnecting = true;
70
+ this.connectionPromise = this.createConnection();
71
+
72
+ try {
73
+ this.db = await this.connectionPromise;
74
+ this.reconnectAttempts = 0; // Reset on successful connection
75
+ return this.db;
76
+ } finally {
77
+ this.isConnecting = false;
78
+ this.connectionPromise = null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Create a new database connection
84
+ */
85
+ private createConnection(): Promise<IDBDatabase> {
86
+ return new Promise((resolve, reject) => {
87
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
88
+
89
+ request.onerror = () => {
90
+ const error = new Error(
91
+ `Failed to open database: ${request.error?.message || 'Unknown error'}`
92
+ );
93
+ this.lastError = error;
94
+ logger.error('[IndexedDB Pool] Connection failed:', error);
95
+ reject(error);
96
+ };
97
+
98
+ request.onsuccess = () => {
99
+ const db = request.result;
100
+ logger.info('[IndexedDB Pool] Connection established');
101
+
102
+ // Set up connection error handlers
103
+ db.onerror = (event) => {
104
+ logger.error('[IndexedDB Pool] Database error:', event);
105
+ this.handleConnectionError();
106
+ };
107
+
108
+ db.onclose = () => {
109
+ logger.info('[IndexedDB Pool] Connection closed');
110
+ this.db = null;
111
+ };
112
+
113
+ resolve(db);
114
+ };
115
+
116
+ request.onupgradeneeded = (event) => {
117
+ const db = (event.target as IDBOpenDBRequest).result;
118
+
119
+ // Create sessions object store if it doesn't exist
120
+ if (!db.objectStoreNames.contains(SESSIONS_STORE)) {
121
+ const sessionsStore = db.createObjectStore(SESSIONS_STORE, {
122
+ keyPath: 'id',
123
+ });
124
+ // Create indexes for faster queries
125
+ sessionsStore.createIndex('status', 'status', { unique: false });
126
+ sessionsStore.createIndex('lastModified', 'lastModified', { unique: false });
127
+ sessionsStore.createIndex('createdAt', 'createdAt', { unique: false });
128
+
129
+ logger.info('[IndexedDB Pool] Database upgraded to version', DB_VERSION);
130
+ }
131
+ };
132
+
133
+ request.onblocked = () => {
134
+ logger.warn('[IndexedDB Pool] Database upgrade blocked by other tabs');
135
+ };
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Handle connection errors with automatic retry
141
+ */
142
+ private async handleConnectionError(): Promise<void> {
143
+ this.db = null;
144
+
145
+ if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
146
+ this.reconnectAttempts++;
147
+ logger.info(
148
+ `[IndexedDB Pool] Attempting reconnection (${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS})...`
149
+ );
150
+
151
+ // Wait before reconnecting
152
+ await new Promise((resolve) =>
153
+ setTimeout(resolve, this.RECONNECT_DELAY * this.reconnectAttempts)
154
+ );
155
+
156
+ try {
157
+ await this.getConnection();
158
+ logger.info('[IndexedDB Pool] Reconnection successful');
159
+ } catch (error) {
160
+ logger.error('[IndexedDB Pool] Reconnection failed:', error);
161
+ }
162
+ } else {
163
+ logger.error('[IndexedDB Pool] Max reconnection attempts reached');
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Close the database connection (for cleanup)
169
+ */
170
+ close(): void {
171
+ if (this.db) {
172
+ logger.info('[IndexedDB Pool] Closing connection');
173
+ this.db.close();
174
+ this.db = null;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Get connection statistics
180
+ */
181
+ getStats(): {
182
+ connected: boolean;
183
+ reconnectAttempts: number;
184
+ lastError: Error | null;
185
+ } {
186
+ return {
187
+ connected: this.db !== null,
188
+ reconnectAttempts: this.reconnectAttempts,
189
+ lastError: this.lastError,
190
+ };
191
+ }
192
+ }
193
+
194
+ // Create singleton instance
195
+ const connectionPool = new IndexedDBConnectionPool();
196
+
197
+ /**
198
+ * GlobalStats Connection Pool Manager for IndexedDB
199
+ * Maintains a single connection for GlobalStats database
200
+ * Separate from main connection pool to avoid cross-database issues
201
+ */
202
+ class GlobalStatsConnectionPool {
203
+ private db: IDBDatabase | null = null;
204
+ private isConnecting = false;
205
+ private connectionPromise: Promise<IDBDatabase> | null = null;
206
+ private lastError: Error | null = null;
207
+ private reconnectAttempts = 0;
208
+ private readonly MAX_RECONNECT_ATTEMPTS = 3;
209
+ private readonly RECONNECT_DELAY = 1000; // 1 second
210
+
211
+ private readonly DB_NAME = 'DocHub_GlobalStats';
212
+ private readonly DB_VERSION = 1;
213
+ private readonly STATS_STORE = 'stats';
214
+
215
+ /**
216
+ * Get database connection (creates if not exists)
217
+ * Uses singleton pattern to ensure only one connection
218
+ */
219
+ async getConnection(): Promise<IDBDatabase> {
220
+ // If we have a valid connection, return it
221
+ if (this.db && this.db.objectStoreNames.length > 0) {
222
+ // Check if connection is still valid
223
+ try {
224
+ // Simple health check - access object store names
225
+ const _objectStoreNames = this.db.objectStoreNames;
226
+ return this.db;
227
+ } catch (_error) {
228
+ logger.warn('[GlobalStats Pool] Connection invalid, reconnecting...');
229
+ this.db = null;
230
+ }
231
+ }
232
+
233
+ // If already connecting, wait for that connection
234
+ if (this.isConnecting && this.connectionPromise) {
235
+ return this.connectionPromise;
236
+ }
237
+
238
+ // Create new connection
239
+ this.isConnecting = true;
240
+ this.connectionPromise = this.createConnection();
241
+
242
+ try {
243
+ this.db = await this.connectionPromise;
244
+ this.reconnectAttempts = 0; // Reset on successful connection
245
+ return this.db;
246
+ } finally {
247
+ this.isConnecting = false;
248
+ this.connectionPromise = null;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Create a new database connection
254
+ */
255
+ private createConnection(): Promise<IDBDatabase> {
256
+ return new Promise((resolve, reject) => {
257
+ const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
258
+
259
+ request.onerror = () => {
260
+ const error = new Error(
261
+ `Failed to open GlobalStats database: ${request.error?.message || 'Unknown error'}`
262
+ );
263
+ this.lastError = error;
264
+ logger.error('[GlobalStats Pool] Connection failed:', error);
265
+ reject(error);
266
+ };
267
+
268
+ request.onsuccess = () => {
269
+ const db = request.result;
270
+ logger.info('[GlobalStats Pool] Connection established');
271
+
272
+ // Set up connection error handlers
273
+ db.onerror = (event) => {
274
+ logger.error('[GlobalStats Pool] Database error:', event);
275
+ this.handleConnectionError();
276
+ };
277
+
278
+ db.onclose = () => {
279
+ logger.info('[GlobalStats Pool] Connection closed');
280
+ this.db = null;
281
+ };
282
+
283
+ resolve(db);
284
+ };
285
+
286
+ request.onupgradeneeded = (event) => {
287
+ const db = (event.target as IDBOpenDBRequest).result;
288
+
289
+ // Create stats object store if it doesn't exist
290
+ if (!db.objectStoreNames.contains(this.STATS_STORE)) {
291
+ db.createObjectStore(this.STATS_STORE);
292
+ logger.info('[GlobalStats Pool] Database upgraded to version', this.DB_VERSION);
293
+ }
294
+ };
295
+
296
+ request.onblocked = () => {
297
+ logger.warn('[GlobalStats Pool] Database upgrade blocked by other tabs');
298
+ };
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Handle connection errors with automatic retry
304
+ */
305
+ private async handleConnectionError(): Promise<void> {
306
+ this.db = null;
307
+
308
+ if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
309
+ this.reconnectAttempts++;
310
+ logger.info(
311
+ `[GlobalStats Pool] Attempting reconnection (${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS})...`
312
+ );
313
+
314
+ // Wait before reconnecting
315
+ await new Promise((resolve) =>
316
+ setTimeout(resolve, this.RECONNECT_DELAY * this.reconnectAttempts)
317
+ );
318
+
319
+ try {
320
+ await this.getConnection();
321
+ logger.info('[GlobalStats Pool] Reconnection successful');
322
+ } catch (error) {
323
+ logger.error('[GlobalStats Pool] Reconnection failed:', error);
324
+ }
325
+ } else {
326
+ logger.error('[GlobalStats Pool] Max reconnection attempts reached');
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Close the database connection (for cleanup)
332
+ */
333
+ close(): void {
334
+ if (this.db) {
335
+ logger.info('[GlobalStats Pool] Closing connection');
336
+ this.db.close();
337
+ this.db = null;
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Get connection statistics
343
+ */
344
+ getStats(): {
345
+ connected: boolean;
346
+ reconnectAttempts: number;
347
+ lastError: Error | null;
348
+ } {
349
+ return {
350
+ connected: this.db !== null,
351
+ reconnectAttempts: this.reconnectAttempts,
352
+ lastError: this.lastError,
353
+ };
354
+ }
355
+ }
356
+
357
+ // Create singleton instance for GlobalStats
358
+ const globalStatsConnectionPool = new GlobalStatsConnectionPool();
359
+
360
+ // Close connections on window unload
361
+ if (typeof window !== 'undefined') {
362
+ window.addEventListener('beforeunload', () => {
363
+ connectionPool.close();
364
+ globalStatsConnectionPool.close();
365
+ });
366
+ }
367
+
368
+ /**
369
+ * Get pooled database connection
370
+ * @deprecated Use connectionPool.getConnection() instead
371
+ */
372
+ async function openDB(): Promise<IDBDatabase> {
373
+ return connectionPool.getConnection();
374
+ }
375
+
376
+ /**
377
+ * Save a session to IndexedDB with quota error recovery
378
+ * Uses connection pool for better performance
379
+ * Handles QuotaExceededError by triggering cleanup
380
+ */
381
+ export async function saveSession(session: SerializedSession): Promise<void> {
382
+ const db = await connectionPool.getConnection();
383
+
384
+ return new Promise((resolve, reject) => {
385
+ const transaction = db.transaction([SESSIONS_STORE], 'readwrite');
386
+ const store = transaction.objectStore(SESSIONS_STORE);
387
+ const request = store.put(session);
388
+
389
+ request.onsuccess = () => {
390
+ resolve();
391
+ };
392
+
393
+ request.onerror = () => {
394
+ const error = request.error;
395
+ // Check if this is a quota exceeded error
396
+ if (error?.name === 'QuotaExceededError') {
397
+ logger.error(`[IndexedDB] Quota exceeded for session: ${session.id}`);
398
+ reject(new Error('DATABASE_QUOTA_EXCEEDED'));
399
+ } else {
400
+ reject(new Error(`Failed to save session: ${session.id}`));
401
+ }
402
+ };
403
+
404
+ transaction.onerror = () => {
405
+ const error = transaction.error;
406
+ if (error?.name === 'QuotaExceededError') {
407
+ logger.error(`[IndexedDB] Transaction quota exceeded for session: ${session.id}`);
408
+ reject(new Error('DATABASE_QUOTA_EXCEEDED'));
409
+ } else {
410
+ reject(new Error(`Transaction failed for session: ${session.id}`));
411
+ }
412
+ };
413
+ });
414
+ }
415
+
416
+ /**
417
+ * Load all sessions from IndexedDB
418
+ * Uses connection pool for better performance
419
+ */
420
+ export async function loadSessions(): Promise<SerializedSession[]> {
421
+ const db = await connectionPool.getConnection();
422
+
423
+ return new Promise((resolve, reject) => {
424
+ const transaction = db.transaction([SESSIONS_STORE], 'readonly');
425
+ const store = transaction.objectStore(SESSIONS_STORE);
426
+ const request = store.getAll();
427
+
428
+ request.onsuccess = () => {
429
+ resolve(request.result || []);
430
+ };
431
+
432
+ request.onerror = () => {
433
+ reject(new Error('Failed to load sessions'));
434
+ };
435
+ });
436
+ }
437
+
438
+ /**
439
+ * Load a single session by ID from IndexedDB
440
+ * Uses connection pool for better performance
441
+ */
442
+ export async function loadSessionById(sessionId: string): Promise<SerializedSession | null> {
443
+ const db = await connectionPool.getConnection();
444
+
445
+ return new Promise((resolve, reject) => {
446
+ const transaction = db.transaction([SESSIONS_STORE], 'readonly');
447
+ const store = transaction.objectStore(SESSIONS_STORE);
448
+ const request = store.get(sessionId);
449
+
450
+ request.onsuccess = () => {
451
+ resolve(request.result || null);
452
+ };
453
+
454
+ request.onerror = () => {
455
+ reject(new Error(`Failed to load session: ${sessionId}`));
456
+ };
457
+ });
458
+ }
459
+
460
+ /**
461
+ * Delete a session from IndexedDB
462
+ * Uses connection pool for better performance
463
+ */
464
+ export async function deleteSession(sessionId: string): Promise<void> {
465
+ const db = await connectionPool.getConnection();
466
+
467
+ return new Promise((resolve, reject) => {
468
+ const transaction = db.transaction([SESSIONS_STORE], 'readwrite');
469
+ const store = transaction.objectStore(SESSIONS_STORE);
470
+ const request = store.delete(sessionId);
471
+
472
+ request.onsuccess = () => {
473
+ resolve();
474
+ };
475
+
476
+ request.onerror = () => {
477
+ reject(new Error(`Failed to delete session: ${sessionId}`));
478
+ };
479
+ });
480
+ }
481
+
482
+ /**
483
+ * Delete all sessions from IndexedDB
484
+ * Uses connection pool for better performance
485
+ */
486
+ export async function clearAllSessions(): Promise<void> {
487
+ const db = await connectionPool.getConnection();
488
+
489
+ return new Promise((resolve, reject) => {
490
+ const transaction = db.transaction([SESSIONS_STORE], 'readwrite');
491
+ const store = transaction.objectStore(SESSIONS_STORE);
492
+ const request = store.clear();
493
+
494
+ request.onsuccess = () => {
495
+ resolve();
496
+ };
497
+
498
+ request.onerror = () => {
499
+ reject(new Error('Failed to clear sessions'));
500
+ };
501
+ });
502
+ }
503
+
504
+ /**
505
+ * Migrate data from localStorage to IndexedDB
506
+ * This is a one-time migration helper
507
+ */
508
+ export async function migrateFromLocalStorage(): Promise<void> {
509
+ try {
510
+ const storedSessions = localStorage.getItem('sessions');
511
+
512
+ if (!storedSessions) {
513
+ logger.debug('[IndexedDB] No sessions found in localStorage to migrate');
514
+ return;
515
+ }
516
+
517
+ const sessions = safeJsonParse<SerializedSession[]>(
518
+ storedSessions,
519
+ [],
520
+ 'localStorage migration'
521
+ );
522
+
523
+ if (!Array.isArray(sessions) || sessions.length === 0) {
524
+ logger.debug('[IndexedDB] No valid sessions to migrate');
525
+ return;
526
+ }
527
+
528
+ logger.info(`[IndexedDB] Migrating ${sessions.length} session(s) from localStorage...`);
529
+
530
+ // Save all sessions to IndexedDB
531
+ for (const session of sessions) {
532
+ await saveSession(session);
533
+ }
534
+
535
+ logger.info('[IndexedDB] Migration completed successfully');
536
+
537
+ // Optionally remove from localStorage after successful migration
538
+ // Uncomment the following line if you want to remove old data
539
+ // localStorage.removeItem('sessions');
540
+ } catch (error) {
541
+ logger.error('[IndexedDB] Migration failed:', error);
542
+ throw error;
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Get active session IDs from IndexedDB
548
+ * Uses connection pool for better performance
549
+ */
550
+ export async function getActiveSessionIds(): Promise<string[]> {
551
+ const db = await connectionPool.getConnection();
552
+
553
+ return new Promise((resolve, reject) => {
554
+ const transaction = db.transaction([SESSIONS_STORE], 'readonly');
555
+ const store = transaction.objectStore(SESSIONS_STORE);
556
+ const index = store.index('status');
557
+ const request = index.getAllKeys('active');
558
+
559
+ request.onsuccess = () => {
560
+ resolve(request.result as string[]);
561
+ };
562
+
563
+ request.onerror = () => {
564
+ reject(new Error('Failed to get active session IDs'));
565
+ };
566
+ });
567
+ }
568
+
569
+ /**
570
+ * Calculate approximate database size in MB
571
+ * Uses JSON string length as proxy for storage size
572
+ */
573
+ export async function calculateDBSize(): Promise<number> {
574
+ try {
575
+ const sessions = await loadSessions();
576
+ const jsonString = JSON.stringify(sessions);
577
+ const sizeInBytes = new Blob([jsonString]).size;
578
+ const sizeInMB = sizeInBytes / (1024 * 1024);
579
+
580
+ logger.debug(
581
+ `[IndexedDB] Database size: ${sizeInMB.toFixed(2)}MB (${sessions.length} sessions)`
582
+ );
583
+ return sizeInMB;
584
+ } catch (error) {
585
+ logger.error('[IndexedDB] Failed to calculate database size:', error);
586
+ return 0;
587
+ }
588
+ }
589
+
590
+ /**
591
+ * Get oldest closed sessions sorted by closedAt date
592
+ * Uses connection pool for better performance
593
+ */
594
+ export async function getOldestClosedSessions(limit: number): Promise<SerializedSession[]> {
595
+ const db = await connectionPool.getConnection();
596
+
597
+ return new Promise((resolve, reject) => {
598
+ const transaction = db.transaction([SESSIONS_STORE], 'readonly');
599
+ const store = transaction.objectStore(SESSIONS_STORE);
600
+ const request = store.getAll();
601
+
602
+ request.onsuccess = () => {
603
+ const sessions = request.result || [];
604
+
605
+ // Filter closed sessions and sort by closedAt (oldest first)
606
+ const closedSessions = sessions
607
+ .filter((s: SerializedSession) => s.status === 'closed' && s.closedAt)
608
+ .sort((a: SerializedSession, b: SerializedSession) => {
609
+ // closedAt is guaranteed to exist from the filter above
610
+ const dateA = new Date(a.closedAt!).getTime();
611
+ const dateB = new Date(b.closedAt!).getTime();
612
+ return dateA - dateB; // Oldest first
613
+ })
614
+ .slice(0, limit);
615
+
616
+ resolve(closedSessions);
617
+ };
618
+
619
+ request.onerror = () => {
620
+ reject(new Error('Failed to get oldest closed sessions'));
621
+ };
622
+ });
623
+ }
624
+
625
+ /**
626
+ * Delete multiple sessions by their IDs
627
+ * Uses connection pool for better performance
628
+ */
629
+ export async function deleteSessions(sessionIds: string[]): Promise<number> {
630
+ const db = await connectionPool.getConnection();
631
+ let deletedCount = 0;
632
+
633
+ return new Promise((resolve, reject) => {
634
+ const transaction = db.transaction([SESSIONS_STORE], 'readwrite');
635
+ const store = transaction.objectStore(SESSIONS_STORE);
636
+
637
+ for (const sessionId of sessionIds) {
638
+ const request = store.delete(sessionId);
639
+ request.onsuccess = () => {
640
+ deletedCount++;
641
+ };
642
+ }
643
+
644
+ transaction.oncomplete = () => {
645
+ logger.info(`[IndexedDB] Deleted ${deletedCount} session(s)`);
646
+ resolve(deletedCount);
647
+ };
648
+
649
+ transaction.onerror = () => {
650
+ reject(new Error('Failed to delete sessions'));
651
+ };
652
+ });
653
+ }
654
+
655
+ /**
656
+ * Clean up database when it exceeds size limit
657
+ * Removes oldest closed sessions until under the limit
658
+ */
659
+ export async function ensureDBSizeLimit(maxSizeMB: number = 200): Promise<void> {
660
+ try {
661
+ const currentSize = await calculateDBSize();
662
+
663
+ if (currentSize <= maxSizeMB) {
664
+ return; // Within limits
665
+ }
666
+
667
+ logger.warn(
668
+ `[IndexedDB] Database size (${currentSize.toFixed(2)}MB) exceeds limit (${maxSizeMB}MB)`
669
+ );
670
+ logger.info('[IndexedDB] Starting cleanup of oldest closed sessions...');
671
+
672
+ // Delete oldest closed sessions in batches until under limit
673
+ let iterationCount = 0;
674
+ const maxIterations = 10; // Safety limit
675
+
676
+ while (iterationCount < maxIterations) {
677
+ const oldestSessions = await getOldestClosedSessions(10); // Delete 10 at a time
678
+
679
+ if (oldestSessions.length === 0) {
680
+ logger.debug('[IndexedDB] No more closed sessions to delete');
681
+ break;
682
+ }
683
+
684
+ const sessionIds = oldestSessions.map((s: SerializedSession) => s.id);
685
+ await deleteSessions(sessionIds);
686
+
687
+ const newSize = await calculateDBSize();
688
+ logger.debug(`[IndexedDB] Size after cleanup: ${newSize.toFixed(2)}MB`);
689
+
690
+ if (newSize <= maxSizeMB) {
691
+ logger.info('[IndexedDB] Database size now under limit');
692
+ break;
693
+ }
694
+
695
+ iterationCount++;
696
+ }
697
+
698
+ if (iterationCount >= maxIterations) {
699
+ logger.warn('[IndexedDB] Max cleanup iterations reached, size may still exceed limit');
700
+ }
701
+ } catch (error) {
702
+ logger.error('[IndexedDB] Failed to ensure database size limit:', error);
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Truncate large change arrays in session documents
708
+ * Prevents excessive storage of tracking data
709
+ */
710
+ export function truncateSessionChanges(
711
+ session: SerializedSession,
712
+ maxChanges: number = 100
713
+ ): SerializedSession {
714
+ if (!session.documents) {
715
+ return session;
716
+ }
717
+
718
+ return {
719
+ ...session,
720
+ documents: session.documents.map((doc: SerializedDocument) => {
721
+ if (doc.processingResult?.changes && doc.processingResult.changes.length > maxChanges) {
722
+ return {
723
+ ...doc,
724
+ processingResult: {
725
+ ...doc.processingResult,
726
+ changes: doc.processingResult.changes.slice(0, maxChanges),
727
+ },
728
+ };
729
+ }
730
+ return doc;
731
+ }),
732
+ };
733
+ }
734
+
735
+ /**
736
+ * Export connection pool for advanced use cases
737
+ * Provides direct access to the pooled connection
738
+ */
739
+ export const getConnectionPool = () => connectionPool;
740
+
741
+ /**
742
+ * Get database performance statistics
743
+ */
744
+ export async function getDBPerformanceStats(): Promise<{
745
+ connected: boolean;
746
+ reconnectAttempts: number;
747
+ lastError: Error | null;
748
+ sessionCount?: number;
749
+ estimatedSizeMB?: number;
750
+ }> {
751
+ const poolStats = connectionPool.getStats();
752
+
753
+ try {
754
+ const sessions = await loadSessions();
755
+ const sizeMB = await calculateDBSize();
756
+
757
+ return {
758
+ ...poolStats,
759
+ sessionCount: sessions.length,
760
+ estimatedSizeMB: sizeMB,
761
+ };
762
+ } catch (error) {
763
+ return {
764
+ ...poolStats,
765
+ sessionCount: 0,
766
+ estimatedSizeMB: 0,
767
+ };
768
+ }
769
+ }
770
+
771
+ /**
772
+ * Handle quota exceeded errors with automatic cleanup and retry
773
+ * Attempts to free up space and retry the operation
774
+ */
775
+ export async function handleQuotaExceededError(
776
+ operation: () => Promise<void>,
777
+ sessionId: string,
778
+ maxRetries: number = 2
779
+ ): Promise<void> {
780
+ let retries = 0;
781
+
782
+ while (retries <= maxRetries) {
783
+ try {
784
+ await operation();
785
+ return; // Success
786
+ } catch (error) {
787
+ if (error instanceof Error && error.message === 'DATABASE_QUOTA_EXCEEDED') {
788
+ if (retries < maxRetries) {
789
+ logger.warn(
790
+ `[IndexedDB] Quota exceeded, attempting cleanup (attempt ${retries + 1}/${maxRetries})`
791
+ );
792
+
793
+ // Aggressive cleanup - delete oldest closed sessions
794
+ const oldestSessions = await getOldestClosedSessions(20);
795
+ if (oldestSessions.length > 0) {
796
+ const sessionIds = oldestSessions.map((s: SerializedSession) => s.id);
797
+ await deleteSessions(sessionIds);
798
+ logger.info(`[IndexedDB] Deleted ${oldestSessions.length} session(s) to free up space`);
799
+ } else {
800
+ // No more closed sessions, truncate active sessions' change history
801
+ const sessions = await loadSessions();
802
+ const activeSessions = sessions.filter((s: SerializedSession) => s.status === 'active');
803
+
804
+ for (const session of activeSessions.slice(0, 5)) {
805
+ const truncated = truncateSessionChanges(session, 50);
806
+ await saveSession(truncated);
807
+ }
808
+ logger.info('[IndexedDB] Truncated change history in active sessions');
809
+ }
810
+
811
+ retries++;
812
+ } else {
813
+ // Max retries exceeded, throw error with guidance
814
+ const sizeMB = await calculateDBSize();
815
+ const error = new Error(
816
+ `DATABASE_QUOTA_EXCEEDED_PERMANENTLY: Database is ${sizeMB.toFixed(2)}MB. ` +
817
+ `Please archive old sessions or export data to free up space.`
818
+ );
819
+ logger.error('[IndexedDB] Permanent quota exceeded:', error);
820
+ throw error;
821
+ }
822
+ } else {
823
+ // Not a quota error, re-throw
824
+ throw error;
825
+ }
826
+ }
827
+ }
828
+ }
829
+
830
+ /**
831
+ * ========================================
832
+ * GlobalStats Database Helper Functions
833
+ * ========================================
834
+ * These functions provide a simple interface for GlobalStats persistence
835
+ * Uses the globalStatsConnectionPool for consistent connection management
836
+ */
837
+
838
+ // Import GlobalStats types
839
+ import type { GlobalStats } from '@/types/globalStats';
840
+
841
+ const STATS_STORE = 'stats';
842
+ const STATS_KEY = 'global';
843
+
844
+ /**
845
+ * Load global statistics from IndexedDB
846
+ * Uses connection pool for better performance and reliability
847
+ */
848
+ export async function loadGlobalStats(): Promise<GlobalStats | null> {
849
+ try {
850
+ const db = await globalStatsConnectionPool.getConnection();
851
+
852
+ return new Promise((resolve, reject) => {
853
+ const transaction = db.transaction([STATS_STORE], 'readonly');
854
+ const store = transaction.objectStore(STATS_STORE);
855
+ const request = store.get(STATS_KEY);
856
+
857
+ request.onsuccess = () => {
858
+ resolve(request.result || null);
859
+ };
860
+
861
+ request.onerror = () => {
862
+ reject(new Error('Failed to load global stats'));
863
+ };
864
+ });
865
+ } catch (error) {
866
+ logger.error('[GlobalStats] Failed to load stats:', error);
867
+ throw error;
868
+ }
869
+ }
870
+
871
+ /**
872
+ * Save global statistics to IndexedDB
873
+ * Uses connection pool for better performance and reliability
874
+ */
875
+ export async function saveGlobalStats(stats: GlobalStats): Promise<void> {
876
+ try {
877
+ const db = await globalStatsConnectionPool.getConnection();
878
+
879
+ return new Promise((resolve, reject) => {
880
+ const transaction = db.transaction([STATS_STORE], 'readwrite');
881
+ const store = transaction.objectStore(STATS_STORE);
882
+ const request = store.put(stats, STATS_KEY);
883
+
884
+ request.onsuccess = () => {
885
+ resolve();
886
+ };
887
+
888
+ request.onerror = () => {
889
+ reject(new Error('Failed to save global stats'));
890
+ };
891
+ });
892
+ } catch (error) {
893
+ logger.error('[GlobalStats] Failed to save stats:', error);
894
+ throw error;
895
+ }
896
+ }
897
+
898
+ /**
899
+ * Reset global statistics to default values
900
+ * Uses connection pool for better performance and reliability
901
+ */
902
+ export async function resetGlobalStats(freshStats: GlobalStats): Promise<void> {
903
+ try {
904
+ await saveGlobalStats(freshStats);
905
+ logger.info('[GlobalStats] Stats reset to default values');
906
+ } catch (error) {
907
+ logger.error('[GlobalStats] Failed to reset stats:', error);
908
+ throw error;
909
+ }
910
+ }
911
+
912
+ /**
913
+ * Export the global stats connection pool for advanced use cases
914
+ */
915
+ export const getGlobalStatsConnectionPool = () => globalStatsConnectionPool;