@superdangerous/app-framework 4.9.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 (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +652 -0
  3. package/dist/api/logsRouter.d.ts +20 -0
  4. package/dist/api/logsRouter.d.ts.map +1 -0
  5. package/dist/api/logsRouter.js +515 -0
  6. package/dist/api/logsRouter.js.map +1 -0
  7. package/dist/cli/dev-server.d.ts +7 -0
  8. package/dist/cli/dev-server.d.ts.map +1 -0
  9. package/dist/cli/dev-server.js +640 -0
  10. package/dist/cli/dev-server.js.map +1 -0
  11. package/dist/cli/index.d.ts +7 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +26 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/core/StandardServer.d.ts +129 -0
  16. package/dist/core/StandardServer.d.ts.map +1 -0
  17. package/dist/core/StandardServer.js +453 -0
  18. package/dist/core/StandardServer.js.map +1 -0
  19. package/dist/core/apiResponse.d.ts +69 -0
  20. package/dist/core/apiResponse.d.ts.map +1 -0
  21. package/dist/core/apiResponse.js +127 -0
  22. package/dist/core/apiResponse.js.map +1 -0
  23. package/dist/core/healthCheck.d.ts +160 -0
  24. package/dist/core/healthCheck.d.ts.map +1 -0
  25. package/dist/core/healthCheck.js +398 -0
  26. package/dist/core/healthCheck.js.map +1 -0
  27. package/dist/core/index.d.ts +40 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +40 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/logger.d.ts +117 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/logger.js +826 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/portUtils.d.ts +71 -0
  36. package/dist/core/portUtils.d.ts.map +1 -0
  37. package/dist/core/portUtils.js +240 -0
  38. package/dist/core/portUtils.js.map +1 -0
  39. package/dist/core/storageService.d.ts +119 -0
  40. package/dist/core/storageService.d.ts.map +1 -0
  41. package/dist/core/storageService.js +405 -0
  42. package/dist/core/storageService.js.map +1 -0
  43. package/dist/desktop/bundler.d.ts +40 -0
  44. package/dist/desktop/bundler.d.ts.map +1 -0
  45. package/dist/desktop/bundler.js +176 -0
  46. package/dist/desktop/bundler.js.map +1 -0
  47. package/dist/desktop/index.d.ts +25 -0
  48. package/dist/desktop/index.d.ts.map +1 -0
  49. package/dist/desktop/index.js +15 -0
  50. package/dist/desktop/index.js.map +1 -0
  51. package/dist/desktop/native-modules.d.ts +66 -0
  52. package/dist/desktop/native-modules.d.ts.map +1 -0
  53. package/dist/desktop/native-modules.js +200 -0
  54. package/dist/desktop/native-modules.js.map +1 -0
  55. package/dist/index.d.ts +29 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +39 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/logging/LogCategories.d.ts +87 -0
  60. package/dist/logging/LogCategories.d.ts.map +1 -0
  61. package/dist/logging/LogCategories.js +205 -0
  62. package/dist/logging/LogCategories.js.map +1 -0
  63. package/dist/middleware/aiErrorHandler.d.ts +31 -0
  64. package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
  65. package/dist/middleware/aiErrorHandler.js +181 -0
  66. package/dist/middleware/aiErrorHandler.js.map +1 -0
  67. package/dist/middleware/auth.d.ts +101 -0
  68. package/dist/middleware/auth.d.ts.map +1 -0
  69. package/dist/middleware/auth.js +230 -0
  70. package/dist/middleware/auth.js.map +1 -0
  71. package/dist/middleware/cors.d.ts +56 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +123 -0
  74. package/dist/middleware/cors.js.map +1 -0
  75. package/dist/middleware/errorHandler.d.ts +13 -0
  76. package/dist/middleware/errorHandler.d.ts.map +1 -0
  77. package/dist/middleware/errorHandler.js +85 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/fileUpload.d.ts +62 -0
  80. package/dist/middleware/fileUpload.d.ts.map +1 -0
  81. package/dist/middleware/fileUpload.js +175 -0
  82. package/dist/middleware/fileUpload.js.map +1 -0
  83. package/dist/middleware/health.d.ts +48 -0
  84. package/dist/middleware/health.d.ts.map +1 -0
  85. package/dist/middleware/health.js +143 -0
  86. package/dist/middleware/health.js.map +1 -0
  87. package/dist/middleware/index.d.ts +20 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +18 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/openapi.d.ts +64 -0
  92. package/dist/middleware/openapi.d.ts.map +1 -0
  93. package/dist/middleware/openapi.js +258 -0
  94. package/dist/middleware/openapi.js.map +1 -0
  95. package/dist/middleware/requestLogging.d.ts +22 -0
  96. package/dist/middleware/requestLogging.d.ts.map +1 -0
  97. package/dist/middleware/requestLogging.js +61 -0
  98. package/dist/middleware/requestLogging.js.map +1 -0
  99. package/dist/middleware/session.d.ts +84 -0
  100. package/dist/middleware/session.d.ts.map +1 -0
  101. package/dist/middleware/session.js +189 -0
  102. package/dist/middleware/session.js.map +1 -0
  103. package/dist/middleware/validation.d.ts +1337 -0
  104. package/dist/middleware/validation.d.ts.map +1 -0
  105. package/dist/middleware/validation.js +483 -0
  106. package/dist/middleware/validation.js.map +1 -0
  107. package/dist/services/aiService.d.ts +180 -0
  108. package/dist/services/aiService.d.ts.map +1 -0
  109. package/dist/services/aiService.js +547 -0
  110. package/dist/services/aiService.js.map +1 -0
  111. package/dist/services/conversationStorage.d.ts +38 -0
  112. package/dist/services/conversationStorage.d.ts.map +1 -0
  113. package/dist/services/conversationStorage.js +158 -0
  114. package/dist/services/conversationStorage.js.map +1 -0
  115. package/dist/services/crossPlatformBuffer.d.ts +84 -0
  116. package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
  117. package/dist/services/crossPlatformBuffer.js +246 -0
  118. package/dist/services/crossPlatformBuffer.js.map +1 -0
  119. package/dist/services/index.d.ts +17 -0
  120. package/dist/services/index.d.ts.map +1 -0
  121. package/dist/services/index.js +18 -0
  122. package/dist/services/index.js.map +1 -0
  123. package/dist/services/networkService.d.ts +81 -0
  124. package/dist/services/networkService.d.ts.map +1 -0
  125. package/dist/services/networkService.js +268 -0
  126. package/dist/services/networkService.js.map +1 -0
  127. package/dist/services/queueService.d.ts +112 -0
  128. package/dist/services/queueService.d.ts.map +1 -0
  129. package/dist/services/queueService.js +338 -0
  130. package/dist/services/queueService.js.map +1 -0
  131. package/dist/services/settingsService.d.ts +135 -0
  132. package/dist/services/settingsService.d.ts.map +1 -0
  133. package/dist/services/settingsService.js +425 -0
  134. package/dist/services/settingsService.js.map +1 -0
  135. package/dist/services/systemMonitor.d.ts +208 -0
  136. package/dist/services/systemMonitor.d.ts.map +1 -0
  137. package/dist/services/systemMonitor.js +693 -0
  138. package/dist/services/systemMonitor.js.map +1 -0
  139. package/dist/services/updateService.d.ts +78 -0
  140. package/dist/services/updateService.d.ts.map +1 -0
  141. package/dist/services/updateService.js +252 -0
  142. package/dist/services/updateService.js.map +1 -0
  143. package/dist/services/websocketEvents.d.ts +372 -0
  144. package/dist/services/websocketEvents.d.ts.map +1 -0
  145. package/dist/services/websocketEvents.js +338 -0
  146. package/dist/services/websocketEvents.js.map +1 -0
  147. package/dist/services/websocketServer.d.ts +80 -0
  148. package/dist/services/websocketServer.d.ts.map +1 -0
  149. package/dist/services/websocketServer.js +299 -0
  150. package/dist/services/websocketServer.js.map +1 -0
  151. package/dist/settings/SettingsSchema.d.ts +151 -0
  152. package/dist/settings/SettingsSchema.d.ts.map +1 -0
  153. package/dist/settings/SettingsSchema.js +424 -0
  154. package/dist/settings/SettingsSchema.js.map +1 -0
  155. package/dist/testing/TestServer.d.ts +69 -0
  156. package/dist/testing/TestServer.d.ts.map +1 -0
  157. package/dist/testing/TestServer.js +250 -0
  158. package/dist/testing/TestServer.js.map +1 -0
  159. package/dist/types/index.d.ts +137 -0
  160. package/dist/types/index.d.ts.map +1 -0
  161. package/dist/types/index.js +5 -0
  162. package/dist/types/index.js.map +1 -0
  163. package/dist/utils/appPaths.d.ts +74 -0
  164. package/dist/utils/appPaths.d.ts.map +1 -0
  165. package/dist/utils/appPaths.js +162 -0
  166. package/dist/utils/appPaths.js.map +1 -0
  167. package/dist/utils/fs-utils.d.ts +50 -0
  168. package/dist/utils/fs-utils.d.ts.map +1 -0
  169. package/dist/utils/fs-utils.js +114 -0
  170. package/dist/utils/fs-utils.js.map +1 -0
  171. package/dist/utils/index.d.ts +12 -0
  172. package/dist/utils/index.d.ts.map +1 -0
  173. package/dist/utils/index.js +10 -0
  174. package/dist/utils/index.js.map +1 -0
  175. package/dist/utils/standardConfig.d.ts +61 -0
  176. package/dist/utils/standardConfig.d.ts.map +1 -0
  177. package/dist/utils/standardConfig.js +109 -0
  178. package/dist/utils/standardConfig.js.map +1 -0
  179. package/dist/utils/startupBanner.d.ts +34 -0
  180. package/dist/utils/startupBanner.d.ts.map +1 -0
  181. package/dist/utils/startupBanner.js +169 -0
  182. package/dist/utils/startupBanner.js.map +1 -0
  183. package/dist/utils/startupLogger.d.ts +45 -0
  184. package/dist/utils/startupLogger.d.ts.map +1 -0
  185. package/dist/utils/startupLogger.js +200 -0
  186. package/dist/utils/startupLogger.js.map +1 -0
  187. package/package.json +151 -0
  188. package/src/api/logsRouter.ts +600 -0
  189. package/src/cli/dev-server.ts +803 -0
  190. package/src/cli/index.ts +31 -0
  191. package/src/core/StandardServer.ts +587 -0
  192. package/src/core/apiResponse.ts +202 -0
  193. package/src/core/healthCheck.ts +565 -0
  194. package/src/core/index.ts +80 -0
  195. package/src/core/logger.ts +1092 -0
  196. package/src/core/portUtils.ts +319 -0
  197. package/src/core/storageService.ts +595 -0
  198. package/src/desktop/bundler.ts +271 -0
  199. package/src/desktop/index.ts +18 -0
  200. package/src/desktop/native-modules.ts +289 -0
  201. package/src/index.ts +142 -0
  202. package/src/logging/LogCategories.ts +302 -0
  203. package/src/middleware/aiErrorHandler.ts +278 -0
  204. package/src/middleware/auth.ts +329 -0
  205. package/src/middleware/cors.ts +187 -0
  206. package/src/middleware/errorHandler.ts +103 -0
  207. package/src/middleware/fileUpload.ts +252 -0
  208. package/src/middleware/health.ts +206 -0
  209. package/src/middleware/index.ts +71 -0
  210. package/src/middleware/openapi.ts +305 -0
  211. package/src/middleware/requestLogging.ts +92 -0
  212. package/src/middleware/session.ts +238 -0
  213. package/src/middleware/validation.ts +603 -0
  214. package/src/services/aiService.ts +789 -0
  215. package/src/services/conversationStorage.ts +232 -0
  216. package/src/services/crossPlatformBuffer.ts +341 -0
  217. package/src/services/index.ts +47 -0
  218. package/src/services/networkService.ts +351 -0
  219. package/src/services/queueService.ts +446 -0
  220. package/src/services/settingsService.ts +549 -0
  221. package/src/services/systemMonitor.ts +936 -0
  222. package/src/services/updateService.ts +334 -0
  223. package/src/services/websocketEvents.ts +409 -0
  224. package/src/services/websocketServer.ts +394 -0
  225. package/src/settings/SettingsSchema.ts +664 -0
  226. package/src/testing/TestServer.ts +312 -0
  227. package/src/types/index.ts +154 -0
  228. package/src/utils/appPaths.ts +196 -0
  229. package/src/utils/fs-utils.ts +130 -0
  230. package/src/utils/index.ts +15 -0
  231. package/src/utils/standardConfig.ts +178 -0
  232. package/src/utils/startupBanner.ts +287 -0
  233. package/src/utils/startupLogger.ts +268 -0
  234. package/ui/dist/index.d.mts +1221 -0
  235. package/ui/dist/index.d.ts +1221 -0
  236. package/ui/dist/index.js +73 -0
  237. package/ui/dist/index.js.map +1 -0
  238. package/ui/dist/index.mjs +73 -0
  239. package/ui/dist/index.mjs.map +1 -0
@@ -0,0 +1,549 @@
1
+ /**
2
+ * Settings Service
3
+ * Manages application settings with persistence, validation, and real-time updates
4
+ */
5
+
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import { EventEmitter } from "events";
9
+ import { ZodSchema } from "zod";
10
+ import { createLogger } from "../core/index.js";
11
+
12
+ let logger: any; // Will be initialized when needed
13
+
14
+ function ensureLogger() {
15
+ if (!logger) {
16
+ logger = createLogger("SettingsService");
17
+ }
18
+ return logger;
19
+ }
20
+
21
+ export interface SettingsCategory {
22
+ id: string;
23
+ label: string;
24
+ description?: string;
25
+ icon?: string;
26
+ schema?: ZodSchema;
27
+ fields: SettingsField[];
28
+ order?: number;
29
+ }
30
+
31
+ export interface SettingsField {
32
+ key: string;
33
+ label: string;
34
+ type:
35
+ | "text"
36
+ | "number"
37
+ | "boolean"
38
+ | "select"
39
+ | "multiselect"
40
+ | "json"
41
+ | "password"
42
+ | "email"
43
+ | "url"
44
+ | "color"
45
+ | "date"
46
+ | "time"
47
+ | "datetime";
48
+ description?: string;
49
+ placeholder?: string;
50
+ defaultValue?: any;
51
+ required?: boolean;
52
+ validation?: ZodSchema;
53
+ options?: Array<{ value: any; label: string; description?: string }>;
54
+ min?: number;
55
+ max?: number;
56
+ step?: number;
57
+ pattern?: string;
58
+ readonly?: boolean;
59
+ hidden?: boolean;
60
+ dependsOn?: {
61
+ field: string;
62
+ value: any;
63
+ };
64
+ transform?: (value: any) => any;
65
+ format?: (value: any) => string;
66
+ }
67
+
68
+ export interface SettingsOptions {
69
+ storagePath?: string;
70
+ autoSave?: boolean;
71
+ saveDebounce?: number;
72
+ encryptSensitive?: boolean;
73
+ sensitiveFields?: string[];
74
+ }
75
+
76
+ export class SettingsService extends EventEmitter {
77
+ private categories: Map<string, SettingsCategory> = new Map();
78
+ private settings: Record<string, any> = {};
79
+ private storagePath?: string;
80
+ private autoSave: boolean;
81
+ private saveDebounce: number;
82
+ private saveTimeout?: NodeJS.Timeout;
83
+ private sensitiveFields: Set<string>;
84
+
85
+ constructor(private options: SettingsOptions = {}) {
86
+ super();
87
+ this.storagePath = options.storagePath;
88
+ this.autoSave = options.autoSave ?? true;
89
+ this.saveDebounce = options.saveDebounce ?? 1000;
90
+ this.sensitiveFields = new Set(options.sensitiveFields || []);
91
+ }
92
+
93
+ /**
94
+ * Register a settings category
95
+ */
96
+ registerCategory(category: SettingsCategory): void {
97
+ this.categories.set(category.id, category);
98
+
99
+ // Initialize default values
100
+ for (const field of category.fields) {
101
+ if (field.defaultValue !== undefined && !(field.key in this.settings)) {
102
+ this.settings[field.key] = field.defaultValue;
103
+ }
104
+ }
105
+
106
+ ensureLogger().info(`Registered settings category: ${category.id}`);
107
+ }
108
+
109
+ /**
110
+ * Load settings from storage
111
+ */
112
+ async load(): Promise<void> {
113
+ if (!this.storagePath) {
114
+ ensureLogger().info("No storage path configured, using defaults");
115
+ return;
116
+ }
117
+
118
+ try {
119
+ const fullPath = path.resolve(this.storagePath);
120
+
121
+ if (!fs.existsSync(fullPath)) {
122
+ ensureLogger().info("Settings file not found, using defaults");
123
+ return;
124
+ }
125
+
126
+ const content = await fs.promises.readFile(fullPath, "utf-8");
127
+ const loaded = JSON.parse(content);
128
+
129
+ // Merge with defaults
130
+ this.settings = { ...this.settings, ...loaded };
131
+
132
+ // Decrypt sensitive fields if needed
133
+ if (this.options.encryptSensitive) {
134
+ this.decryptSensitiveFields();
135
+ }
136
+
137
+ ensureLogger().info("Settings loaded successfully");
138
+ this.emit("loaded", this.settings);
139
+ } catch (_error: any) {
140
+ ensureLogger().error("Failed to load settings:", _error);
141
+ throw new Error(`Failed to load settings: ${_error.message}`);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Save settings to storage
147
+ */
148
+ async save(): Promise<void> {
149
+ if (!this.storagePath) {
150
+ ensureLogger().info("No storage path configured, skipping save");
151
+ return;
152
+ }
153
+
154
+ try {
155
+ const fullPath = path.resolve(this.storagePath);
156
+ const dir = path.dirname(fullPath);
157
+
158
+ // Ensure directory exists
159
+ if (!fs.existsSync(dir)) {
160
+ await fs.promises.mkdir(dir, { recursive: true });
161
+ }
162
+
163
+ // Prepare settings for saving
164
+ let toSave = { ...this.settings };
165
+
166
+ // Encrypt sensitive fields if needed
167
+ if (this.options.encryptSensitive) {
168
+ toSave = this.encryptSensitiveFields(toSave);
169
+ }
170
+
171
+ // Save to file
172
+ const content = JSON.stringify(toSave, null, 2);
173
+ await fs.promises.writeFile(fullPath, content, "utf-8");
174
+
175
+ ensureLogger().info("Settings saved successfully");
176
+ this.emit("saved", this.settings);
177
+ } catch (_error: any) {
178
+ ensureLogger().error("Failed to save settings:", _error);
179
+ throw new Error(`Failed to save settings: ${_error.message}`);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Get a setting value
185
+ */
186
+ get<T = any>(key: string, defaultValue?: T): T {
187
+ const value = this.getNestedValue(this.settings, key);
188
+ return value !== undefined ? value : (defaultValue as T);
189
+ }
190
+
191
+ /**
192
+ * Set a setting value
193
+ */
194
+ async set(key: string, value: any): Promise<void> {
195
+ const previous = this.get(key);
196
+
197
+ // Find field definition
198
+ const field = this.findField(key);
199
+
200
+ // Validate if field has validation
201
+ if (field?.validation) {
202
+ const result = field.validation.safeParse(value);
203
+ if (!result.success) {
204
+ throw new Error(
205
+ `Validation failed for ${key}: ${result.error.message}`,
206
+ );
207
+ }
208
+ value = result.data;
209
+ }
210
+
211
+ // Apply transform if defined
212
+ if (field?.transform) {
213
+ value = field.transform(value);
214
+ }
215
+
216
+ // Set the value
217
+ this.setNestedValue(this.settings, key, value);
218
+
219
+ // Emit change event
220
+ this.emit("change", { key, value, previous });
221
+
222
+ // Auto-save if enabled
223
+ if (this.autoSave) {
224
+ this.scheduleSave();
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Update multiple settings
230
+ */
231
+ async update(updates: Record<string, any>): Promise<void> {
232
+ const changes: Array<{ key: string; value: any; previous: any }> = [];
233
+
234
+ for (const [key, value] of Object.entries(updates)) {
235
+ const previous = this.get(key);
236
+ await this.set(key, value);
237
+ changes.push({ key, value, previous });
238
+ }
239
+
240
+ this.emit("bulk-change", changes);
241
+ }
242
+
243
+ /**
244
+ * Get all settings
245
+ */
246
+ getAll(): Record<string, any> {
247
+ return { ...this.settings };
248
+ }
249
+
250
+ /**
251
+ * Get settings by category
252
+ */
253
+ getByCategory(categoryId: string): Record<string, any> {
254
+ const category = this.categories.get(categoryId);
255
+ if (!category) {
256
+ return {};
257
+ }
258
+
259
+ const result: Record<string, any> = {};
260
+ for (const field of category.fields) {
261
+ result[field.key] = this.get(field.key, field.defaultValue);
262
+ }
263
+ return result;
264
+ }
265
+
266
+ /**
267
+ * Get all categories
268
+ */
269
+ getCategories(): SettingsCategory[] {
270
+ return Array.from(this.categories.values()).sort(
271
+ (a, b) => (a.order || 0) - (b.order || 0),
272
+ );
273
+ }
274
+
275
+ /**
276
+ * Get category by ID
277
+ */
278
+ getCategory(id: string): SettingsCategory | undefined {
279
+ return this.categories.get(id);
280
+ }
281
+
282
+ /**
283
+ * Validate all settings
284
+ */
285
+ validate(): { valid: boolean; errors: Record<string, string> } {
286
+ const errors: Record<string, string> = {};
287
+
288
+ for (const category of this.categories.values()) {
289
+ for (const field of category.fields) {
290
+ const value = this.get(field.key);
291
+
292
+ // Check required fields
293
+ if (
294
+ field.required &&
295
+ (value === undefined || value === null || value === "")
296
+ ) {
297
+ errors[field.key] = `${field.label} is required`;
298
+ continue;
299
+ }
300
+
301
+ // Validate with schema
302
+ if (field.validation && value !== undefined) {
303
+ const result = field.validation.safeParse(value);
304
+ if (!result.success) {
305
+ errors[field.key] =
306
+ result.error.issues[0]?.message || "Validation failed";
307
+ }
308
+ }
309
+ }
310
+ }
311
+
312
+ return {
313
+ valid: Object.keys(errors).length === 0,
314
+ errors,
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Reset settings to defaults
320
+ */
321
+ reset(categoryId?: string): void {
322
+ if (categoryId) {
323
+ const category = this.categories.get(categoryId);
324
+ if (category) {
325
+ for (const field of category.fields) {
326
+ if (field.defaultValue !== undefined) {
327
+ delete this.settings[field.key];
328
+ this.setNestedValue(this.settings, field.key, field.defaultValue);
329
+ } else {
330
+ delete this.settings[field.key];
331
+ }
332
+ }
333
+ }
334
+ } else {
335
+ // Reset all settings
336
+ this.settings = {};
337
+ for (const category of this.categories.values()) {
338
+ for (const field of category.fields) {
339
+ if (field.defaultValue !== undefined) {
340
+ this.settings[field.key] = field.defaultValue;
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ this.emit("reset", categoryId);
347
+
348
+ if (this.autoSave) {
349
+ this.scheduleSave();
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Export settings
355
+ */
356
+ export(categoryId?: string): string {
357
+ const data = categoryId ? this.getByCategory(categoryId) : this.settings;
358
+ return JSON.stringify(data, null, 2);
359
+ }
360
+
361
+ /**
362
+ * Import settings
363
+ */
364
+ async import(data: string): Promise<void> {
365
+ try {
366
+ const imported = JSON.parse(data);
367
+ await this.update(imported);
368
+ ensureLogger().info("Settings imported successfully");
369
+ } catch (_error: any) {
370
+ ensureLogger().error("Failed to import settings:", _error);
371
+ throw new Error(`Failed to import settings: ${_error.message}`);
372
+ }
373
+ }
374
+
375
+ // Private methods
376
+
377
+ private findField(key: string): SettingsField | undefined {
378
+ for (const category of this.categories.values()) {
379
+ const field = category.fields.find((f) => f.key === key);
380
+ if (field) return field;
381
+ }
382
+ return undefined;
383
+ }
384
+
385
+ private scheduleSave(): void {
386
+ if (this.saveTimeout) {
387
+ clearTimeout(this.saveTimeout);
388
+ }
389
+
390
+ this.saveTimeout = setTimeout(async () => {
391
+ try {
392
+ await this.save();
393
+ } catch (_error: any) {
394
+ ensureLogger().error("Auto-save failed:", _error);
395
+ this.emit("save-error", _error);
396
+ }
397
+ }, this.saveDebounce);
398
+ }
399
+
400
+ private encryptSensitiveFields(
401
+ settings: Record<string, any>,
402
+ ): Record<string, any> {
403
+ const result = { ...settings };
404
+
405
+ for (const field of this.sensitiveFields) {
406
+ const value = this.getNestedValue(result, field);
407
+ if (value !== undefined && typeof value === "string") {
408
+ // Simple base64 encoding for demonstration
409
+ // In production, use proper encryption
410
+ this.setNestedValue(
411
+ result,
412
+ field,
413
+ Buffer.from(value).toString("base64"),
414
+ );
415
+ }
416
+ }
417
+
418
+ return result;
419
+ }
420
+
421
+ private decryptSensitiveFields(): void {
422
+ for (const field of this.sensitiveFields) {
423
+ const value = this.getNestedValue(this.settings, field);
424
+ if (value !== undefined && typeof value === "string") {
425
+ try {
426
+ // Simple base64 decoding for demonstration
427
+ // In production, use proper decryption
428
+ this.setNestedValue(
429
+ this.settings,
430
+ field,
431
+ Buffer.from(value, "base64").toString(),
432
+ );
433
+ } catch {
434
+ // Value might not be encrypted, leave as is
435
+ }
436
+ }
437
+ }
438
+ }
439
+
440
+ private getNestedValue(obj: any, path: string): any {
441
+ // Support both nested objects and flat keys containing dots
442
+ if (Object.prototype.hasOwnProperty.call(obj, path)) {
443
+ return (obj as any)[path];
444
+ }
445
+
446
+ return path.split(".").reduce((current, key) => current?.[key], obj);
447
+ }
448
+
449
+ private setNestedValue(obj: any, path: string, value: any): void {
450
+ const keys = path.split(".");
451
+ const lastKey = keys.pop()!;
452
+ const target = keys.reduce((current, key) => {
453
+ if (!current[key]) {
454
+ current[key] = {};
455
+ }
456
+ return current[key];
457
+ }, obj);
458
+ target[lastKey] = value;
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Create common settings categories
464
+ */
465
+ export const CommonSettingsCategories = {
466
+ general: (): SettingsCategory => ({
467
+ id: "general",
468
+ label: "General",
469
+ description: "General application settings",
470
+ icon: "Settings",
471
+ order: 1,
472
+ fields: [
473
+ {
474
+ key: "app.name",
475
+ label: "Application Name",
476
+ type: "text",
477
+ defaultValue: "My Application",
478
+ required: true,
479
+ },
480
+ {
481
+ key: "app.description",
482
+ label: "Description",
483
+ type: "text",
484
+ placeholder: "Enter application description",
485
+ },
486
+ ],
487
+ }),
488
+
489
+ server: (): SettingsCategory => ({
490
+ id: "server",
491
+ label: "Server",
492
+ description: "Server configuration",
493
+ icon: "Server",
494
+ order: 2,
495
+ fields: [
496
+ {
497
+ key: "server.port",
498
+ label: "Port",
499
+ type: "number",
500
+ defaultValue: 3000,
501
+ min: 1,
502
+ max: 65535,
503
+ required: true,
504
+ },
505
+ {
506
+ key: "server.host",
507
+ label: "Host",
508
+ type: "text",
509
+ defaultValue: "localhost",
510
+ },
511
+ {
512
+ key: "server.https.enabled",
513
+ label: "Enable HTTPS",
514
+ type: "boolean",
515
+ defaultValue: false,
516
+ },
517
+ ],
518
+ }),
519
+
520
+ logging: (): SettingsCategory => ({
521
+ id: "logging",
522
+ label: "Logging",
523
+ description: "Logging configuration",
524
+ icon: "FileText",
525
+ order: 3,
526
+ fields: [
527
+ {
528
+ key: "logging.level",
529
+ label: "Log Level",
530
+ type: "select",
531
+ defaultValue: "info",
532
+ options: [
533
+ { value: "debug", label: "Debug" },
534
+ { value: "info", label: "Info" },
535
+ { value: "warn", label: "Warning" },
536
+ { value: "error", label: "Error" },
537
+ ],
538
+ },
539
+ {
540
+ key: "logging.file",
541
+ label: "Log File Path",
542
+ type: "text",
543
+ placeholder: "./logs/app.log",
544
+ },
545
+ ],
546
+ }),
547
+ };
548
+
549
+ export default SettingsService;