@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,313 @@
1
+ /**
2
+ * Async/Promise utilities for functional programming
3
+ * Handle promises with Result types
4
+ *
5
+ * DESIGN RATIONALE:
6
+ * - Convert Promise rejections to Result types
7
+ * - Composable async operations
8
+ * - No unhandled promise rejections
9
+ * - Type-safe async error handling
10
+ */
11
+
12
+ import type { AppError } from './error-types.js';
13
+ import { toAppError } from './error-types.js';
14
+ import type { Result } from './result.js';
15
+ import { failure, isSuccess, success } from './result.js';
16
+
17
+ /**
18
+ * Async Result type
19
+ */
20
+ export type AsyncResult<T, E = AppError> = Promise<Result<T, E>>;
21
+
22
+ /**
23
+ * Convert Promise to AsyncResult
24
+ * Catches rejections and converts to Result
25
+ */
26
+ export const fromPromise = async <T>(
27
+ promise: Promise<T>,
28
+ onError?: (error: unknown) => AppError
29
+ ): AsyncResult<T> => {
30
+ try {
31
+ const value = await promise;
32
+ return success(value);
33
+ } catch (error) {
34
+ return failure(onError ? onError(error) : toAppError(error));
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Map over async result
40
+ */
41
+ export const mapAsync =
42
+ <T, U>(fn: (value: T) => U | Promise<U>) =>
43
+ async <E>(result: AsyncResult<T, E>): AsyncResult<U, E> => {
44
+ const resolved = await result;
45
+ if (isSuccess(resolved)) {
46
+ const mapped = await fn(resolved.value);
47
+ return success(mapped);
48
+ }
49
+ return resolved;
50
+ };
51
+
52
+ /**
53
+ * FlatMap over async result
54
+ */
55
+ export const flatMapAsync =
56
+ <T, U, E>(fn: (value: T) => AsyncResult<U, E>) =>
57
+ async (result: AsyncResult<T, E>): AsyncResult<U, E> => {
58
+ const resolved = await result;
59
+ if (isSuccess(resolved)) {
60
+ return fn(resolved.value);
61
+ }
62
+ return resolved;
63
+ };
64
+
65
+ /**
66
+ * Run async operation with timeout
67
+ */
68
+ export const withTimeout = <T>(
69
+ promise: Promise<T>,
70
+ timeoutMs: number,
71
+ onTimeout?: () => AppError
72
+ ): AsyncResult<T> => {
73
+ return fromPromise(
74
+ Promise.race([
75
+ promise,
76
+ new Promise<T>((_, reject) =>
77
+ setTimeout(() => reject(new Error('Operation timed out')), timeoutMs)
78
+ ),
79
+ ]),
80
+ onTimeout
81
+ );
82
+ };
83
+
84
+ /**
85
+ * Retry async operation
86
+ */
87
+ export const retry = async <T>(
88
+ fn: () => Promise<T>,
89
+ options: {
90
+ maxAttempts: number;
91
+ delayMs?: number;
92
+ backoff?: number;
93
+ onRetry?: (attempt: number, error: AppError) => void;
94
+ }
95
+ ): AsyncResult<T> => {
96
+ const { maxAttempts, delayMs = 1000, backoff = 2, onRetry } = options;
97
+
98
+ let lastError: AppError | null = null;
99
+ let currentDelay = delayMs;
100
+
101
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
102
+ const result = await fromPromise(fn());
103
+
104
+ if (isSuccess(result)) {
105
+ return result;
106
+ }
107
+
108
+ lastError = result.error;
109
+
110
+ if (attempt < maxAttempts) {
111
+ if (onRetry) {
112
+ onRetry(attempt, lastError);
113
+ }
114
+
115
+ await new Promise((resolve) => setTimeout(resolve, currentDelay));
116
+ currentDelay *= backoff;
117
+ }
118
+ }
119
+
120
+ return failure(lastError!);
121
+ };
122
+
123
+ /**
124
+ * Run promises in parallel and collect results
125
+ * Fails if any promise fails
126
+ */
127
+ export const allAsync = async <T>(promises: AsyncResult<T, AppError>[]): AsyncResult<T[]> => {
128
+ const results = await Promise.all(promises);
129
+
130
+ const values: T[] = [];
131
+ for (const result of results) {
132
+ if (isSuccess(result)) {
133
+ values.push(result.value);
134
+ } else {
135
+ return result;
136
+ }
137
+ }
138
+
139
+ return success(values);
140
+ };
141
+
142
+ /**
143
+ * Run promises in parallel and collect all results
144
+ * Returns both successes and failures
145
+ */
146
+ export const allSettledAsync = async <T>(
147
+ promises: AsyncResult<T, AppError>[]
148
+ ): Promise<Result<T, AppError>[]> => {
149
+ return Promise.all(promises);
150
+ };
151
+
152
+ /**
153
+ * Run promises sequentially
154
+ */
155
+ export const sequenceAsync = async <T>(
156
+ promises: Array<() => AsyncResult<T, AppError>>
157
+ ): AsyncResult<T[]> => {
158
+ const values: T[] = [];
159
+
160
+ for (const promiseFn of promises) {
161
+ const result = await promiseFn();
162
+ if (isSuccess(result)) {
163
+ values.push(result.value);
164
+ } else {
165
+ return result;
166
+ }
167
+ }
168
+
169
+ return success(values);
170
+ };
171
+
172
+ /**
173
+ * Race promises - return first to complete
174
+ */
175
+ export const raceAsync = async <T>(promises: AsyncResult<T, AppError>[]): AsyncResult<T> => {
176
+ return Promise.race(promises);
177
+ };
178
+
179
+ /**
180
+ * Delay execution
181
+ */
182
+ export const delay = (ms: number): Promise<void> => {
183
+ return new Promise((resolve) => setTimeout(resolve, ms));
184
+ };
185
+
186
+ /**
187
+ * Run with concurrency limit
188
+ */
189
+ export const withConcurrency = async <T>(
190
+ tasks: Array<() => AsyncResult<T, AppError>>,
191
+ concurrency: number
192
+ ): AsyncResult<T[]> => {
193
+ const results: T[] = [];
194
+ const executing: Promise<void>[] = [];
195
+
196
+ for (const task of tasks) {
197
+ const promise = (async () => {
198
+ const result = await task();
199
+ if (isSuccess(result)) {
200
+ results.push(result.value);
201
+ } else {
202
+ throw result.error;
203
+ }
204
+ })();
205
+
206
+ executing.push(promise);
207
+
208
+ if (executing.length >= concurrency) {
209
+ await Promise.race(executing);
210
+ executing.splice(executing.indexOf(promise), 1);
211
+ }
212
+ }
213
+
214
+ try {
215
+ await Promise.all(executing);
216
+ return success(results);
217
+ } catch (error) {
218
+ return failure(toAppError(error));
219
+ }
220
+ };
221
+
222
+ /**
223
+ * Memoize async function
224
+ */
225
+ export const memoizeAsync = <Args extends any[], T>(
226
+ fn: (...args: Args) => AsyncResult<T, AppError>,
227
+ keyFn?: (...args: Args) => string
228
+ ): ((...args: Args) => AsyncResult<T, AppError>) => {
229
+ const cache = new Map<string, AsyncResult<T, AppError>>();
230
+
231
+ return (...args: Args): AsyncResult<T, AppError> => {
232
+ const key = keyFn ? keyFn(...args) : JSON.stringify(args);
233
+
234
+ if (cache.has(key)) {
235
+ return cache.get(key)!;
236
+ }
237
+
238
+ const result = fn(...args);
239
+ cache.set(key, result);
240
+ return result;
241
+ };
242
+ };
243
+
244
+ /**
245
+ * Debounce async function
246
+ */
247
+ export const debounceAsync = <Args extends any[], T>(
248
+ fn: (...args: Args) => AsyncResult<T, AppError>,
249
+ delayMs: number
250
+ ): ((...args: Args) => AsyncResult<T, AppError>) => {
251
+ let timeoutId: NodeJS.Timeout | null = null;
252
+ let latestArgs: Args | null = null;
253
+ let latestPromise: AsyncResult<T, AppError> | null = null;
254
+
255
+ return (...args: Args): AsyncResult<T, AppError> => {
256
+ latestArgs = args;
257
+
258
+ if (timeoutId) {
259
+ clearTimeout(timeoutId);
260
+ }
261
+
262
+ if (!latestPromise) {
263
+ latestPromise = new Promise((resolve) => {
264
+ timeoutId = setTimeout(async () => {
265
+ if (latestArgs) {
266
+ const result = await fn(...latestArgs);
267
+ resolve(result);
268
+ latestPromise = null;
269
+ timeoutId = null;
270
+ }
271
+ }, delayMs);
272
+ }) as AsyncResult<T, AppError>;
273
+ }
274
+
275
+ return latestPromise;
276
+ };
277
+ };
278
+
279
+ /**
280
+ * Throttle async function
281
+ */
282
+ export const throttleAsync = <Args extends any[], T>(
283
+ fn: (...args: Args) => AsyncResult<T, AppError>,
284
+ limitMs: number
285
+ ): ((...args: Args) => AsyncResult<T, AppError>) => {
286
+ let lastRun = 0;
287
+ let pending: AsyncResult<T, AppError> | null = null;
288
+
289
+ return (...args: Args): AsyncResult<T, AppError> => {
290
+ const now = Date.now();
291
+
292
+ if (now - lastRun >= limitMs) {
293
+ lastRun = now;
294
+ return fn(...args);
295
+ }
296
+
297
+ if (!pending) {
298
+ pending = new Promise((resolve) => {
299
+ setTimeout(
300
+ async () => {
301
+ lastRun = Date.now();
302
+ const result = await fn(...args);
303
+ pending = null;
304
+ resolve(result);
305
+ },
306
+ limitMs - (now - lastRun)
307
+ );
308
+ }) as AsyncResult<T, AppError>;
309
+ }
310
+
311
+ return pending;
312
+ };
313
+ };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Either type for representing values that can be one of two types
3
+ * Commonly used for success/error, but more general than Result
4
+ *
5
+ * DESIGN RATIONALE:
6
+ * - Generic sum type for two possibilities
7
+ * - Left traditionally represents error/failure
8
+ * - Right traditionally represents success/value
9
+ * - Result is a specialized Either<Error, Value>
10
+ */
11
+
12
+ export type Either<L, R> = Left<L> | Right<R>;
13
+
14
+ export interface Left<L> {
15
+ readonly _tag: 'Left';
16
+ readonly left: L;
17
+ }
18
+
19
+ export interface Right<R> {
20
+ readonly _tag: 'Right';
21
+ readonly right: R;
22
+ }
23
+
24
+ /**
25
+ * Constructors
26
+ */
27
+
28
+ export const left = <L>(value: L): Left<L> => ({
29
+ _tag: 'Left',
30
+ left: value,
31
+ });
32
+
33
+ export const right = <R>(value: R): Right<R> => ({
34
+ _tag: 'Right',
35
+ right: value,
36
+ });
37
+
38
+ /**
39
+ * Type guards
40
+ */
41
+
42
+ export const isLeft = <L, R>(either: Either<L, R>): either is Left<L> => either._tag === 'Left';
43
+
44
+ export const isRight = <L, R>(either: Either<L, R>): either is Right<R> => either._tag === 'Right';
45
+
46
+ /**
47
+ * Transformations
48
+ */
49
+
50
+ export const map =
51
+ <L, R, R2>(fn: (value: R) => R2) =>
52
+ (either: Either<L, R>): Either<L, R2> => {
53
+ if (isRight(either)) {
54
+ return right(fn(either.right));
55
+ }
56
+ return either;
57
+ };
58
+
59
+ export const mapLeft =
60
+ <L, L2, R>(fn: (value: L) => L2) =>
61
+ (either: Either<L, R>): Either<L2, R> => {
62
+ if (isLeft(either)) {
63
+ return left(fn(either.left));
64
+ }
65
+ return either;
66
+ };
67
+
68
+ export const flatMap =
69
+ <L, R, R2>(fn: (value: R) => Either<L, R2>) =>
70
+ (either: Either<L, R>): Either<L, R2> => {
71
+ if (isRight(either)) {
72
+ return fn(either.right);
73
+ }
74
+ return either;
75
+ };
76
+
77
+ /**
78
+ * Pattern matching
79
+ */
80
+ export const match =
81
+ <L, R, T>(onLeft: (left: L) => T, onRight: (right: R) => T) =>
82
+ (either: Either<L, R>): T => {
83
+ if (isLeft(either)) {
84
+ return onLeft(either.left);
85
+ }
86
+ return onRight(either.right);
87
+ };
88
+
89
+ /**
90
+ * Extract value or provide default
91
+ */
92
+ export const getOrElse =
93
+ <R>(defaultValue: R) =>
94
+ <L>(either: Either<L, R>): R => {
95
+ if (isRight(either)) {
96
+ return either.right;
97
+ }
98
+ return defaultValue;
99
+ };
100
+
101
+ /**
102
+ * Swap Left and Right
103
+ */
104
+ export const swap = <L, R>(either: Either<L, R>): Either<R, L> => {
105
+ if (isLeft(either)) {
106
+ return right(either.left);
107
+ }
108
+ return left(either.right);
109
+ };
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Functional error handling utilities
3
+ * Replaces exception-based error handling with Result-based approach
4
+ *
5
+ * DESIGN RATIONALE:
6
+ * - Errors as values, not exceptions
7
+ * - Explicit error handling in function signatures
8
+ * - Composable error handling
9
+ * - Separation of error handling from business logic
10
+ */
11
+
12
+ import type { AppError } from './error-types.js';
13
+ import { formatError, toAppError } from './error-types.js';
14
+ import type { Result } from './result.js';
15
+ import { failure, success } from './result.js';
16
+
17
+ /**
18
+ * Execute a function and convert exceptions to Result
19
+ */
20
+ export const execute = <T>(fn: () => T): Result<T, AppError> => {
21
+ try {
22
+ return success(fn());
23
+ } catch (error) {
24
+ return failure(toAppError(error));
25
+ }
26
+ };
27
+
28
+ /**
29
+ * Execute an async function and convert exceptions to Result
30
+ */
31
+ export const executeAsync = async <T>(fn: () => Promise<T>): Promise<Result<T, AppError>> => {
32
+ try {
33
+ const value = await fn();
34
+ return success(value);
35
+ } catch (error) {
36
+ return failure(toAppError(error));
37
+ }
38
+ };
39
+
40
+ /**
41
+ * Log error to console (side effect)
42
+ */
43
+ export const logError = (error: AppError): void => {
44
+ console.error(formatError(error));
45
+ };
46
+
47
+ /**
48
+ * Log error and exit process (side effect)
49
+ * Only use at top-level command handlers
50
+ */
51
+ export const exitWithError = (error: AppError, exitCode = 1): never => {
52
+ logError(error);
53
+ process.exit(exitCode);
54
+ };
55
+
56
+ /**
57
+ * Convert Result to exit code
58
+ * 0 for success, 1 for failure
59
+ * Use at top-level command handlers
60
+ */
61
+ export const toExitCode = <T>(result: Result<T, AppError>): number => {
62
+ if (result._tag === 'Success') {
63
+ return 0;
64
+ }
65
+ logError(result.error);
66
+ return 1;
67
+ };
68
+
69
+ /**
70
+ * Retry logic with exponential backoff
71
+ * Retries a function that returns a Result
72
+ */
73
+ export const retry = async <T>(
74
+ fn: () => Promise<Result<T, AppError>>,
75
+ options: {
76
+ maxRetries: number;
77
+ delayMs: number;
78
+ backoff?: number;
79
+ onRetry?: (error: AppError, attempt: number) => void;
80
+ }
81
+ ): Promise<Result<T, AppError>> => {
82
+ const { maxRetries, delayMs, backoff = 2, onRetry } = options;
83
+
84
+ let lastError: AppError | null = null;
85
+ let currentDelay = delayMs;
86
+
87
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
88
+ const result = await fn();
89
+
90
+ if (result._tag === 'Success') {
91
+ return result;
92
+ }
93
+
94
+ lastError = result.error;
95
+
96
+ if (attempt < maxRetries) {
97
+ if (onRetry) {
98
+ onRetry(lastError, attempt + 1);
99
+ }
100
+
101
+ await new Promise((resolve) => setTimeout(resolve, currentDelay));
102
+ currentDelay *= backoff;
103
+ }
104
+ }
105
+
106
+ return failure(lastError!);
107
+ };
108
+
109
+ /**
110
+ * Create an async handler that wraps a function returning Result
111
+ * Useful for command handlers
112
+ */
113
+ export const createAsyncHandler =
114
+ <T extends Record<string, any>>(handler: (options: T) => Promise<Result<void, AppError>>) =>
115
+ async (options: T): Promise<void> => {
116
+ const result = await handler(options);
117
+
118
+ if (result._tag === 'Failure') {
119
+ exitWithError(result.error);
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Create a sync handler that wraps a function returning Result
125
+ * Useful for command handlers
126
+ */
127
+ export const createSyncHandler =
128
+ <T extends Record<string, any>>(handler: (options: T) => Result<void, AppError>) =>
129
+ (options: T): void => {
130
+ const result = handler(options);
131
+
132
+ if (result._tag === 'Failure') {
133
+ exitWithError(result.error);
134
+ }
135
+ };