thatcher 1.0.4

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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +398 -0
  3. package/package.json +73 -0
  4. package/src/adapters/google-auth.js +148 -0
  5. package/src/adapters/google-drive.js +209 -0
  6. package/src/app/api/[entity]/[[...path]]/route.js +33 -0
  7. package/src/app/api/audit/dashboard/route.js +77 -0
  8. package/src/app/api/audit/logs/route.js +46 -0
  9. package/src/app/api/audit/permissions/[id]/route.js +37 -0
  10. package/src/app/api/audit/permissions/route.js +93 -0
  11. package/src/app/api/audit/permissions/stats/route.js +29 -0
  12. package/src/app/api/audit/route.js +79 -0
  13. package/src/app/api/audit/stats/route.js +18 -0
  14. package/src/app/api/auth/google/callback/route.js +94 -0
  15. package/src/app/api/auth/google/route.js +58 -0
  16. package/src/app/api/auth/login/route.js +121 -0
  17. package/src/app/api/auth/logout/route.js +52 -0
  18. package/src/app/api/auth/me/route.js +16 -0
  19. package/src/app/api/auth/mwr-bridge/route.js +77 -0
  20. package/src/app/api/auth/password-reset/route.js +65 -0
  21. package/src/app/api/cron/trigger/route.js +58 -0
  22. package/src/app/api/csrf-token/route.js +8 -0
  23. package/src/app/api/debug/config/route.js +22 -0
  24. package/src/app/api/debug/hooks/route.js +16 -0
  25. package/src/app/api/debug/plugins/route.js +20 -0
  26. package/src/app/api/debug/sqlite/route.js +21 -0
  27. package/src/app/api/debug/sync/route.js +29 -0
  28. package/src/app/api/debug/workflow/route.js +20 -0
  29. package/src/app/api/domains/[domain]/route.js +26 -0
  30. package/src/app/api/domains/route.js +21 -0
  31. package/src/app/api/email/allocate/batch/route.js +106 -0
  32. package/src/app/api/email/allocate/route.js +141 -0
  33. package/src/app/api/email/receive/route.js +158 -0
  34. package/src/app/api/email/route.js +3 -0
  35. package/src/app/api/email/send/route.js +77 -0
  36. package/src/app/api/email/unallocated/route.js +47 -0
  37. package/src/app/api/files/[id]/route.js +38 -0
  38. package/src/app/api/health/route.js +95 -0
  39. package/src/app/api/metrics/route.js +73 -0
  40. package/src/app/api/monitoring/dashboard/route.js +18 -0
  41. package/src/cli.js +243 -0
  42. package/src/config/config-loader.js +112 -0
  43. package/src/config/constants.js +127 -0
  44. package/src/config/env.js +164 -0
  45. package/src/config/spec-helpers.js +232 -0
  46. package/src/engine.server.js +212 -0
  47. package/src/index.js +368 -0
  48. package/src/lib/accessibility.js +162 -0
  49. package/src/lib/action-factory.js +34 -0
  50. package/src/lib/action-utils.js +21 -0
  51. package/src/lib/alert-manager.js +189 -0
  52. package/src/lib/api-error-wrapper.js +125 -0
  53. package/src/lib/api-helpers.js +53 -0
  54. package/src/lib/api.js +82 -0
  55. package/src/lib/audit-logger-enhanced.js +117 -0
  56. package/src/lib/audit-logger.js +193 -0
  57. package/src/lib/auth-middleware.js +102 -0
  58. package/src/lib/auth-route-helpers.js +83 -0
  59. package/src/lib/business-rules-engine.js +86 -0
  60. package/src/lib/compression.js +44 -0
  61. package/src/lib/config-field-helpers.js +91 -0
  62. package/src/lib/config-generator-engine.js +445 -0
  63. package/src/lib/config-helpers.js +120 -0
  64. package/src/lib/connection-guard.js +79 -0
  65. package/src/lib/crud-action-helpers.js +34 -0
  66. package/src/lib/crud-factory.js +83 -0
  67. package/src/lib/crud-handlers.js +244 -0
  68. package/src/lib/csrf-protection.js +63 -0
  69. package/src/lib/database-core.js +258 -0
  70. package/src/lib/database-migrations.js +96 -0
  71. package/src/lib/date-utils.js +159 -0
  72. package/src/lib/db-backup.js +97 -0
  73. package/src/lib/db-monitor.js +127 -0
  74. package/src/lib/domain-loader.js +82 -0
  75. package/src/lib/email-sender.js +100 -0
  76. package/src/lib/error-boundary.js +134 -0
  77. package/src/lib/error-handler.js +84 -0
  78. package/src/lib/error-recovery.js +190 -0
  79. package/src/lib/error-resilience.js +130 -0
  80. package/src/lib/errors.js +69 -0
  81. package/src/lib/events-engine.js +182 -0
  82. package/src/lib/field-iterator.js +50 -0
  83. package/src/lib/field-registry.js +68 -0
  84. package/src/lib/field-types.js +154 -0
  85. package/src/lib/generic-crud-handler.js +32 -0
  86. package/src/lib/health-monitor.js +134 -0
  87. package/src/lib/hook-engine.js +169 -0
  88. package/src/lib/hot-reload/cache-invalidator.js +115 -0
  89. package/src/lib/hot-reload/checkpoint.js +95 -0
  90. package/src/lib/hot-reload/debug-exposure.js +67 -0
  91. package/src/lib/hot-reload/directory-watcher.js +96 -0
  92. package/src/lib/hot-reload/index.js +50 -0
  93. package/src/lib/hot-reload/mutex.js +75 -0
  94. package/src/lib/hot-reload/promise-container.js +66 -0
  95. package/src/lib/hot-reload/route-wrapper.js +46 -0
  96. package/src/lib/hot-reload/safe-error.js +51 -0
  97. package/src/lib/hot-reload/supervisor.js +161 -0
  98. package/src/lib/hot-reload/timeout-wrapper.js +52 -0
  99. package/src/lib/http-methods-factory.js +25 -0
  100. package/src/lib/index-optimizer.js +96 -0
  101. package/src/lib/index.js +35 -0
  102. package/src/lib/list-data-transform.js +39 -0
  103. package/src/lib/log-aggregator.js +116 -0
  104. package/src/lib/logger.js +55 -0
  105. package/src/lib/metrics-collector.js +102 -0
  106. package/src/lib/minifier.js +19 -0
  107. package/src/lib/monitoring-init.js +67 -0
  108. package/src/lib/next-compat.js +80 -0
  109. package/src/lib/next-polyfills.js +135 -0
  110. package/src/lib/perf-monitor.js +91 -0
  111. package/src/lib/progress-components.js +181 -0
  112. package/src/lib/query-cache.js +126 -0
  113. package/src/lib/query-engine-write.js +221 -0
  114. package/src/lib/query-engine.js +399 -0
  115. package/src/lib/query-perf.js +117 -0
  116. package/src/lib/query-string-adapter.js +75 -0
  117. package/src/lib/realtime-server.js +67 -0
  118. package/src/lib/render-cache.js +61 -0
  119. package/src/lib/request-tracker.js +43 -0
  120. package/src/lib/resource-hints.js +29 -0
  121. package/src/lib/resource-monitor.js +117 -0
  122. package/src/lib/response-formatter.js +80 -0
  123. package/src/lib/route-helpers.js +33 -0
  124. package/src/lib/route-resolver.js +142 -0
  125. package/src/lib/safe-json.js +8 -0
  126. package/src/lib/server-bootstrap.js +71 -0
  127. package/src/lib/stage-pipeline.js +153 -0
  128. package/src/lib/state-protocol.js +171 -0
  129. package/src/lib/state-transport-client.js +169 -0
  130. package/src/lib/state-transport-reconnect.js +121 -0
  131. package/src/lib/state-transport-server.js +181 -0
  132. package/src/lib/static-server.js +97 -0
  133. package/src/lib/status-helpers.js +98 -0
  134. package/src/lib/universal-handler.js +7 -0
  135. package/src/lib/utils.js +93 -0
  136. package/src/lib/validate.js +197 -0
  137. package/src/lib/validation/business-validators.js +61 -0
  138. package/src/lib/validation/csrf.js +51 -0
  139. package/src/lib/validation/file-validators.js +34 -0
  140. package/src/lib/validation/format-validators.js +106 -0
  141. package/src/lib/validation/index.js +19 -0
  142. package/src/lib/validation/rate-limit.js +31 -0
  143. package/src/lib/validation/security-validators.js +78 -0
  144. package/src/lib/validation-middleware.js +133 -0
  145. package/src/lib/validators.js +105 -0
  146. package/src/lib/with-audit-logging.js +63 -0
  147. package/src/lib/with-error-handler.js +31 -0
  148. package/src/lib/workflow-engine.js +250 -0
  149. package/src/server/server.js +305 -0
  150. package/src/services/collaborator-role.service.js +205 -0
  151. package/src/services/email-sender.js +105 -0
  152. package/src/services/notification-engine.js +110 -0
  153. package/src/services/permission.service.js +181 -0
  154. package/src/ui/advanced-search-renderer.js +42 -0
  155. package/src/ui/advanced-widgets.js +47 -0
  156. package/src/ui/auth-pages.js +114 -0
  157. package/src/ui/auth-styles.js +53 -0
  158. package/src/ui/client.js +99 -0
  159. package/src/ui/collaboration-dialogs.js +31 -0
  160. package/src/ui/common-handlers.js +156 -0
  161. package/src/ui/component-engine.js +103 -0
  162. package/src/ui/dashboard-renderer.js +150 -0
  163. package/src/ui/dialog-engine.js +147 -0
  164. package/src/ui/dialog-factory.js +38 -0
  165. package/src/ui/engagement-cards.js +76 -0
  166. package/src/ui/engagement-dialogs.js +100 -0
  167. package/src/ui/engagement-grid-renderer.js +109 -0
  168. package/src/ui/entity-renderer.js +176 -0
  169. package/src/ui/event-delegation.js +108 -0
  170. package/src/ui/fetch-json.js +24 -0
  171. package/src/ui/file-dialogs.js +81 -0
  172. package/src/ui/flexup-report-renderer.js +173 -0
  173. package/src/ui/format-helpers.js +102 -0
  174. package/src/ui/global-tags.js +144 -0
  175. package/src/ui/highlight-threading-renderer.js +176 -0
  176. package/src/ui/idle-logout.js +156 -0
  177. package/src/ui/job-management-renderer.js +61 -0
  178. package/src/ui/layout.js +219 -0
  179. package/src/ui/letter-dialogs.js +37 -0
  180. package/src/ui/ml-console-renderer.js +108 -0
  181. package/src/ui/monitoring-dashboard-client.js +136 -0
  182. package/src/ui/monitoring-dashboard.js +134 -0
  183. package/src/ui/notifications-renderer.js +51 -0
  184. package/src/ui/page-handler-admin.js +121 -0
  185. package/src/ui/page-handler-helpers.js +111 -0
  186. package/src/ui/page-handler-reviews.js +165 -0
  187. package/src/ui/page-handler-rfi.js +42 -0
  188. package/src/ui/page-handler.js +210 -0
  189. package/src/ui/password-reset-page.js +146 -0
  190. package/src/ui/perf-helpers.js +96 -0
  191. package/src/ui/perf-renderer.js +71 -0
  192. package/src/ui/permissions-ui.js +163 -0
  193. package/src/ui/picker-dialogs.js +100 -0
  194. package/src/ui/render-helpers.js +124 -0
  195. package/src/ui/renderer.js +35 -0
  196. package/src/ui/review-comparison-renderer.js +58 -0
  197. package/src/ui/review-detail-panels.js +71 -0
  198. package/src/ui/review-detail-renderer.js +170 -0
  199. package/src/ui/review-detail-script.js +95 -0
  200. package/src/ui/review-mwr-renderer.js +113 -0
  201. package/src/ui/review-renderer.js +202 -0
  202. package/src/ui/review-widgets.js +88 -0
  203. package/src/ui/review-zone-nav.js +12 -0
  204. package/src/ui/rfi-detail-renderer.js +191 -0
  205. package/src/ui/rfi-renderer.js +194 -0
  206. package/src/ui/rfi-report-renderer.js +56 -0
  207. package/src/ui/rippleui.css +1 -0
  208. package/src/ui/settings-renderer-advanced.js +195 -0
  209. package/src/ui/settings-renderer-advanced2.js +158 -0
  210. package/src/ui/settings-renderer-teams.js +112 -0
  211. package/src/ui/settings-renderer.js +166 -0
  212. package/src/ui/spacing-system.js +155 -0
  213. package/src/ui/standalone-login.js +109 -0
  214. package/src/ui/styles.css +2530 -0
  215. package/src/ui/styles2.css +1602 -0
  216. package/src/ui/test-page.js +23 -0
  217. package/src/ui/validation-rules.js +73 -0
  218. package/src/ui/validation-ui.js +147 -0
  219. package/src/ui/virtual-scroll.js +107 -0
  220. package/src/ui/webjsx.js +61 -0
  221. package/src/ui/widgets.js +152 -0
@@ -0,0 +1,134 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
2
+
3
+ const errorBoundaryState = new Map();
4
+
5
+ export class ErrorBoundary {
6
+ constructor(id, options = {}) {
7
+ this.id = id;
8
+ this.fallback = options.fallback || this.defaultFallback.bind(this);
9
+ this.onError = options.onError;
10
+ this.reset = this.reset.bind(this);
11
+
12
+ errorBoundaryState.set(id, {
13
+ hasError: false,
14
+ error: null,
15
+ errorInfo: null,
16
+ retryCount: 0
17
+ });
18
+ }
19
+
20
+ getState() {
21
+ return errorBoundaryState.get(this.id);
22
+ }
23
+
24
+ setState(updates) {
25
+ const current = this.getState();
26
+ errorBoundaryState.set(this.id, { ...current, ...updates });
27
+ }
28
+
29
+ catchError(error, errorInfo = {}) {
30
+ const state = this.getState();
31
+
32
+ console.error(`[ErrorBoundary:${this.id}] Caught error:`, error);
33
+
34
+ this.setState({
35
+ hasError: true,
36
+ error,
37
+ errorInfo,
38
+ retryCount: state.retryCount + 1
39
+ });
40
+
41
+ if (this.onError) {
42
+ this.onError(error, errorInfo);
43
+ }
44
+ }
45
+
46
+ reset() {
47
+ this.setState({
48
+ hasError: false,
49
+ error: null,
50
+ errorInfo: null
51
+ });
52
+ }
53
+
54
+ defaultFallback(error, retry) {
55
+ const state = this.getState();
56
+
57
+ return _jsxs('div', {
58
+ className: 'p-6 bg-error-content border border-error rounded-lg',
59
+ children: [
60
+ _jsx('h2', {
61
+ className: 'text-xl font-bold mb-2 text-error',
62
+ children: 'Something went wrong'
63
+ }),
64
+ _jsx('p', {
65
+ className: 'text-sm mb-4',
66
+ children: String(error?.message || 'An unexpected error occurred')
67
+ }),
68
+ state.retryCount < 3 && _jsx('button', {
69
+ onClick: retry,
70
+ className: 'btn btn-primary btn-sm',
71
+ children: 'Try again'
72
+ }),
73
+ state.retryCount >= 3 && _jsx('p', {
74
+ className: 'text-sm text-error',
75
+ children: 'Please refresh the page or contact support'
76
+ })
77
+ ]
78
+ });
79
+ }
80
+
81
+ render(children) {
82
+ const state = this.getState();
83
+
84
+ if (state.hasError) {
85
+ return this.fallback(state.error, this.reset);
86
+ }
87
+
88
+ return children;
89
+ }
90
+ }
91
+
92
+ export function createErrorBoundary(id, options = {}) {
93
+ return new ErrorBoundary(id, options);
94
+ }
95
+
96
+ export function wrapWithErrorBoundary(component, id, options = {}) {
97
+ const boundary = createErrorBoundary(id, options);
98
+
99
+ return (...args) => {
100
+ try {
101
+ const result = component(...args);
102
+ return boundary.render(result);
103
+ } catch (error) {
104
+ boundary.catchError(error);
105
+ return boundary.render(null);
106
+ }
107
+ };
108
+ }
109
+
110
+ export async function tryCatch(fn, fallback = null) {
111
+ try {
112
+ return await fn();
113
+ } catch (error) {
114
+ console.error('[tryCatch] Error caught:', error);
115
+ return fallback;
116
+ }
117
+ }
118
+
119
+ export function safeRender(component, props = {}, fallback = null) {
120
+ try {
121
+ return component(props);
122
+ } catch (error) {
123
+ console.error('[safeRender] Render error:', error);
124
+ return fallback || _jsx('div', {
125
+ className: 'p-4 text-error',
126
+ children: 'Failed to render component'
127
+ });
128
+ }
129
+ }
130
+
131
+ if (typeof global !== 'undefined') {
132
+ global.errorBoundaryState = errorBoundaryState;
133
+ global.createErrorBoundary = createErrorBoundary;
134
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Error Handling - Centralized error types and utilities
3
+ */
4
+
5
+ export class AppError extends Error {
6
+ constructor(message, code = 'APP_ERROR', status = 500, details = null) {
7
+ super(message);
8
+ this.name = 'AppError';
9
+ this.code = code;
10
+ this.status = status;
11
+ this.details = details;
12
+ if (Error.captureStackTrace) Error.captureStackTrace(this, AppError);
13
+ }
14
+ }
15
+
16
+ export class UnauthorizedError extends AppError {
17
+ constructor(message = 'Authentication required') {
18
+ super(message, 'UNAUTHORIZED', 401);
19
+ this.name = 'UnauthorizedError';
20
+ }
21
+ }
22
+
23
+ export class PermissionError extends AppError {
24
+ constructor(message = 'Permission denied') {
25
+ super(message, 'PERMISSION_DENIED', 403);
26
+ this.name = 'PermissionError';
27
+ }
28
+ }
29
+
30
+ export class NotFoundError extends AppError {
31
+ constructor(entity = 'Resource', id = null) {
32
+ const msg = id ? `${entity} with id ${id} not found` : `${entity} not found`;
33
+ super(msg, 'NOT_FOUND', 404);
34
+ this.name = 'NotFoundError';
35
+ this.entity = entity;
36
+ this.id = id;
37
+ }
38
+ }
39
+
40
+ export class ValidationError extends AppError {
41
+ constructor(message = 'Validation failed', errors = {}) {
42
+ super(message, 'VALIDATION_ERROR', 422);
43
+ this.name = 'ValidationError';
44
+ this.errors = errors;
45
+ }
46
+ }
47
+
48
+ export class DatabaseError extends AppError {
49
+ constructor(operation = 'Database operation', originalError = null) {
50
+ super(`Database ${operation} failed: ${originalError?.message || 'unknown error'}`, 'DATABASE_ERROR', 500);
51
+ this.name = 'DatabaseError';
52
+ this.originalError = originalError;
53
+ }
54
+ }
55
+
56
+ export class ConflictError extends AppError {
57
+ constructor(message = 'Resource already exists') {
58
+ super(message, 'CONFLICT', 409);
59
+ this.name = 'ConflictError';
60
+ }
61
+ }
62
+
63
+ export class BadRequestError extends AppError {
64
+ constructor(message = 'Invalid request') {
65
+ super(message, 'BAD_REQUEST', 400);
66
+ this.name = 'BadRequestError';
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Create an error logger
72
+ * @param {string} context
73
+ * @returns {object}
74
+ */
75
+ export function createErrorLogger(context = '') {
76
+ return {
77
+ error: (msg, meta = {}) => {
78
+ console.error(`[${context}] ${msg}`, meta);
79
+ },
80
+ warn: (msg, meta = {}) => {
81
+ console.warn(`[${context}] ${msg}`, meta);
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,190 @@
1
+ import { retryWithBackoff, withCircuitBreaker, checkpoint, restoreCheckpoint, logRecovery, normalizeError } from '@/lib/error-handler';
2
+ import { AppError } from '@/lib/errors';
3
+ import { HTTP } from '@/config/constants';
4
+
5
+ const recoveryState = { supervisors: new Map(), lastHealthCheck: null };
6
+
7
+ export function createSupervisor(name, fn, options = {}) {
8
+ const { maxRestarts = 5, restartWindow = 60000, onRestart = null } = options;
9
+
10
+ const supervisor = {
11
+ name,
12
+ fn,
13
+ restarts: [],
14
+ state: 'running',
15
+ lastError: null,
16
+ maxRestarts,
17
+ restartWindow,
18
+ onRestart
19
+ };
20
+
21
+ recoveryState.supervisors.set(name, supervisor);
22
+ return supervisor;
23
+ }
24
+
25
+ export async function supervise(name, fn, options = {}) {
26
+ const supervisor = recoveryState.supervisors.get(name) || createSupervisor(name, fn, options);
27
+
28
+ const now = Date.now();
29
+ supervisor.restarts = supervisor.restarts.filter(t => now - t < supervisor.restartWindow);
30
+
31
+ if (supervisor.restarts.length >= supervisor.maxRestarts) {
32
+ const error = new AppError(
33
+ `Supervisor ${name} exceeded restart limit`,
34
+ 'SUPERVISOR_LIMIT',
35
+ HTTP.INTERNAL_ERROR,
36
+ { restarts: supervisor.restarts.length }
37
+ );
38
+ supervisor.state = 'failed';
39
+ supervisor.lastError = error;
40
+ console.error(`[SUPERVISOR] ${name} failed permanently after ${supervisor.restarts.length} restarts`);
41
+ throw error;
42
+ }
43
+
44
+ try {
45
+ supervisor.state = 'running';
46
+ const result = await fn();
47
+ supervisor.restarts = [];
48
+ return result;
49
+ } catch (error) {
50
+ supervisor.restarts.push(now);
51
+ supervisor.lastError = normalizeError(error);
52
+
53
+ if (supervisor.onRestart) {
54
+ await supervisor.onRestart(error, supervisor.restarts.length);
55
+ }
56
+
57
+ logRecovery({ supervisor: name, restarts: supervisor.restarts.length }, 'supervisor_restart');
58
+
59
+ if (supervisor.restarts.length < supervisor.maxRestarts) {
60
+ const delay = Math.min(1000 * Math.pow(2, supervisor.restarts.length - 1), 30000);
61
+ console.warn(`[SUPERVISOR] ${name} restarting in ${delay}ms (attempt ${supervisor.restarts.length})`);
62
+ await new Promise(resolve => setTimeout(resolve, delay));
63
+ return supervise(name, fn, options);
64
+ }
65
+
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ export async function degradedMode(fn, fallback, options = {}) {
71
+ const { timeout = 5000 } = options;
72
+
73
+ try {
74
+ const result = await Promise.race([
75
+ fn(),
76
+ new Promise((_, reject) =>
77
+ setTimeout(() => reject(new Error('Operation timeout')), timeout)
78
+ )
79
+ ]);
80
+ return { mode: 'normal', result };
81
+ } catch (error) {
82
+ console.warn('[DEGRADED] Falling back to degraded mode', { error: error.message });
83
+ const result = await fallback();
84
+ return { mode: 'degraded', result };
85
+ }
86
+ }
87
+
88
+ export async function healthCheck(checks) {
89
+ const results = {};
90
+ const timestamp = new Date().toISOString();
91
+
92
+ for (const [name, checkFn] of Object.entries(checks)) {
93
+ try {
94
+ const start = Date.now();
95
+ await checkFn();
96
+ results[name] = { status: 'healthy', latency: Date.now() - start };
97
+ } catch (error) {
98
+ results[name] = {
99
+ status: 'unhealthy',
100
+ error: String(error.message || error),
101
+ timestamp
102
+ };
103
+ }
104
+ }
105
+
106
+ const overall = Object.values(results).every(r => r.status === 'healthy') ? 'healthy' : 'degraded';
107
+
108
+ recoveryState.lastHealthCheck = { timestamp, overall, checks: results };
109
+
110
+ return { status: overall, timestamp, checks: results };
111
+ }
112
+
113
+ export function isolateFailure(fn, defaultValue = null) {
114
+ return async (...args) => {
115
+ try {
116
+ return await fn(...args);
117
+ } catch (error) {
118
+ console.error('[ISOLATE] Failure contained', {
119
+ function: fn.name,
120
+ error: String(error.message || error)
121
+ });
122
+ return defaultValue;
123
+ }
124
+ };
125
+ }
126
+
127
+ export async function withRecovery(fn, options = {}) {
128
+ const {
129
+ retry = true,
130
+ circuitBreaker = null,
131
+ supervisor = null,
132
+ checkpointName = null,
133
+ fallback = null
134
+ } = options;
135
+
136
+ let operation = fn;
137
+
138
+ if (checkpointName) {
139
+ const savedState = restoreCheckpoint(checkpointName);
140
+ if (savedState) {
141
+ }
142
+ }
143
+
144
+ if (supervisor) {
145
+ operation = () => supervise(supervisor, operation, options);
146
+ }
147
+
148
+ if (circuitBreaker) {
149
+ operation = () => withCircuitBreaker(circuitBreaker, operation, options);
150
+ }
151
+
152
+ if (retry) {
153
+ operation = () => retryWithBackoff(operation, options);
154
+ }
155
+
156
+ try {
157
+ const result = await operation();
158
+
159
+ if (checkpointName) {
160
+ checkpoint(checkpointName, result);
161
+ }
162
+
163
+ return result;
164
+ } catch (error) {
165
+ if (fallback) {
166
+ console.warn('[RECOVERY] Using fallback after error', { error: error.message });
167
+ return fallback();
168
+ }
169
+ throw error;
170
+ }
171
+ }
172
+
173
+ export function getSupervisorStats() {
174
+ return {
175
+ supervisors: Array.from(recoveryState.supervisors.entries()).map(([name, s]) => ({
176
+ name,
177
+ state: s.state,
178
+ restarts: s.restarts.length,
179
+ lastError: s.lastError ? String(s.lastError.message) : null
180
+ })),
181
+ lastHealthCheck: recoveryState.lastHealthCheck
182
+ };
183
+ }
184
+
185
+ if (typeof global !== 'undefined') {
186
+ global.recoveryState = recoveryState;
187
+ global.getSupervisorStats = getSupervisorStats;
188
+ global.supervise = supervise;
189
+ global.healthCheck = healthCheck;
190
+ }
@@ -0,0 +1,130 @@
1
+ import { HTTP } from '@/config/constants';
2
+ import { AppError } from '@/lib/errors';
3
+ import { normalizeError } from '@/lib/error-handler';
4
+
5
+ const errorState = { errors: [], circuitBreakers: new Map(), checkpoints: new Map() };
6
+
7
+ export async function retryWithBackoff(fn, options = {}) {
8
+ const { maxAttempts = 3, delay = 1000, backoff = 2, context = {} } = options;
9
+
10
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
11
+ try {
12
+ return await fn();
13
+ } catch (error) {
14
+ if (attempt === maxAttempts) {
15
+ const normalized = normalizeError(error);
16
+ console.error(`[RETRY] Final attempt failed:`, { ...context, attempt, error: normalized.toJSON() });
17
+ throw normalized;
18
+ }
19
+
20
+ const waitTime = delay * Math.pow(backoff, attempt - 1);
21
+ console.warn(`[RETRY] Attempt ${attempt} failed, retrying in ${waitTime}ms`, context);
22
+ await new Promise(resolve => setTimeout(resolve, waitTime));
23
+ }
24
+ }
25
+ }
26
+
27
+ export function createCircuitBreaker(name, options = {}) {
28
+ const { threshold = 5, resetTimeout = 30000 } = options;
29
+
30
+ if (!errorState.circuitBreakers.has(name)) {
31
+ errorState.circuitBreakers.set(name, {
32
+ failures: 0,
33
+ state: 'closed',
34
+ lastFailure: null,
35
+ nextAttempt: null,
36
+ threshold,
37
+ resetTimeout
38
+ });
39
+ }
40
+
41
+ return errorState.circuitBreakers.get(name);
42
+ }
43
+
44
+ export async function withCircuitBreaker(name, fn, options = {}) {
45
+ const breaker = createCircuitBreaker(name, options);
46
+
47
+ if (breaker.state === 'open') {
48
+ const now = Date.now();
49
+ if (breaker.nextAttempt && now < breaker.nextAttempt) {
50
+ throw new AppError(`Service unavailable: ${name}`, 'CIRCUIT_OPEN', HTTP.SERVICE_UNAVAILABLE, { nextAttempt: breaker.nextAttempt });
51
+ }
52
+ breaker.state = 'half-open';
53
+ }
54
+
55
+ try {
56
+ const result = await fn();
57
+ if (breaker.state === 'half-open') {
58
+ }
59
+ breaker.failures = 0;
60
+ breaker.state = 'closed';
61
+ return result;
62
+ } catch (error) {
63
+ breaker.failures++;
64
+ breaker.lastFailure = Date.now();
65
+
66
+ if (breaker.failures >= breaker.threshold) {
67
+ breaker.state = 'open';
68
+ breaker.nextAttempt = Date.now() + breaker.resetTimeout;
69
+ console.error(`[CIRCUIT] ${name} opened after ${breaker.failures} failures`);
70
+ }
71
+
72
+ throw error;
73
+ }
74
+ }
75
+
76
+ export function checkpoint(name, state) {
77
+ errorState.checkpoints.set(name, {
78
+ state: JSON.parse(JSON.stringify(state)),
79
+ timestamp: Date.now()
80
+ });
81
+ }
82
+
83
+ export function restoreCheckpoint(name) {
84
+ const cp = errorState.checkpoints.get(name);
85
+ if (cp) {
86
+ return cp.state;
87
+ }
88
+ return null;
89
+ }
90
+
91
+ export function logRecovery(context, action) {
92
+ errorState.errors.push({
93
+ type: 'recovery',
94
+ context,
95
+ action,
96
+ timestamp: new Date().toISOString()
97
+ });
98
+ if (errorState.errors.length > 1000) errorState.errors.shift();
99
+ }
100
+
101
+ export function getErrorStats() {
102
+ const recent = errorState.errors.slice(-100);
103
+ const byType = {};
104
+
105
+ for (const err of recent) {
106
+ byType[err.type || 'error'] = (byType[err.type || 'error'] || 0) + 1;
107
+ }
108
+
109
+ return {
110
+ total: errorState.errors.length,
111
+ recent: recent.length,
112
+ byType,
113
+ circuitBreakers: Array.from(errorState.circuitBreakers.entries()).map(([name, state]) => ({
114
+ name,
115
+ state: state.state,
116
+ failures: state.failures,
117
+ lastFailure: state.lastFailure ? new Date(state.lastFailure).toISOString() : null
118
+ })),
119
+ checkpoints: Array.from(errorState.checkpoints.keys())
120
+ };
121
+ }
122
+
123
+ if (typeof global !== 'undefined') {
124
+ global.errorState = errorState;
125
+ global.getErrorStats = getErrorStats;
126
+ global.retryWithBackoff = retryWithBackoff;
127
+ global.withCircuitBreaker = withCircuitBreaker;
128
+ global.checkpoint = checkpoint;
129
+ global.restoreCheckpoint = restoreCheckpoint;
130
+ }
@@ -0,0 +1,69 @@
1
+ import { HTTP } from '@/config/constants';
2
+
3
+ export class AppError extends Error {
4
+ constructor(message, code = 'ERROR', statusCode = HTTP.INTERNAL_ERROR, context = {}) {
5
+ super(message);
6
+ this.name = 'AppError';
7
+ this.code = code;
8
+ this.statusCode = statusCode;
9
+ this.context = context;
10
+ }
11
+
12
+ toJSON() {
13
+ return {
14
+ status: 'error',
15
+ message: this.message,
16
+ code: this.code,
17
+ statusCode: this.statusCode,
18
+ context: this.context,
19
+ };
20
+ }
21
+ }
22
+
23
+ export class ValidationError extends AppError {
24
+ constructor(message, errors = {}) {
25
+ super(message, 'VALIDATION_ERROR', HTTP.BAD_REQUEST);
26
+ this.errors = errors;
27
+ }
28
+
29
+ toJSON() {
30
+ return {
31
+ status: 'error',
32
+ message: this.message,
33
+ code: this.code,
34
+ statusCode: this.statusCode,
35
+ errors: this.errors,
36
+ context: this.context,
37
+ };
38
+ }
39
+ }
40
+
41
+ export class AuthError extends AppError {
42
+ constructor(message = 'Authentication required') {
43
+ super(message, 'UNAUTHORIZED', HTTP.UNAUTHORIZED);
44
+ }
45
+ }
46
+
47
+ export class ForbiddenError extends AppError {
48
+ constructor(message = 'Permission denied') {
49
+ super(message, 'FORBIDDEN', HTTP.FORBIDDEN);
50
+ }
51
+ }
52
+
53
+ export class NotFoundError extends AppError {
54
+ constructor(message = 'Not found') {
55
+ super(message, 'NOT_FOUND', HTTP.NOT_FOUND);
56
+ }
57
+ }
58
+
59
+ export class ConflictError extends AppError {
60
+ constructor(message = 'Conflict') {
61
+ super(message, 'CONFLICT', HTTP.CONFLICT);
62
+ }
63
+ }
64
+
65
+ export class ServerError extends AppError {
66
+ constructor(message = 'Server error', code = 'SERVER_ERROR') {
67
+ super(message, code, HTTP.INTERNAL_ERROR);
68
+ }
69
+ }