@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,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Utilities - 類型安全工具
|
|
3
|
+
* Functional approach to type safety and validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
// Re-export unified Result type and utilities
|
|
9
|
+
export {
|
|
10
|
+
Result,
|
|
11
|
+
AsyncResult,
|
|
12
|
+
ok,
|
|
13
|
+
err,
|
|
14
|
+
isOk,
|
|
15
|
+
isErr,
|
|
16
|
+
map,
|
|
17
|
+
flatMap,
|
|
18
|
+
mapError,
|
|
19
|
+
getOrElse,
|
|
20
|
+
getOrElseLazy,
|
|
21
|
+
match,
|
|
22
|
+
unwrap,
|
|
23
|
+
tryCatch,
|
|
24
|
+
tryCatchAsync,
|
|
25
|
+
safeAsync,
|
|
26
|
+
safeSync,
|
|
27
|
+
all,
|
|
28
|
+
allAsync,
|
|
29
|
+
tap,
|
|
30
|
+
tapError,
|
|
31
|
+
type SuccessType,
|
|
32
|
+
type ErrorType,
|
|
33
|
+
type SafeResult,
|
|
34
|
+
} from './result.js';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Common validation schemas
|
|
38
|
+
*/
|
|
39
|
+
export const Schemas = {
|
|
40
|
+
/** Non-empty string validation */
|
|
41
|
+
nonEmptyString: z.string().min(1, 'String cannot be empty'),
|
|
42
|
+
|
|
43
|
+
/** Optional non-empty string */
|
|
44
|
+
optionalNonEmptyString: z.string().min(1).optional(),
|
|
45
|
+
|
|
46
|
+
/** Positive number validation */
|
|
47
|
+
positiveNumber: z.number().positive('Number must be positive'),
|
|
48
|
+
|
|
49
|
+
/** Optional positive number */
|
|
50
|
+
optionalPositiveNumber: z.number().positive().optional(),
|
|
51
|
+
|
|
52
|
+
/** Array validation */
|
|
53
|
+
nonEmptyArray: z.array(z.any()).min(1, 'Array cannot be empty'),
|
|
54
|
+
|
|
55
|
+
/** Email validation */
|
|
56
|
+
email: z.string().email('Invalid email format'),
|
|
57
|
+
|
|
58
|
+
/** URL validation */
|
|
59
|
+
url: z.string().url('Invalid URL format'),
|
|
60
|
+
|
|
61
|
+
/** Object validation */
|
|
62
|
+
object: z.object({}).passthrough(),
|
|
63
|
+
|
|
64
|
+
/** Storage configuration */
|
|
65
|
+
storageConfig: z.object({
|
|
66
|
+
type: z.enum(['memory', 'cache', 'vector', 'drizzle']),
|
|
67
|
+
connectionString: z.string().optional(),
|
|
68
|
+
defaultTTL: z.number().positive().optional(),
|
|
69
|
+
maxCacheSize: z.number().positive().optional(),
|
|
70
|
+
vectorDimensions: z.number().positive().optional(),
|
|
71
|
+
storageDir: z.string().optional(),
|
|
72
|
+
}),
|
|
73
|
+
|
|
74
|
+
/** CLI options */
|
|
75
|
+
cliOptions: z.object({
|
|
76
|
+
target: z.string().optional(),
|
|
77
|
+
verbose: z.boolean().default(false),
|
|
78
|
+
dryRun: z.boolean().default(false),
|
|
79
|
+
clear: z.boolean().default(false),
|
|
80
|
+
mcp: z.union([z.array(z.string()), z.boolean(), z.null()]).default(null),
|
|
81
|
+
quiet: z.boolean().default(false),
|
|
82
|
+
agent: z.string().optional(),
|
|
83
|
+
}),
|
|
84
|
+
} as const;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Type guards
|
|
88
|
+
*/
|
|
89
|
+
export const TypeGuards = {
|
|
90
|
+
/** Check if value is a non-empty string */
|
|
91
|
+
isNonEmptyString: (value: unknown): value is string => {
|
|
92
|
+
return typeof value === 'string' && value.length > 0;
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
/** Check if value is a positive number */
|
|
96
|
+
isPositiveNumber: (value: unknown): value is number => {
|
|
97
|
+
return typeof value === 'number' && value > 0;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/** Check if value is a non-empty array */
|
|
101
|
+
isNonEmptyArray: (value: unknown): value is unknown[] => {
|
|
102
|
+
return Array.isArray(value) && value.length > 0;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/** Check if value is an object */
|
|
106
|
+
isObject: (value: unknown): value is Record<string, unknown> => {
|
|
107
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/** Check if value is a function */
|
|
111
|
+
isFunction: (value: unknown): value is Function => {
|
|
112
|
+
return typeof value === 'function';
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/** Check if value is a Date */
|
|
116
|
+
isDate: (value: unknown): value is Date => {
|
|
117
|
+
return value instanceof Date && !isNaN(value.getTime());
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/** Check if value is a Buffer */
|
|
121
|
+
isBuffer: (value: unknown): value is Buffer => {
|
|
122
|
+
return Buffer.isBuffer(value);
|
|
123
|
+
},
|
|
124
|
+
} as const;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Safe parsing utilities
|
|
128
|
+
*/
|
|
129
|
+
export const SafeParse = {
|
|
130
|
+
/** Parse JSON safely */
|
|
131
|
+
json: <T = unknown>(str: string): Result<T> => {
|
|
132
|
+
return safeSync(() => JSON.parse(str) as T,
|
|
133
|
+
error => new Error(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`));
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/** Parse number safely */
|
|
137
|
+
number: (str: string, radix = 10): Result<number> => {
|
|
138
|
+
return safeSync(() => {
|
|
139
|
+
const num = parseInt(str, radix);
|
|
140
|
+
if (isNaN(num)) throw new Error(`Invalid number: ${str}`);
|
|
141
|
+
return num;
|
|
142
|
+
}, error => new Error(`Failed to parse number: ${error instanceof Error ? error.message : String(error)}`));
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/** Parse float safely */
|
|
146
|
+
float: (str: string): Result<number> => {
|
|
147
|
+
return safeSync(() => {
|
|
148
|
+
const num = parseFloat(str);
|
|
149
|
+
if (isNaN(num)) throw new Error(`Invalid float: ${str}`);
|
|
150
|
+
return num;
|
|
151
|
+
}, error => new Error(`Failed to parse float: ${error instanceof Error ? error.message : String(error)}`));
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/** Parse boolean safely */
|
|
155
|
+
boolean: (str: string): Result<boolean> => {
|
|
156
|
+
const lower = str.toLowerCase();
|
|
157
|
+
if (['true', '1', 'yes', 'on'].includes(lower)) return ok(true);
|
|
158
|
+
if (['false', '0', 'no', 'off'].includes(lower)) return ok(false);
|
|
159
|
+
return err(new Error(`Invalid boolean: ${str}`));
|
|
160
|
+
},
|
|
161
|
+
} as const;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* String utilities
|
|
165
|
+
*/
|
|
166
|
+
export const StringUtils = {
|
|
167
|
+
/** Truncate string to max length */
|
|
168
|
+
truncate: (str: string, maxLength: number, suffix = '...'): string => {
|
|
169
|
+
if (str.length <= maxLength) return str;
|
|
170
|
+
return str.slice(0, maxLength - suffix.length) + suffix;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/** Convert to kebab-case */
|
|
174
|
+
kebabCase: (str: string): string => {
|
|
175
|
+
return str
|
|
176
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
177
|
+
.replace(/[\s_]+/g, '-')
|
|
178
|
+
.toLowerCase();
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/** Convert to camelCase */
|
|
182
|
+
camelCase: (str: string): string => {
|
|
183
|
+
return str
|
|
184
|
+
.replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
|
|
185
|
+
.replace(/^[A-Z]/, char => char.toLowerCase());
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/** Convert to PascalCase */
|
|
189
|
+
pascalCase: (str: string): string => {
|
|
190
|
+
const camel = StringUtils.camelCase(str);
|
|
191
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
/** Capitalize first letter */
|
|
195
|
+
capitalize: (str: string): string => {
|
|
196
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/** Check if string is empty or whitespace */
|
|
200
|
+
isEmpty: (str: string): boolean => {
|
|
201
|
+
return str.trim().length === 0;
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/** Generate random string */
|
|
205
|
+
random: (length = 8): string => {
|
|
206
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
207
|
+
let result = '';
|
|
208
|
+
for (let i = 0; i < length; i++) {
|
|
209
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
/** Escape regex special characters */
|
|
215
|
+
escapeRegex: (str: string): string => {
|
|
216
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
217
|
+
},
|
|
218
|
+
} as const;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Array utilities
|
|
222
|
+
*/
|
|
223
|
+
export const ArrayUtils = {
|
|
224
|
+
/** Check if array is empty */
|
|
225
|
+
isEmpty: <T>(arr: T[]): boolean => arr.length === 0,
|
|
226
|
+
|
|
227
|
+
/** Remove duplicates */
|
|
228
|
+
unique: <T>(arr: T[]): T[] => [...new Set(arr)],
|
|
229
|
+
|
|
230
|
+
/** Group array by key */
|
|
231
|
+
groupBy: <T, K extends string | number>(
|
|
232
|
+
arr: T[],
|
|
233
|
+
keyFn: (item: T) => K
|
|
234
|
+
): Record<K, T[]> => {
|
|
235
|
+
return arr.reduce((groups, item) => {
|
|
236
|
+
const key = keyFn(item);
|
|
237
|
+
if (!groups[key]) groups[key] = [];
|
|
238
|
+
groups[key].push(item);
|
|
239
|
+
return groups;
|
|
240
|
+
}, {} as Record<K, T[]>);
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/** Chunk array into smaller arrays */
|
|
244
|
+
chunk: <T>(arr: T[], size: number): T[][] => {
|
|
245
|
+
const chunks: T[][] = [];
|
|
246
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
247
|
+
chunks.push(arr.slice(i, i + size));
|
|
248
|
+
}
|
|
249
|
+
return chunks;
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/** Flatten nested arrays */
|
|
253
|
+
flatten: <T>(arr: (T | T[])[]): T[] => {
|
|
254
|
+
return arr.reduce<T[]>((flat, item) => {
|
|
255
|
+
return flat.concat(Array.isArray(item) ? ArrayUtils.flatten(item) : item);
|
|
256
|
+
}, []);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
/** Pick random element */
|
|
260
|
+
sample: <T>(arr: T[]): T | undefined => {
|
|
261
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
/** Shuffle array */
|
|
265
|
+
shuffle: <T>(arr: T[]): T[] => {
|
|
266
|
+
const shuffled = [...arr];
|
|
267
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
268
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
269
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
270
|
+
}
|
|
271
|
+
return shuffled;
|
|
272
|
+
},
|
|
273
|
+
} as const;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Object utilities
|
|
277
|
+
*/
|
|
278
|
+
export const ObjectUtils = {
|
|
279
|
+
/** Check if object is empty */
|
|
280
|
+
isEmpty: (obj: Record<string, unknown>): boolean => {
|
|
281
|
+
return Object.keys(obj).length === 0;
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
/** Pick specific keys from object */
|
|
285
|
+
pick: <T extends Record<string, unknown>, K extends keyof T>(
|
|
286
|
+
obj: T,
|
|
287
|
+
keys: K[]
|
|
288
|
+
): Pick<T, K> => {
|
|
289
|
+
return keys.reduce((picked, key) => {
|
|
290
|
+
if (key in obj) {
|
|
291
|
+
picked[key] = obj[key];
|
|
292
|
+
}
|
|
293
|
+
return picked;
|
|
294
|
+
}, {} as Pick<T, K>);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/** Omit specific keys from object */
|
|
298
|
+
omit: <T extends Record<string, unknown>, K extends keyof T>(
|
|
299
|
+
obj: T,
|
|
300
|
+
keys: K[]
|
|
301
|
+
): Omit<T, K> => {
|
|
302
|
+
const result = { ...obj };
|
|
303
|
+
keys.forEach(key => delete result[key]);
|
|
304
|
+
return result as Omit<T, K>;
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/** Deep clone object */
|
|
308
|
+
deepClone: <T>(obj: T): T => {
|
|
309
|
+
return JSON.parse(JSON.stringify(obj));
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
/** Merge objects */
|
|
313
|
+
merge: <T extends Record<string, unknown>>(
|
|
314
|
+
...objects: Partial<T>[]
|
|
315
|
+
): T => {
|
|
316
|
+
return objects.reduce((merged, obj) => ({ ...merged, ...obj }), {} as T);
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
/** Get nested value from object */
|
|
320
|
+
get: (obj: any, path: string, defaultValue?: unknown): unknown => {
|
|
321
|
+
const keys = path.split('.');
|
|
322
|
+
let current = obj;
|
|
323
|
+
|
|
324
|
+
for (const key of keys) {
|
|
325
|
+
if (current == null || typeof current !== 'object') {
|
|
326
|
+
return defaultValue;
|
|
327
|
+
}
|
|
328
|
+
current = current[key];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return current !== undefined ? current : defaultValue;
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
/** Set nested value in object */
|
|
335
|
+
set: (obj: any, path: string, value: unknown): void => {
|
|
336
|
+
const keys = path.split('.');
|
|
337
|
+
let current = obj;
|
|
338
|
+
|
|
339
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
340
|
+
const key = keys[i];
|
|
341
|
+
if (!(key in current) || typeof current[key] !== 'object') {
|
|
342
|
+
current[key] = {};
|
|
343
|
+
}
|
|
344
|
+
current = current[key];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
current[keys[keys.length - 1]] = value;
|
|
348
|
+
},
|
|
349
|
+
} as const;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Function utilities
|
|
353
|
+
*/
|
|
354
|
+
export const FunctionUtils = {
|
|
355
|
+
/** Debounce function */
|
|
356
|
+
debounce: <T extends (...args: any[]) => any>(
|
|
357
|
+
fn: T,
|
|
358
|
+
delay: number
|
|
359
|
+
): ((...args: Parameters<T>) => void) => {
|
|
360
|
+
let timeoutId: NodeJS.Timeout;
|
|
361
|
+
return (...args: Parameters<T>) => {
|
|
362
|
+
clearTimeout(timeoutId);
|
|
363
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
/** Throttle function */
|
|
368
|
+
throttle: <T extends (...args: any[]) => any>(
|
|
369
|
+
fn: T,
|
|
370
|
+
delay: number
|
|
371
|
+
): ((...args: Parameters<T>) => void) => {
|
|
372
|
+
let lastCall = 0;
|
|
373
|
+
return (...args: Parameters<T>) => {
|
|
374
|
+
const now = Date.now();
|
|
375
|
+
if (now - lastCall >= delay) {
|
|
376
|
+
lastCall = now;
|
|
377
|
+
fn(...args);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
/** Memoize function */
|
|
383
|
+
memoize: <T extends (...args: any[]) => any>(
|
|
384
|
+
fn: T,
|
|
385
|
+
keyFn?: (...args: Parameters<T>) => string
|
|
386
|
+
): T => {
|
|
387
|
+
const cache = new Map<string, ReturnType<T>>();
|
|
388
|
+
|
|
389
|
+
return ((...args: Parameters<T>) => {
|
|
390
|
+
const key = keyFn ? keyFn(...args) : JSON.stringify(args);
|
|
391
|
+
|
|
392
|
+
if (cache.has(key)) {
|
|
393
|
+
return cache.get(key);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const result = fn(...args);
|
|
397
|
+
cache.set(key, result);
|
|
398
|
+
return result;
|
|
399
|
+
}) as T;
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
/** Retry function */
|
|
403
|
+
retry: async <T>(
|
|
404
|
+
fn: () => Promise<T>,
|
|
405
|
+
maxAttempts = 3,
|
|
406
|
+
delay = 1000
|
|
407
|
+
): Promise<T> => {
|
|
408
|
+
let lastError: Error;
|
|
409
|
+
|
|
410
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
411
|
+
try {
|
|
412
|
+
return await fn();
|
|
413
|
+
} catch (error) {
|
|
414
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
415
|
+
|
|
416
|
+
if (attempt === maxAttempts) {
|
|
417
|
+
throw lastError;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Exponential backoff
|
|
421
|
+
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt - 1)));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
throw lastError!;
|
|
426
|
+
},
|
|
427
|
+
} as const;
|