@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.
- package/CHANGELOG.md +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +389 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- 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
|
+
};
|