@sylphx/flow 1.0.1 → 1.0.3

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 (229) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +10 -9
  3. package/src/commands/codebase-command.ts +168 -0
  4. package/src/commands/flow-command.ts +1137 -0
  5. package/src/commands/flow-orchestrator.ts +296 -0
  6. package/src/commands/hook-command.ts +444 -0
  7. package/src/commands/init-command.ts +92 -0
  8. package/src/commands/init-core.ts +322 -0
  9. package/src/commands/knowledge-command.ts +161 -0
  10. package/src/commands/run-command.ts +120 -0
  11. package/src/components/benchmark-monitor.tsx +331 -0
  12. package/src/components/reindex-progress.tsx +261 -0
  13. package/src/composables/functional/index.ts +14 -0
  14. package/src/composables/functional/useEnvironment.ts +171 -0
  15. package/src/composables/functional/useFileSystem.ts +139 -0
  16. package/src/composables/index.ts +5 -0
  17. package/src/composables/useEnv.ts +13 -0
  18. package/src/composables/useRuntimeConfig.ts +27 -0
  19. package/src/composables/useTargetConfig.ts +45 -0
  20. package/src/config/ai-config.ts +376 -0
  21. package/src/config/constants.ts +35 -0
  22. package/src/config/index.ts +27 -0
  23. package/src/config/rules.ts +43 -0
  24. package/src/config/servers.ts +371 -0
  25. package/src/config/targets.ts +126 -0
  26. package/src/core/agent-loader.ts +141 -0
  27. package/src/core/agent-manager.ts +174 -0
  28. package/src/core/ai-sdk.ts +603 -0
  29. package/src/core/app-factory.ts +381 -0
  30. package/src/core/builtin-agents.ts +9 -0
  31. package/src/core/command-system.ts +550 -0
  32. package/src/core/config-system.ts +550 -0
  33. package/src/core/connection-pool.ts +390 -0
  34. package/src/core/di-container.ts +155 -0
  35. package/src/core/error-handling.ts +519 -0
  36. package/src/core/formatting/bytes.test.ts +115 -0
  37. package/src/core/formatting/bytes.ts +64 -0
  38. package/src/core/functional/async.ts +313 -0
  39. package/src/core/functional/either.ts +109 -0
  40. package/src/core/functional/error-handler.ts +135 -0
  41. package/src/core/functional/error-types.ts +311 -0
  42. package/src/core/functional/index.ts +19 -0
  43. package/src/core/functional/option.ts +142 -0
  44. package/src/core/functional/pipe.ts +189 -0
  45. package/src/core/functional/result.ts +204 -0
  46. package/src/core/functional/validation.ts +138 -0
  47. package/src/core/headless-display.ts +96 -0
  48. package/src/core/index.ts +6 -0
  49. package/src/core/installers/file-installer.ts +303 -0
  50. package/src/core/installers/mcp-installer.ts +213 -0
  51. package/src/core/interfaces/index.ts +22 -0
  52. package/src/core/interfaces/repository.interface.ts +91 -0
  53. package/src/core/interfaces/service.interface.ts +133 -0
  54. package/src/core/interfaces.ts +129 -0
  55. package/src/core/loop-controller.ts +200 -0
  56. package/src/core/result.ts +351 -0
  57. package/src/core/rule-loader.ts +147 -0
  58. package/src/core/rule-manager.ts +240 -0
  59. package/src/core/service-config.ts +252 -0
  60. package/src/core/session-service.ts +121 -0
  61. package/src/core/state-detector.ts +389 -0
  62. package/src/core/storage-factory.ts +115 -0
  63. package/src/core/stream-handler.ts +288 -0
  64. package/src/core/target-manager.ts +161 -0
  65. package/src/core/type-utils.ts +427 -0
  66. package/src/core/unified-storage.ts +456 -0
  67. package/src/core/upgrade-manager.ts +300 -0
  68. package/src/core/validation/limit.test.ts +155 -0
  69. package/src/core/validation/limit.ts +46 -0
  70. package/src/core/validation/query.test.ts +44 -0
  71. package/src/core/validation/query.ts +20 -0
  72. package/src/db/auto-migrate.ts +322 -0
  73. package/src/db/base-database-client.ts +144 -0
  74. package/src/db/cache-db.ts +218 -0
  75. package/src/db/cache-schema.ts +75 -0
  76. package/src/db/database.ts +70 -0
  77. package/src/db/index.ts +252 -0
  78. package/src/db/memory-db.ts +153 -0
  79. package/src/db/memory-schema.ts +29 -0
  80. package/src/db/schema.ts +289 -0
  81. package/src/db/session-repository.ts +733 -0
  82. package/src/domains/codebase/index.ts +5 -0
  83. package/src/domains/codebase/tools.ts +139 -0
  84. package/src/domains/index.ts +8 -0
  85. package/src/domains/knowledge/index.ts +10 -0
  86. package/src/domains/knowledge/resources.ts +537 -0
  87. package/src/domains/knowledge/tools.ts +174 -0
  88. package/src/domains/utilities/index.ts +6 -0
  89. package/src/domains/utilities/time/index.ts +5 -0
  90. package/src/domains/utilities/time/tools.ts +291 -0
  91. package/src/index.ts +211 -0
  92. package/src/services/agent-service.ts +273 -0
  93. package/src/services/claude-config-service.ts +252 -0
  94. package/src/services/config-service.ts +258 -0
  95. package/src/services/evaluation-service.ts +271 -0
  96. package/src/services/functional/evaluation-logic.ts +296 -0
  97. package/src/services/functional/file-processor.ts +273 -0
  98. package/src/services/functional/index.ts +12 -0
  99. package/src/services/index.ts +13 -0
  100. package/src/services/mcp-service.ts +432 -0
  101. package/src/services/memory.service.ts +476 -0
  102. package/src/services/search/base-indexer.ts +156 -0
  103. package/src/services/search/codebase-indexer-types.ts +38 -0
  104. package/src/services/search/codebase-indexer.ts +647 -0
  105. package/src/services/search/embeddings-provider.ts +455 -0
  106. package/src/services/search/embeddings.ts +316 -0
  107. package/src/services/search/functional-indexer.ts +323 -0
  108. package/src/services/search/index.ts +27 -0
  109. package/src/services/search/indexer.ts +380 -0
  110. package/src/services/search/knowledge-indexer.ts +422 -0
  111. package/src/services/search/semantic-search.ts +244 -0
  112. package/src/services/search/tfidf.ts +559 -0
  113. package/src/services/search/unified-search-service.ts +888 -0
  114. package/src/services/smart-config-service.ts +385 -0
  115. package/src/services/storage/cache-storage.ts +487 -0
  116. package/src/services/storage/drizzle-storage.ts +581 -0
  117. package/src/services/storage/index.ts +15 -0
  118. package/src/services/storage/lancedb-vector-storage.ts +494 -0
  119. package/src/services/storage/memory-storage.ts +268 -0
  120. package/src/services/storage/separated-storage.ts +467 -0
  121. package/src/services/storage/vector-storage.ts +13 -0
  122. package/src/shared/agents/index.ts +63 -0
  123. package/src/shared/files/index.ts +99 -0
  124. package/src/shared/index.ts +32 -0
  125. package/src/shared/logging/index.ts +24 -0
  126. package/src/shared/processing/index.ts +153 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/targets/claude-code.ts +574 -0
  129. package/src/targets/functional/claude-code-logic.ts +185 -0
  130. package/src/targets/functional/index.ts +6 -0
  131. package/src/targets/opencode.ts +529 -0
  132. package/src/types/agent.types.ts +32 -0
  133. package/src/types/api/batch.ts +108 -0
  134. package/src/types/api/errors.ts +118 -0
  135. package/src/types/api/index.ts +55 -0
  136. package/src/types/api/requests.ts +76 -0
  137. package/src/types/api/responses.ts +180 -0
  138. package/src/types/api/websockets.ts +85 -0
  139. package/src/types/api.types.ts +9 -0
  140. package/src/types/benchmark.ts +49 -0
  141. package/src/types/cli.types.ts +87 -0
  142. package/src/types/common.types.ts +35 -0
  143. package/src/types/database.types.ts +510 -0
  144. package/src/types/mcp-config.types.ts +448 -0
  145. package/src/types/mcp.types.ts +69 -0
  146. package/src/types/memory-types.ts +63 -0
  147. package/src/types/provider.types.ts +28 -0
  148. package/src/types/rule.types.ts +24 -0
  149. package/src/types/session.types.ts +214 -0
  150. package/src/types/target-config.types.ts +295 -0
  151. package/src/types/target.types.ts +140 -0
  152. package/src/types/todo.types.ts +25 -0
  153. package/src/types.ts +40 -0
  154. package/src/utils/advanced-tokenizer.ts +191 -0
  155. package/src/utils/agent-enhancer.ts +114 -0
  156. package/src/utils/ai-model-fetcher.ts +19 -0
  157. package/src/utils/async-file-operations.ts +516 -0
  158. package/src/utils/audio-player.ts +345 -0
  159. package/src/utils/cli-output.ts +266 -0
  160. package/src/utils/codebase-helpers.ts +211 -0
  161. package/src/utils/console-ui.ts +79 -0
  162. package/src/utils/database-errors.ts +140 -0
  163. package/src/utils/debug-logger.ts +49 -0
  164. package/src/utils/error-handler.ts +53 -0
  165. package/src/utils/file-operations.ts +310 -0
  166. package/src/utils/file-scanner.ts +259 -0
  167. package/src/utils/functional/array.ts +355 -0
  168. package/src/utils/functional/index.ts +15 -0
  169. package/src/utils/functional/object.ts +279 -0
  170. package/src/utils/functional/string.ts +281 -0
  171. package/src/utils/functional.ts +543 -0
  172. package/src/utils/help.ts +20 -0
  173. package/src/utils/immutable-cache.ts +106 -0
  174. package/src/utils/index.ts +78 -0
  175. package/src/utils/jsonc.ts +158 -0
  176. package/src/utils/logger.ts +396 -0
  177. package/src/utils/mcp-config.ts +249 -0
  178. package/src/utils/memory-tui.ts +414 -0
  179. package/src/utils/models-dev.ts +91 -0
  180. package/src/utils/notifications.ts +169 -0
  181. package/src/utils/object-utils.ts +51 -0
  182. package/src/utils/parallel-operations.ts +487 -0
  183. package/src/utils/paths.ts +143 -0
  184. package/src/utils/process-manager.ts +155 -0
  185. package/src/utils/prompts.ts +120 -0
  186. package/src/utils/search-tool-builder.ts +214 -0
  187. package/src/utils/secret-utils.ts +179 -0
  188. package/src/utils/security.ts +537 -0
  189. package/src/utils/session-manager.ts +168 -0
  190. package/src/utils/session-title.ts +87 -0
  191. package/src/utils/settings.ts +182 -0
  192. package/src/utils/simplified-errors.ts +410 -0
  193. package/src/utils/sync-utils.ts +159 -0
  194. package/src/utils/target-config.ts +570 -0
  195. package/src/utils/target-utils.ts +394 -0
  196. package/src/utils/template-engine.ts +94 -0
  197. package/src/utils/test-audio.ts +71 -0
  198. package/src/utils/todo-context.ts +46 -0
  199. package/src/utils/token-counter.ts +288 -0
  200. package/dist/index.d.ts +0 -10
  201. package/dist/index.js +0 -59554
  202. package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
  203. package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
  204. package/dist/shared/chunk-25dwp0dp.js +0 -89
  205. package/dist/shared/chunk-3pjb6063.js +0 -208
  206. package/dist/shared/chunk-4d6ydpw7.js +0 -2854
  207. package/dist/shared/chunk-4wjcadjk.js +0 -225
  208. package/dist/shared/chunk-5j4w74t6.js +0 -30
  209. package/dist/shared/chunk-5j8m3dh3.js +0 -58
  210. package/dist/shared/chunk-5thh3qem.js +0 -91
  211. package/dist/shared/chunk-6g9xy73m.js +0 -252
  212. package/dist/shared/chunk-7eq34c42.js +0 -23
  213. package/dist/shared/chunk-c2gwgx3r.js +0 -115
  214. package/dist/shared/chunk-cjd3mk4c.js +0 -1320
  215. package/dist/shared/chunk-g5cv6703.js +0 -368
  216. package/dist/shared/chunk-hpkhykhq.js +0 -574
  217. package/dist/shared/chunk-m2322pdk.js +0 -122
  218. package/dist/shared/chunk-nd5fdvaq.js +0 -26
  219. package/dist/shared/chunk-pgd3m6zf.js +0 -108
  220. package/dist/shared/chunk-qk8n91hw.js +0 -494
  221. package/dist/shared/chunk-rkkn8szp.js +0 -16855
  222. package/dist/shared/chunk-t16rfxh0.js +0 -61
  223. package/dist/shared/chunk-t4fbfa5v.js +0 -19
  224. package/dist/shared/chunk-t77h86w6.js +0 -276
  225. package/dist/shared/chunk-v0ez4aef.js +0 -71
  226. package/dist/shared/chunk-v29j2r3s.js +0 -32051
  227. package/dist/shared/chunk-vfbc6ew5.js +0 -765
  228. package/dist/shared/chunk-vmeqwm1c.js +0 -204
  229. package/dist/shared/chunk-x66eh37x.js +0 -137
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Session Title Generation Utility
3
+ * Re-exports pure functions from feature and adds streaming functionality
4
+ */
5
+
6
+ import { createAIStream } from '../core/ai-sdk.js';
7
+ import type { ProviderId } from '../types/config.types.js';
8
+
9
+ // Re-export pure functions from feature
10
+ export {
11
+ generateSessionTitle,
12
+ formatSessionDisplay,
13
+ formatRelativeTime,
14
+ cleanTitle,
15
+ truncateTitle,
16
+ } from '../features/session/utils/title.js';
17
+
18
+ /**
19
+ * Generate a session title using LLM with streaming
20
+ */
21
+ export async function generateSessionTitleWithStreaming(
22
+ firstMessage: string,
23
+ provider: ProviderId,
24
+ modelName: string,
25
+ providerConfig: any,
26
+ onChunk: (chunk: string) => void
27
+ ): Promise<string> {
28
+ if (!firstMessage || firstMessage.trim().length === 0) {
29
+ return 'New Chat';
30
+ }
31
+
32
+ try {
33
+ // Get the provider instance and create the model
34
+ const { getProvider } = await import('../providers/index.js');
35
+ const providerInstance = getProvider(provider);
36
+ const model = providerInstance.createClient(providerConfig, modelName);
37
+
38
+ const streamGenerator = createAIStream({
39
+ model,
40
+ messages: [
41
+ {
42
+ role: 'user',
43
+ content: `You need to generate a SHORT, DESCRIPTIVE title (maximum 50 characters) for a chat conversation.
44
+
45
+ User's first message: "${firstMessage}"
46
+
47
+ Requirements:
48
+ - Summarize the TOPIC or INTENT, don't just copy the message
49
+ - Be concise and descriptive
50
+ - Maximum 50 characters
51
+ - Output ONLY the title, nothing else
52
+
53
+ Examples:
54
+ - Message: "How do I implement authentication?" → Title: "Authentication Implementation"
55
+ - Message: "你好,请帮我修复这个 bug" → Title: "Bug 修复请求"
56
+ - Message: "Can you help me with React hooks?" → Title: "React Hooks Help"
57
+
58
+ Now generate the title:`,
59
+ },
60
+ ],
61
+ });
62
+
63
+ let fullTitle = '';
64
+
65
+ // Iterate the async generator and stream to UI
66
+ for await (const chunk of streamGenerator) {
67
+ if (chunk.type === 'text-delta' && chunk.textDelta) {
68
+ fullTitle += chunk.textDelta;
69
+ onChunk(chunk.textDelta);
70
+ }
71
+ }
72
+
73
+ // Clean up title
74
+ let cleaned = fullTitle.trim();
75
+ cleaned = cleaned.replace(/^["'「『]+|["'」』]+$/g, ''); // Remove quotes
76
+ cleaned = cleaned.replace(/^(Title:|标题:)\s*/i, ''); // Remove "Title:" prefix
77
+ cleaned = cleaned.replace(/\n+/g, ' '); // Replace newlines with spaces
78
+ cleaned = cleaned.trim();
79
+
80
+ // Return truncated if needed
81
+ return cleaned.length > 50 ? cleaned.substring(0, 50) + '...' : cleaned;
82
+ } catch (error) {
83
+ // Fallback to simple title generation on any error
84
+ return generateSessionTitle(firstMessage);
85
+ }
86
+ }
87
+
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Project settings manager - functional implementation
3
+ * Pure functions for managing uncommitted project-specific settings
4
+ */
5
+
6
+ import fs from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import { type Result, success, tryCatchAsync } from '../core/functional/result.js';
9
+
10
+ export interface ProjectSettings {
11
+ /** Default target for the project */
12
+ defaultTarget?: string;
13
+ /** Settings version for migration purposes */
14
+ version?: string;
15
+ }
16
+
17
+ const SETTINGS_FILE = '.sylphx-flow/settings.json';
18
+ const CURRENT_VERSION = '1.0.0';
19
+
20
+ /**
21
+ * Get settings file path for a given working directory
22
+ */
23
+ export const getSettingsPath = (cwd: string = process.cwd()): string =>
24
+ path.join(cwd, SETTINGS_FILE);
25
+
26
+ /**
27
+ * Check if settings file exists
28
+ */
29
+ export const settingsExists = async (cwd: string = process.cwd()): Promise<boolean> => {
30
+ try {
31
+ await fs.access(getSettingsPath(cwd));
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Load project settings from file
40
+ * Returns Result type for explicit error handling
41
+ */
42
+ export const loadSettings = async (
43
+ cwd: string = process.cwd()
44
+ ): Promise<Result<ProjectSettings, Error>> => {
45
+ const settingsPath = getSettingsPath(cwd);
46
+
47
+ return tryCatchAsync(
48
+ async () => {
49
+ const content = await fs.readFile(settingsPath, 'utf8');
50
+ return JSON.parse(content) as ProjectSettings;
51
+ },
52
+ (error: any) => {
53
+ // File not found is not an error - return empty settings
54
+ if (error.code === 'ENOENT') {
55
+ return new Error('EMPTY_SETTINGS');
56
+ }
57
+ return new Error(`Failed to load settings: ${error.message}`);
58
+ }
59
+ ).then((result) => {
60
+ // Convert EMPTY_SETTINGS error to success with empty object
61
+ if (result._tag === 'Failure' && result.error.message === 'EMPTY_SETTINGS') {
62
+ return success({});
63
+ }
64
+ return result;
65
+ });
66
+ };
67
+
68
+ /**
69
+ * Save project settings to file
70
+ * Returns Result type for explicit error handling
71
+ */
72
+ export const saveSettings = async (
73
+ settings: ProjectSettings,
74
+ cwd: string = process.cwd()
75
+ ): Promise<Result<void, Error>> => {
76
+ const settingsPath = getSettingsPath(cwd);
77
+
78
+ return tryCatchAsync(
79
+ async () => {
80
+ // Ensure the directory exists
81
+ await fs.mkdir(path.dirname(settingsPath), { recursive: true });
82
+
83
+ // Add current version if not present
84
+ const settingsWithVersion = {
85
+ ...settings,
86
+ version: settings.version || CURRENT_VERSION,
87
+ };
88
+
89
+ // Write settings with proper formatting
90
+ await fs.writeFile(settingsPath, `${JSON.stringify(settingsWithVersion, null, 2)}\n`, 'utf8');
91
+ },
92
+ (error: any) => new Error(`Failed to save settings: ${error.message}`)
93
+ );
94
+ };
95
+
96
+ /**
97
+ * Update specific settings properties
98
+ */
99
+ export const updateSettings = async (
100
+ updates: Partial<ProjectSettings>,
101
+ cwd: string = process.cwd()
102
+ ): Promise<Result<void, Error>> => {
103
+ const currentResult = await loadSettings(cwd);
104
+
105
+ if (currentResult._tag === 'Failure') {
106
+ return currentResult;
107
+ }
108
+
109
+ const newSettings = { ...currentResult.value, ...updates };
110
+ return saveSettings(newSettings, cwd);
111
+ };
112
+
113
+ /**
114
+ * Get the default target from settings
115
+ */
116
+ export const getDefaultTarget = async (
117
+ cwd: string = process.cwd()
118
+ ): Promise<string | undefined> => {
119
+ const result = await loadSettings(cwd);
120
+ return result._tag === 'Success' ? result.value.defaultTarget : undefined;
121
+ };
122
+
123
+ /**
124
+ * Set the default target in settings
125
+ */
126
+ export const setDefaultTarget = async (
127
+ target: string,
128
+ cwd: string = process.cwd()
129
+ ): Promise<Result<void, Error>> => updateSettings({ defaultTarget: target }, cwd);
130
+
131
+ /**
132
+ * Legacy class-based interface for backward compatibility
133
+ * @deprecated Use functional exports instead (loadSettings, saveSettings, etc.)
134
+ */
135
+ export class ProjectSettings {
136
+ constructor(private cwd: string = process.cwd()) {
137
+ this.settingsPath = getSettingsPath(cwd);
138
+ }
139
+
140
+ async load(): Promise<ProjectSettings> {
141
+ const result = await loadSettings(this.cwd);
142
+ if (result._tag === 'Failure') {
143
+ throw result.error;
144
+ }
145
+ return result.value;
146
+ }
147
+
148
+ async save(settings: ProjectSettings): Promise<void> {
149
+ const result = await saveSettings(settings, this.cwd);
150
+ if (result._tag === 'Failure') {
151
+ throw result.error;
152
+ }
153
+ }
154
+
155
+ async update(updates: Partial<ProjectSettings>): Promise<void> {
156
+ const result = await updateSettings(updates, this.cwd);
157
+ if (result._tag === 'Failure') {
158
+ throw result.error;
159
+ }
160
+ }
161
+
162
+ async getDefaultTarget(): Promise<string | undefined> {
163
+ return getDefaultTarget(this.cwd);
164
+ }
165
+
166
+ async setDefaultTarget(target: string): Promise<void> {
167
+ const result = await setDefaultTarget(target, this.cwd);
168
+ if (result._tag === 'Failure') {
169
+ throw result.error;
170
+ }
171
+ }
172
+
173
+ async exists(): Promise<boolean> {
174
+ return settingsExists(this.cwd);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Singleton instance for backward compatibility
180
+ * @deprecated Use functional exports with explicit cwd parameter
181
+ */
182
+ export const projectSettings = new ProjectSettings();
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Simplified Error Handling System
3
+ * Reduces complexity from 11+ error types to 3 core types
4
+ */
5
+
6
+ import { logger } from './logger.js';
7
+
8
+ /**
9
+ * Error severity levels
10
+ */
11
+ export enum ErrorSeverity {
12
+ LOW = 'LOW',
13
+ MEDIUM = 'MEDIUM',
14
+ HIGH = 'HIGH',
15
+ CRITICAL = 'CRITICAL',
16
+ }
17
+
18
+ /**
19
+ * Error categories for classification
20
+ */
21
+ export enum ErrorCategory {
22
+ VALIDATION = 'VALIDATION',
23
+ CONFIGURATION = 'CONFIGURATION',
24
+ DATABASE = 'DATABASE',
25
+ NETWORK = 'NETWORK',
26
+ FILESYSTEM = 'FILESYSTEM',
27
+ AUTHENTICATION = 'AUTHENTICATION',
28
+ RUNTIME = 'RUNTIME',
29
+ EXTERNAL = 'EXTERNAL',
30
+ INTERNAL = 'INTERNAL',
31
+ }
32
+
33
+ /**
34
+ * Core error interface
35
+ */
36
+ export interface ErrorInfo {
37
+ code: string;
38
+ message: string;
39
+ category: ErrorCategory;
40
+ severity: ErrorSeverity;
41
+ context?: Record<string, unknown>;
42
+ cause?: Error;
43
+ timestamp: string;
44
+ id: string;
45
+ }
46
+
47
+ /**
48
+ * Simplified Error Class - The main error type for most use cases
49
+ */
50
+ export class AppError extends Error implements ErrorInfo {
51
+ public readonly code: string;
52
+ public readonly category: ErrorCategory;
53
+ public readonly severity: ErrorSeverity;
54
+ public readonly context?: Record<string, unknown>;
55
+ public readonly timestamp: string;
56
+ public readonly id: string;
57
+
58
+ constructor(
59
+ message: string,
60
+ code: string,
61
+ category: ErrorCategory = ErrorCategory.RUNTIME,
62
+ severity: ErrorSeverity = ErrorSeverity.MEDIUM,
63
+ context?: Record<string, unknown>,
64
+ cause?: Error
65
+ ) {
66
+ super(message, { cause });
67
+ this.name = 'AppError';
68
+ this.code = code;
69
+ this.category = category;
70
+ this.severity = severity;
71
+ this.context = context;
72
+ this.timestamp = new Date().toISOString();
73
+ this.id = this.generateErrorId();
74
+
75
+ // Log the error
76
+ this.logError();
77
+ }
78
+
79
+ private generateErrorId(): string {
80
+ return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
81
+ }
82
+
83
+ private logError(): void {
84
+ const logLevel = this.getLogLevel();
85
+ logger[logLevel]('AppError occurred', {
86
+ id: this.id,
87
+ code: this.code,
88
+ message: this.message,
89
+ category: this.category,
90
+ severity: this.severity,
91
+ context: this.context,
92
+ stack: this.stack,
93
+ });
94
+ }
95
+
96
+ private getLogLevel(): 'error' | 'warn' | 'info' | 'debug' {
97
+ switch (this.severity) {
98
+ case ErrorSeverity.CRITICAL:
99
+ case ErrorSeverity.HIGH:
100
+ return 'error';
101
+ case ErrorSeverity.MEDIUM:
102
+ return 'warn';
103
+ case ErrorSeverity.LOW:
104
+ return 'info';
105
+ default:
106
+ return 'debug';
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Convert to JSON for serialization
112
+ */
113
+ toJSON(): ErrorInfo {
114
+ return {
115
+ code: this.code,
116
+ message: this.message,
117
+ category: this.category,
118
+ severity: this.severity,
119
+ context: this.context,
120
+ cause: this.cause?.message,
121
+ timestamp: this.timestamp,
122
+ id: this.id,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Get user-friendly message
128
+ */
129
+ getUserMessage(): string {
130
+ // For validation errors, be more specific
131
+ if (this.category === ErrorCategory.VALIDATION) {
132
+ return `Validation failed: ${this.message}`;
133
+ }
134
+
135
+ // For configuration errors, provide actionable guidance
136
+ if (this.category === ErrorCategory.CONFIGURATION) {
137
+ return `Configuration error: ${this.message}. Please check your settings.`;
138
+ }
139
+
140
+ // For database errors, be more generic
141
+ if (this.category === ErrorCategory.DATABASE) {
142
+ return 'Database operation failed. Please try again later.';
143
+ }
144
+
145
+ // For network/external errors
146
+ if (this.category === ErrorCategory.NETWORK || this.category === ErrorCategory.EXTERNAL) {
147
+ return 'External service unavailable. Please try again later.';
148
+ }
149
+
150
+ // Default message
151
+ return this.message;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Validation Error - For input validation failures
157
+ */
158
+ export class ValidationError extends AppError {
159
+ constructor(message: string, field?: string, value?: unknown) {
160
+ const context = field ? { field, value } : undefined;
161
+ super(message, 'VALIDATION_ERROR', ErrorCategory.VALIDATION, ErrorSeverity.LOW, context);
162
+ this.name = 'ValidationError';
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Configuration Error - For configuration issues
168
+ */
169
+ export class ConfigurationError extends AppError {
170
+ constructor(message: string, configKey?: string) {
171
+ const context = configKey ? { configKey } : undefined;
172
+ super(message, 'CONFIG_ERROR', ErrorCategory.CONFIGURATION, ErrorSeverity.HIGH, context);
173
+ this.name = 'ConfigurationError';
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Database Error - For database operation failures
179
+ */
180
+ export class DatabaseError extends AppError {
181
+ constructor(message: string, operation?: string, query?: string) {
182
+ const context = operation ? { operation, query } : undefined;
183
+ super(message, 'DATABASE_ERROR', ErrorCategory.DATABASE, ErrorSeverity.HIGH, context);
184
+ this.name = 'DatabaseError';
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Network Error - For network-related failures
190
+ */
191
+ export class NetworkError extends AppError {
192
+ constructor(message: string, url?: string, statusCode?: number) {
193
+ const context = url ? { url, statusCode } : undefined;
194
+ super(message, 'NETWORK_ERROR', ErrorCategory.NETWORK, ErrorSeverity.MEDIUM, context);
195
+ this.name = 'NetworkError';
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Filesystem Error - For file system operations
201
+ */
202
+ export class FilesystemError extends AppError {
203
+ constructor(message: string, path?: string, operation?: string) {
204
+ const context = path ? { path, operation } : undefined;
205
+ super(message, 'FILESYSTEM_ERROR', ErrorCategory.FILESYSTEM, ErrorSeverity.MEDIUM, context);
206
+ this.name = 'FilesystemError';
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Authentication Error - For auth failures
212
+ */
213
+ export class AuthenticationError extends AppError {
214
+ constructor(message = 'Authentication failed') {
215
+ super(message, 'AUTH_ERROR', ErrorCategory.AUTHENTICATION, ErrorSeverity.HIGH);
216
+ this.name = 'AuthenticationError';
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Error factory for convenient error creation
222
+ */
223
+ export class ErrorFactory {
224
+ /**
225
+ * Create validation error
226
+ */
227
+ static validation(message: string, field?: string, value?: unknown): ValidationError {
228
+ return new ValidationError(message, field, value);
229
+ }
230
+
231
+ /**
232
+ * Create configuration error
233
+ */
234
+ static configuration(message: string, configKey?: string): ConfigurationError {
235
+ return new ConfigurationError(message, configKey);
236
+ }
237
+
238
+ /**
239
+ * Create database error
240
+ */
241
+ static database(message: string, operation?: string, query?: string): DatabaseError {
242
+ return new DatabaseError(message, operation, query);
243
+ }
244
+
245
+ /**
246
+ * Create network error
247
+ */
248
+ static network(message: string, url?: string, statusCode?: number): NetworkError {
249
+ return new NetworkError(message, url, statusCode);
250
+ }
251
+
252
+ /**
253
+ * Create filesystem error
254
+ */
255
+ static filesystem(message: string, path?: string, operation?: string): FilesystemError {
256
+ return new FilesystemError(message, path, operation);
257
+ }
258
+
259
+ /**
260
+ * Create authentication error
261
+ */
262
+ static authentication(message?: string): AuthenticationError {
263
+ return new AuthenticationError(message);
264
+ }
265
+
266
+ /**
267
+ * Create generic app error
268
+ */
269
+ static app(
270
+ message: string,
271
+ code: string,
272
+ category: ErrorCategory = ErrorCategory.RUNTIME,
273
+ severity: ErrorSeverity = ErrorSeverity.MEDIUM,
274
+ context?: Record<string, unknown>
275
+ ): AppError {
276
+ return new AppError(message, code, category, severity, context);
277
+ }
278
+
279
+ /**
280
+ * Create error from unknown error type
281
+ */
282
+ static fromUnknown(error: unknown, defaultMessage = 'Unknown error occurred'): AppError {
283
+ if (error instanceof AppError) {
284
+ return error;
285
+ }
286
+
287
+ if (error instanceof Error) {
288
+ return new AppError(
289
+ error.message,
290
+ 'UNKNOWN_ERROR',
291
+ ErrorCategory.INTERNAL,
292
+ ErrorSeverity.MEDIUM,
293
+ { originalError: error.name },
294
+ error
295
+ );
296
+ }
297
+
298
+ if (typeof error === 'string') {
299
+ return new AppError(error, 'STRING_ERROR', ErrorCategory.INTERNAL, ErrorSeverity.LOW);
300
+ }
301
+
302
+ return new AppError(
303
+ defaultMessage,
304
+ 'UNKNOWN_ERROR',
305
+ ErrorCategory.INTERNAL,
306
+ ErrorSeverity.MEDIUM,
307
+ { originalError: error }
308
+ );
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Error handler utility class
314
+ */
315
+ export class ErrorHandler {
316
+ /**
317
+ * Handle error and convert to user-friendly response
318
+ */
319
+ static handle(error: unknown): { message: string; code: string; severity: ErrorSeverity } {
320
+ const appError = ErrorFactory.fromUnknown(error);
321
+
322
+ return {
323
+ message: appError.getUserMessage(),
324
+ code: appError.code,
325
+ severity: appError.severity,
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Execute operation with error handling
331
+ */
332
+ static async execute<T>(
333
+ operation: () => Promise<T>,
334
+ errorContext?: Record<string, unknown>
335
+ ): Promise<{ success: true; data: T } | { success: false; error: AppError }> {
336
+ try {
337
+ const data = await operation();
338
+ return { success: true, data };
339
+ } catch (error) {
340
+ const appError = ErrorFactory.fromUnknown(error);
341
+
342
+ // Add context if provided
343
+ if (errorContext) {
344
+ appError.context = { ...appError.context, ...errorContext };
345
+ }
346
+
347
+ return { success: false, error: appError };
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Check if error is recoverable
353
+ */
354
+ static isRecoverable(error: Error): boolean {
355
+ if (error instanceof AppError) {
356
+ return error.severity !== ErrorSeverity.CRITICAL;
357
+ }
358
+ return true; // Assume unknown errors are recoverable
359
+ }
360
+
361
+ /**
362
+ * Get retry delay for error
363
+ */
364
+ static getRetryDelay(error: Error, attempt: number): number {
365
+ if (error instanceof AppError) {
366
+ switch (error.category) {
367
+ case ErrorCategory.NETWORK:
368
+ case ErrorCategory.EXTERNAL:
369
+ return Math.min(1000 * 2 ** attempt, 30000); // Exponential backoff
370
+ case ErrorCategory.DATABASE:
371
+ return Math.min(500 * 2 ** attempt, 5000);
372
+ default:
373
+ return 1000;
374
+ }
375
+ }
376
+ return 1000;
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Convenience functions
382
+ */
383
+ export const createValidationError = (message: string, field?: string, value?: unknown) =>
384
+ ErrorFactory.validation(message, field, value);
385
+
386
+ export const createConfigurationError = (message: string, configKey?: string) =>
387
+ ErrorFactory.configuration(message, configKey);
388
+
389
+ export const createDatabaseError = (message: string, operation?: string, query?: string) =>
390
+ ErrorFactory.database(message, operation, query);
391
+
392
+ export const createNetworkError = (message: string, url?: string, statusCode?: number) =>
393
+ ErrorFactory.network(message, url, statusCode);
394
+
395
+ export const createFilesystemError = (message: string, path?: string, operation?: string) =>
396
+ ErrorFactory.filesystem(message, path, operation);
397
+
398
+ export const createAuthenticationError = (message?: string) => ErrorFactory.authentication(message);
399
+
400
+ export const createError = (
401
+ message: string,
402
+ code: string,
403
+ category: ErrorCategory = ErrorCategory.RUNTIME,
404
+ severity: ErrorSeverity = ErrorSeverity.MEDIUM,
405
+ context?: Record<string, unknown>
406
+ ) => ErrorFactory.app(message, code, category, severity, context);
407
+
408
+ // Legacy exports for backward compatibility
409
+ export { AppError as BaseError };
410
+ export type { ErrorInfo };