@kb-labs/shared 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/.vscode/settings.json +23 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/docs/DECLARATIVE-FLAGS-AND-ENV.md +622 -0
- package/docs/DOCUMENTATION.md +70 -0
- package/docs/adr/0000-template.md +52 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +31 -0
- package/docs/adr/0002-plugins-and-extensibility.md +44 -0
- package/docs/adr/0003-package-and-module-boundaries.md +35 -0
- package/docs/adr/0004-versioning-and-release-policy.md +36 -0
- package/docs/adr/0005-reactive-loader-pattern.md +179 -0
- package/docs/adr/0006-declarative-flags-and-env-systems.md +376 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +88 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/shared-cli-ui/CHANGELOG.md +20 -0
- package/packages/shared-cli-ui/README.md +342 -0
- package/packages/shared-cli-ui/docs/ARCHITECTURE.md +105 -0
- package/packages/shared-cli-ui/eslint.config.js +27 -0
- package/packages/shared-cli-ui/package.json +72 -0
- package/packages/shared-cli-ui/src/__tests__/artifacts-display.spec.ts +89 -0
- package/packages/shared-cli-ui/src/__tests__/format.spec.ts +44 -0
- package/packages/shared-cli-ui/src/__tests__/loader-json-mode.test.ts +119 -0
- package/packages/shared-cli-ui/src/artifacts-display.ts +266 -0
- package/packages/shared-cli-ui/src/cli-auto-discovery.ts +120 -0
- package/packages/shared-cli-ui/src/colors.ts +142 -0
- package/packages/shared-cli-ui/src/command-discovery.ts +72 -0
- package/packages/shared-cli-ui/src/command-output.ts +153 -0
- package/packages/shared-cli-ui/src/command-result.ts +267 -0
- package/packages/shared-cli-ui/src/command-runner.ts +310 -0
- package/packages/shared-cli-ui/src/command-suggestions.ts +204 -0
- package/packages/shared-cli-ui/src/debug/components/output.ts +141 -0
- package/packages/shared-cli-ui/src/debug/components/trace.ts +101 -0
- package/packages/shared-cli-ui/src/debug/components/tree.ts +88 -0
- package/packages/shared-cli-ui/src/debug/formatters/ai.ts +17 -0
- package/packages/shared-cli-ui/src/debug/formatters/human.ts +98 -0
- package/packages/shared-cli-ui/src/debug/formatters/timeline.ts +94 -0
- package/packages/shared-cli-ui/src/debug/index.ts +56 -0
- package/packages/shared-cli-ui/src/debug/types.ts +57 -0
- package/packages/shared-cli-ui/src/debug/utilities.ts +203 -0
- package/packages/shared-cli-ui/src/dynamic-command-discovery.ts +131 -0
- package/packages/shared-cli-ui/src/format.ts +412 -0
- package/packages/shared-cli-ui/src/index.ts +34 -0
- package/packages/shared-cli-ui/src/loader.ts +196 -0
- package/packages/shared-cli-ui/src/manifest-parser.ts +151 -0
- package/packages/shared-cli-ui/src/modern-format.ts +271 -0
- package/packages/shared-cli-ui/src/multi-cli-suggestions.ts +159 -0
- package/packages/shared-cli-ui/src/table.ts +134 -0
- package/packages/shared-cli-ui/src/timing-tracker.ts +68 -0
- package/packages/shared-cli-ui/src/utils/context.ts +12 -0
- package/packages/shared-cli-ui/src/utils/env.ts +164 -0
- package/packages/shared-cli-ui/src/utils/flags.ts +269 -0
- package/packages/shared-cli-ui/src/utils/path.ts +8 -0
- package/packages/shared-cli-ui/tsconfig.build.json +15 -0
- package/packages/shared-cli-ui/tsconfig.json +9 -0
- package/packages/shared-cli-ui/tsup.config.ts +11 -0
- package/packages/shared-cli-ui/vitest.config.ts +15 -0
- package/packages/shared-command-kit/CHANGELOG.md +20 -0
- package/packages/shared-command-kit/LICENSE +22 -0
- package/packages/shared-command-kit/README.md +1030 -0
- package/packages/shared-command-kit/docs/HIGH-LEVEL-API.md +89 -0
- package/packages/shared-command-kit/docs/LOW-LEVEL-API.md +105 -0
- package/packages/shared-command-kit/docs/MIGRATION-GUIDE.md +135 -0
- package/packages/shared-command-kit/eslint.config.js +27 -0
- package/packages/shared-command-kit/eslint.config.ts +14 -0
- package/packages/shared-command-kit/package.json +76 -0
- package/packages/shared-command-kit/prettierrc.json +5 -0
- package/packages/shared-command-kit/src/__tests__/define-command.spec.ts +294 -0
- package/packages/shared-command-kit/src/__tests__/define-route.test.ts +285 -0
- package/packages/shared-command-kit/src/__tests__/define-system-command.spec.ts +508 -0
- package/packages/shared-command-kit/src/__tests__/define-webhook.test.ts +156 -0
- package/packages/shared-command-kit/src/__tests__/define-websocket.test.ts +316 -0
- package/packages/shared-command-kit/src/__tests__/errors.spec.ts +45 -0
- package/packages/shared-command-kit/src/__tests__/flags.spec.ts +353 -0
- package/packages/shared-command-kit/src/__tests__/platform-api.test.ts +135 -0
- package/packages/shared-command-kit/src/__tests__/plugin-context-v3.snapshot.spec.ts +240 -0
- package/packages/shared-command-kit/src/__tests__/ws-types.test.ts +359 -0
- package/packages/shared-command-kit/src/analytics/index.ts +6 -0
- package/packages/shared-command-kit/src/analytics/with-analytics.ts +195 -0
- package/packages/shared-command-kit/src/define-action.ts +100 -0
- package/packages/shared-command-kit/src/define-command.ts +113 -0
- package/packages/shared-command-kit/src/define-route.ts +113 -0
- package/packages/shared-command-kit/src/define-system-command.ts +362 -0
- package/packages/shared-command-kit/src/define-webhook.ts +115 -0
- package/packages/shared-command-kit/src/define-websocket.ts +308 -0
- package/packages/shared-command-kit/src/errors/factory.ts +282 -0
- package/packages/shared-command-kit/src/errors/format-validation.ts +144 -0
- package/packages/shared-command-kit/src/errors/format.ts +92 -0
- package/packages/shared-command-kit/src/errors/index.ts +9 -0
- package/packages/shared-command-kit/src/errors/types.ts +32 -0
- package/packages/shared-command-kit/src/flags/define.ts +92 -0
- package/packages/shared-command-kit/src/flags/index.ts +9 -0
- package/packages/shared-command-kit/src/flags/types.ts +153 -0
- package/packages/shared-command-kit/src/flags/validate.ts +358 -0
- package/packages/shared-command-kit/src/helpers/context.ts +8 -0
- package/packages/shared-command-kit/src/helpers/flags.ts +84 -0
- package/packages/shared-command-kit/src/helpers/index.ts +42 -0
- package/packages/shared-command-kit/src/helpers/patterns.ts +464 -0
- package/packages/shared-command-kit/src/helpers/platform.ts +335 -0
- package/packages/shared-command-kit/src/helpers/use-analytics.ts +95 -0
- package/packages/shared-command-kit/src/helpers/use-cache.ts +97 -0
- package/packages/shared-command-kit/src/helpers/use-config.ts +99 -0
- package/packages/shared-command-kit/src/helpers/use-embeddings.ts +49 -0
- package/packages/shared-command-kit/src/helpers/use-llm.ts +316 -0
- package/packages/shared-command-kit/src/helpers/use-logger.ts +77 -0
- package/packages/shared-command-kit/src/helpers/use-platform.ts +111 -0
- package/packages/shared-command-kit/src/helpers/use-resource-broker.ts +106 -0
- package/packages/shared-command-kit/src/helpers/use-storage.ts +71 -0
- package/packages/shared-command-kit/src/helpers/use-vector-store.ts +49 -0
- package/packages/shared-command-kit/src/helpers/validation.ts +398 -0
- package/packages/shared-command-kit/src/index.ts +410 -0
- package/packages/shared-command-kit/src/jobs.ts +132 -0
- package/packages/shared-command-kit/src/lifecycle/define-handlers.ts +366 -0
- package/packages/shared-command-kit/src/lifecycle/index.ts +6 -0
- package/packages/shared-command-kit/src/manifest.ts +127 -0
- package/packages/shared-command-kit/src/rest/define-handler.ts +187 -0
- package/packages/shared-command-kit/src/rest/index.ts +11 -0
- package/packages/shared-command-kit/src/studio/index.ts +12 -0
- package/packages/shared-command-kit/src/validation/index.ts +6 -0
- package/packages/shared-command-kit/src/validation/schema-builders.ts +409 -0
- package/packages/shared-command-kit/src/ws-types.ts +106 -0
- package/packages/shared-command-kit/tsconfig.build.json +15 -0
- package/packages/shared-command-kit/tsconfig.json +9 -0
- package/packages/shared-command-kit/tsup.config.ts +30 -0
- package/packages/shared-command-kit/vitest.config.ts +4 -0
- package/packages/shared-http/package.json +67 -0
- package/packages/shared-http/src/__tests__/log-correlation.test.ts +81 -0
- package/packages/shared-http/src/__tests__/operation-metrics-tracker.test.ts +55 -0
- package/packages/shared-http/src/http-observability-collector.ts +363 -0
- package/packages/shared-http/src/index.ts +36 -0
- package/packages/shared-http/src/log-correlation.ts +89 -0
- package/packages/shared-http/src/operation-metrics-tracker.ts +107 -0
- package/packages/shared-http/src/register-openapi.ts +108 -0
- package/packages/shared-http/src/resolve-schema-ref.ts +75 -0
- package/packages/shared-http/src/schemas.ts +29 -0
- package/packages/shared-http/src/service-observability.ts +63 -0
- package/packages/shared-http/tsconfig.build.json +15 -0
- package/packages/shared-http/tsconfig.json +9 -0
- package/packages/shared-http/tsup.config.ts +23 -0
- package/packages/shared-http/vitest.config.ts +13 -0
- package/packages/shared-perm-presets/CHANGELOG.md +20 -0
- package/packages/shared-perm-presets/README.md +78 -0
- package/packages/shared-perm-presets/eslint.config.js +27 -0
- package/packages/shared-perm-presets/package.json +45 -0
- package/packages/shared-perm-presets/src/__tests__/combine.test.ts +403 -0
- package/packages/shared-perm-presets/src/__tests__/presets.test.ts +205 -0
- package/packages/shared-perm-presets/src/combine.ts +278 -0
- package/packages/shared-perm-presets/src/index.ts +18 -0
- package/packages/shared-perm-presets/src/presets/ci-environment.ts +34 -0
- package/packages/shared-perm-presets/src/presets/full-env.ts +16 -0
- package/packages/shared-perm-presets/src/presets/git-workflow.ts +40 -0
- package/packages/shared-perm-presets/src/presets/index.ts +8 -0
- package/packages/shared-perm-presets/src/presets/kb-platform.ts +30 -0
- package/packages/shared-perm-presets/src/presets/llm-access.ts +29 -0
- package/packages/shared-perm-presets/src/presets/minimal.ts +21 -0
- package/packages/shared-perm-presets/src/presets/npm-publish.ts +48 -0
- package/packages/shared-perm-presets/src/presets/vector-store.ts +40 -0
- package/packages/shared-perm-presets/src/types.ts +192 -0
- package/packages/shared-perm-presets/tsconfig.build.json +15 -0
- package/packages/shared-perm-presets/tsconfig.json +9 -0
- package/packages/shared-perm-presets/tsup.config.ts +8 -0
- package/packages/shared-perm-presets/vitest.config.ts +9 -0
- package/packages/shared-testing/CHANGELOG.md +20 -0
- package/packages/shared-testing/README.md +430 -0
- package/packages/shared-testing/package.json +51 -0
- package/packages/shared-testing/src/__tests__/create-test-context.test.ts +199 -0
- package/packages/shared-testing/src/__tests__/mock-cache.test.ts +174 -0
- package/packages/shared-testing/src/__tests__/mock-llm.test.ts +212 -0
- package/packages/shared-testing/src/__tests__/setup-platform.test.ts +90 -0
- package/packages/shared-testing/src/__tests__/test-command.test.ts +557 -0
- package/packages/shared-testing/src/create-test-context.ts +550 -0
- package/packages/shared-testing/src/index.ts +77 -0
- package/packages/shared-testing/src/mock-cache.ts +179 -0
- package/packages/shared-testing/src/mock-llm.ts +319 -0
- package/packages/shared-testing/src/mock-logger.ts +97 -0
- package/packages/shared-testing/src/mock-storage.ts +108 -0
- package/packages/shared-testing/src/setup-platform.ts +101 -0
- package/packages/shared-testing/src/test-command.ts +288 -0
- package/packages/shared-testing/tsconfig.build.json +15 -0
- package/packages/shared-testing/tsconfig.json +9 -0
- package/packages/shared-testing/tsup.config.ts +20 -0
- package/packages/shared-testing/vitest.config.ts +3 -0
- package/packages/shared-tool-kit/CHANGELOG.md +20 -0
- package/packages/shared-tool-kit/package.json +47 -0
- package/packages/shared-tool-kit/src/__tests__/factory.test.ts +103 -0
- package/packages/shared-tool-kit/src/__tests__/mock-tool.test.ts +95 -0
- package/packages/shared-tool-kit/src/factory.ts +126 -0
- package/packages/shared-tool-kit/src/index.ts +32 -0
- package/packages/shared-tool-kit/src/testing/index.ts +84 -0
- package/packages/shared-tool-kit/tsconfig.build.json +15 -0
- package/packages/shared-tool-kit/tsconfig.json +9 -0
- package/packages/shared-tool-kit/tsup.config.ts +21 -0
- package/pnpm-workspace.yaml +11070 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/tsconfig.base.json +9 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +50 -0
- package/tsconfig.tools.json +18 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +104 -0
- package/vitest.config.ts +48 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/shared-command-kit/helpers/patterns
|
|
3
|
+
* Common patterns for plugin development (spinner, batch processing, retry logic).
|
|
4
|
+
*
|
|
5
|
+
* These patterns make it easy to build robust, user-friendly plugins with
|
|
6
|
+
* proper error handling and progress feedback.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PluginContextV3 as PluginContext } from '@kb-labs/plugin-runtime';
|
|
10
|
+
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
// SPINNER PATTERN (Progress feedback)
|
|
13
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Execute function with progress feedback.
|
|
17
|
+
* Shows message while running, updates with success/failure.
|
|
18
|
+
*
|
|
19
|
+
* @param ctx - Plugin context
|
|
20
|
+
* @param message - Progress message (e.g., "Processing files")
|
|
21
|
+
* @param fn - Async function to execute
|
|
22
|
+
* @returns Function result
|
|
23
|
+
* @throws Re-throws any error from fn
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const result = await withSpinner(
|
|
28
|
+
* ctx,
|
|
29
|
+
* 'Indexing documents',
|
|
30
|
+
* async () => {
|
|
31
|
+
* // Long-running operation
|
|
32
|
+
* return await indexDocuments(docs);
|
|
33
|
+
* }
|
|
34
|
+
* );
|
|
35
|
+
* // Output: "Indexing documents... ✓" (or "✗" on error)
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export async function withSpinner<T>(
|
|
39
|
+
ctx: PluginContext,
|
|
40
|
+
message: string,
|
|
41
|
+
fn: () => Promise<T>
|
|
42
|
+
): Promise<T> {
|
|
43
|
+
ctx.ui?.info(`${message}...`);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const result = await fn();
|
|
47
|
+
ctx.ui?.success(`${message} ✓`);
|
|
48
|
+
return result;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
ctx.ui?.error(`${message} ✗`);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Execute multiple steps with progress feedback.
|
|
57
|
+
* Each step shows its own progress message.
|
|
58
|
+
*
|
|
59
|
+
* @param ctx - Plugin context
|
|
60
|
+
* @param steps - Array of steps with message and function
|
|
61
|
+
* @returns Array of step results
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const [docs, vectors, stored] = await withSteps(ctx, [
|
|
66
|
+
* { message: 'Loading documents', fn: () => loadDocuments() },
|
|
67
|
+
* { message: 'Generating embeddings', fn: () => generateEmbeddings(docs) },
|
|
68
|
+
* { message: 'Storing in database', fn: () => storeVectors(vectors) },
|
|
69
|
+
* ]);
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export async function withSteps<T extends any[]>(
|
|
73
|
+
ctx: PluginContext,
|
|
74
|
+
steps: Array<{ message: string; fn: () => Promise<any> }>
|
|
75
|
+
): Promise<T> {
|
|
76
|
+
const results: any[] = [];
|
|
77
|
+
|
|
78
|
+
for (const step of steps) {
|
|
79
|
+
const result = await withSpinner(ctx, step.message, step.fn);
|
|
80
|
+
results.push(result);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return results as T;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
87
|
+
// BATCH PROCESSING (Concurrency control)
|
|
88
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
89
|
+
|
|
90
|
+
export interface BatchOptions<T, R> {
|
|
91
|
+
/** Maximum concurrent operations (default: 5) */
|
|
92
|
+
concurrency?: number;
|
|
93
|
+
/** Progress callback (called after each item) */
|
|
94
|
+
onProgress?: (completed: number, total: number, item: T, result: R) => void;
|
|
95
|
+
/** Error callback (called on item failure, can decide to continue or throw) */
|
|
96
|
+
onError?: (error: Error, item: T) => void | Promise<void>;
|
|
97
|
+
/** Stop on first error (default: false) */
|
|
98
|
+
stopOnError?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Process array of items with concurrency control.
|
|
103
|
+
* Limits number of concurrent operations for better resource management.
|
|
104
|
+
*
|
|
105
|
+
* @param items - Array of items to process
|
|
106
|
+
* @param fn - Processing function
|
|
107
|
+
* @param options - Batch options
|
|
108
|
+
* @returns Array of results
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const results = await processBatch(
|
|
113
|
+
* files,
|
|
114
|
+
* async (file) => await processFile(file),
|
|
115
|
+
* {
|
|
116
|
+
* concurrency: 10,
|
|
117
|
+
* onProgress: (completed, total) => {
|
|
118
|
+
* ctx.ui.info(`Processed ${completed}/${total}`);
|
|
119
|
+
* },
|
|
120
|
+
* }
|
|
121
|
+
* );
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export async function processBatch<T, R>(
|
|
125
|
+
items: T[],
|
|
126
|
+
fn: (item: T, index: number) => Promise<R>,
|
|
127
|
+
options: BatchOptions<T, R> = {}
|
|
128
|
+
): Promise<R[]> {
|
|
129
|
+
const { concurrency = 5, onProgress, onError, stopOnError = false } = options;
|
|
130
|
+
|
|
131
|
+
const results: R[] = [];
|
|
132
|
+
const errors: Array<{ index: number; item: T; error: Error }> = [];
|
|
133
|
+
let completed = 0;
|
|
134
|
+
|
|
135
|
+
// Process in batches
|
|
136
|
+
for (let i = 0; i < items.length; i += concurrency) {
|
|
137
|
+
const batch = items.slice(i, i + concurrency);
|
|
138
|
+
const batchPromises = batch.map(async (item, batchIndex) => {
|
|
139
|
+
const index = i + batchIndex;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const result = await fn(item, index);
|
|
143
|
+
results[index] = result;
|
|
144
|
+
completed++;
|
|
145
|
+
|
|
146
|
+
if (onProgress) {
|
|
147
|
+
onProgress(completed, items.length, item, result);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
153
|
+
errors.push({ index, item, error: err });
|
|
154
|
+
|
|
155
|
+
if (onError) {
|
|
156
|
+
await onError(err, item);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (stopOnError) {
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Continue processing other items
|
|
164
|
+
return undefined as R;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await Promise.all(batchPromises);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (errors.length > 0 && stopOnError) {
|
|
172
|
+
throw new Error(`Batch processing failed: ${errors.length} error(s)`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Process items in batches with progress feedback in UI.
|
|
180
|
+
* Convenience wrapper combining processBatch with UI updates.
|
|
181
|
+
*
|
|
182
|
+
* @param ctx - Plugin context
|
|
183
|
+
* @param items - Array of items
|
|
184
|
+
* @param fn - Processing function
|
|
185
|
+
* @param options - Batch options (concurrency, etc.)
|
|
186
|
+
* @returns Array of results
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```typescript
|
|
190
|
+
* const results = await processBatchWithUI(
|
|
191
|
+
* ctx,
|
|
192
|
+
* files,
|
|
193
|
+
* async (file) => await processFile(file),
|
|
194
|
+
* { concurrency: 10 }
|
|
195
|
+
* );
|
|
196
|
+
* // Automatically shows: "Processing 1/100... 2/100..."
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export async function processBatchWithUI<T, R>(
|
|
200
|
+
ctx: PluginContext,
|
|
201
|
+
items: T[],
|
|
202
|
+
fn: (item: T, index: number) => Promise<R>,
|
|
203
|
+
options: Omit<BatchOptions<T, R>, 'onProgress'> = {}
|
|
204
|
+
): Promise<R[]> {
|
|
205
|
+
return processBatch(items, fn, {
|
|
206
|
+
...options,
|
|
207
|
+
onProgress: (completed, total) => {
|
|
208
|
+
ctx.ui.info(`Processing ${completed}/${total}...`);
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
214
|
+
// RETRY LOGIC (Error recovery)
|
|
215
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
216
|
+
|
|
217
|
+
export interface RetryOptions {
|
|
218
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
219
|
+
maxRetries?: number;
|
|
220
|
+
/** Initial delay in ms (default: 1000) */
|
|
221
|
+
delay?: number;
|
|
222
|
+
/** Backoff multiplier (default: 2 = exponential) */
|
|
223
|
+
backoff?: number;
|
|
224
|
+
/** Maximum delay in ms (default: 30000) */
|
|
225
|
+
maxDelay?: number;
|
|
226
|
+
/** Callback before each retry */
|
|
227
|
+
onRetry?: (attempt: number, error: Error) => void | Promise<void>;
|
|
228
|
+
/** Predicate to determine if error is retryable */
|
|
229
|
+
shouldRetry?: (error: Error) => boolean;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Retry function with exponential backoff.
|
|
234
|
+
* Useful for transient failures (network, rate limits, etc.).
|
|
235
|
+
*
|
|
236
|
+
* @param fn - Function to retry
|
|
237
|
+
* @param options - Retry options
|
|
238
|
+
* @returns Function result
|
|
239
|
+
* @throws Last error if all retries exhausted
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const data = await retryWithBackoff(
|
|
244
|
+
* async () => await fetchFromAPI(),
|
|
245
|
+
* {
|
|
246
|
+
* maxRetries: 5,
|
|
247
|
+
* delay: 1000,
|
|
248
|
+
* backoff: 2,
|
|
249
|
+
* onRetry: (attempt, error) => {
|
|
250
|
+
* console.log(`Retry ${attempt}: ${error.message}`);
|
|
251
|
+
* },
|
|
252
|
+
* }
|
|
253
|
+
* );
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
export async function retryWithBackoff<T>(
|
|
257
|
+
fn: () => Promise<T>,
|
|
258
|
+
options: RetryOptions = {}
|
|
259
|
+
): Promise<T> {
|
|
260
|
+
const {
|
|
261
|
+
maxRetries = 3,
|
|
262
|
+
delay = 1000,
|
|
263
|
+
backoff = 2,
|
|
264
|
+
maxDelay = 30000,
|
|
265
|
+
onRetry,
|
|
266
|
+
shouldRetry = () => true,
|
|
267
|
+
} = options;
|
|
268
|
+
|
|
269
|
+
let lastError: Error | undefined;
|
|
270
|
+
let currentDelay = delay;
|
|
271
|
+
|
|
272
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
273
|
+
try {
|
|
274
|
+
return await fn();
|
|
275
|
+
} catch (error) {
|
|
276
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
277
|
+
|
|
278
|
+
// Check if error is retryable
|
|
279
|
+
if (!shouldRetry(lastError)) {
|
|
280
|
+
throw lastError;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Last attempt - don't retry
|
|
284
|
+
if (attempt === maxRetries) {
|
|
285
|
+
throw lastError;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Callback before retry
|
|
289
|
+
if (onRetry) {
|
|
290
|
+
await onRetry(attempt + 1, lastError);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Wait before retry
|
|
294
|
+
await sleep(Math.min(currentDelay, maxDelay));
|
|
295
|
+
|
|
296
|
+
// Increase delay (exponential backoff)
|
|
297
|
+
currentDelay *= backoff;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
throw lastError instanceof Error ? lastError : new Error('Retry loop exhausted without error');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Retry with backoff and UI feedback.
|
|
306
|
+
* Shows retry attempts to user.
|
|
307
|
+
*
|
|
308
|
+
* @param ctx - Plugin context
|
|
309
|
+
* @param message - Operation description
|
|
310
|
+
* @param fn - Function to retry
|
|
311
|
+
* @param options - Retry options
|
|
312
|
+
* @returns Function result
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* const data = await retryWithUI(
|
|
317
|
+
* ctx,
|
|
318
|
+
* 'Fetching data from API',
|
|
319
|
+
* async () => await api.getData(),
|
|
320
|
+
* { maxRetries: 3 }
|
|
321
|
+
* );
|
|
322
|
+
* // Output: "Retrying (1/3)...", "Retrying (2/3)..."
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
export async function retryWithUI<T>(
|
|
326
|
+
ctx: PluginContext,
|
|
327
|
+
message: string,
|
|
328
|
+
fn: () => Promise<T>,
|
|
329
|
+
options: RetryOptions = {}
|
|
330
|
+
): Promise<T> {
|
|
331
|
+
return retryWithBackoff(fn, {
|
|
332
|
+
...options,
|
|
333
|
+
onRetry: async (attempt, error) => {
|
|
334
|
+
ctx.ui?.warn(`${message} failed: ${error.message}`);
|
|
335
|
+
ctx.ui?.info(`Retrying (${attempt}/${options.maxRetries ?? 3})...`);
|
|
336
|
+
|
|
337
|
+
if (options.onRetry) {
|
|
338
|
+
await options.onRetry(attempt, error);
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
345
|
+
// UTILITIES
|
|
346
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Sleep for specified duration.
|
|
350
|
+
*
|
|
351
|
+
* @param ms - Duration in milliseconds
|
|
352
|
+
*/
|
|
353
|
+
export function sleep(ms: number): Promise<void> {
|
|
354
|
+
return new Promise<void>((resolve) => { setTimeout(resolve, ms); });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Timeout wrapper - reject if function takes too long.
|
|
359
|
+
*
|
|
360
|
+
* @param fn - Function to execute
|
|
361
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
362
|
+
* @returns Function result or timeout error
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```typescript
|
|
366
|
+
* const result = await withTimeout(
|
|
367
|
+
* async () => await slowOperation(),
|
|
368
|
+
* 5000 // 5 seconds
|
|
369
|
+
* );
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
export async function withTimeout<T>(fn: () => Promise<T>, timeoutMs: number): Promise<T> {
|
|
373
|
+
return Promise.race([
|
|
374
|
+
fn(),
|
|
375
|
+
sleep(timeoutMs).then(() => {
|
|
376
|
+
throw new Error(`Operation timed out after ${timeoutMs}ms`);
|
|
377
|
+
}),
|
|
378
|
+
]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Debounce function - wait for calls to stop before executing.
|
|
383
|
+
* Useful for rate-limited APIs or expensive operations.
|
|
384
|
+
*
|
|
385
|
+
* @param fn - Function to debounce
|
|
386
|
+
* @param delayMs - Delay in milliseconds
|
|
387
|
+
* @returns Debounced function
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* ```typescript
|
|
391
|
+
* const debouncedSave = debounce(
|
|
392
|
+
* async (data) => await saveToDatabase(data),
|
|
393
|
+
* 1000
|
|
394
|
+
* );
|
|
395
|
+
*
|
|
396
|
+
* // Only last call will execute
|
|
397
|
+
* debouncedSave(data1);
|
|
398
|
+
* debouncedSave(data2);
|
|
399
|
+
* debouncedSave(data3); // Only this one executes
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
export function debounce<T extends (...args: any[]) => Promise<any>>(
|
|
403
|
+
fn: T,
|
|
404
|
+
delayMs: number
|
|
405
|
+
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
|
|
406
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
407
|
+
|
|
408
|
+
return (...args: Parameters<T>): Promise<ReturnType<T>> => {
|
|
409
|
+
return new Promise((resolve, reject) => {
|
|
410
|
+
if (timeoutId) {
|
|
411
|
+
clearTimeout(timeoutId);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
timeoutId = setTimeout(async () => {
|
|
415
|
+
try {
|
|
416
|
+
const result = await fn(...args);
|
|
417
|
+
resolve(result);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
reject(error);
|
|
420
|
+
}
|
|
421
|
+
}, delayMs);
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Throttle function - execute at most once per interval.
|
|
428
|
+
* Useful for rate-limited APIs.
|
|
429
|
+
*
|
|
430
|
+
* @param fn - Function to throttle
|
|
431
|
+
* @param intervalMs - Minimum interval between calls
|
|
432
|
+
* @returns Throttled function
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```typescript
|
|
436
|
+
* const throttledLog = throttle(
|
|
437
|
+
* async (message) => await sendToAPI(message),
|
|
438
|
+
* 5000
|
|
439
|
+
* );
|
|
440
|
+
*
|
|
441
|
+
* // Only executes once per 5 seconds
|
|
442
|
+
* throttledLog('msg1'); // Executes immediately
|
|
443
|
+
* throttledLog('msg2'); // Ignored (within 5s)
|
|
444
|
+
* // ... 5s later ...
|
|
445
|
+
* throttledLog('msg3'); // Executes
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
export function throttle<T extends (...args: any[]) => Promise<any>>(
|
|
449
|
+
fn: T,
|
|
450
|
+
intervalMs: number
|
|
451
|
+
): (...args: Parameters<T>) => Promise<ReturnType<T> | undefined> {
|
|
452
|
+
let lastCall = 0;
|
|
453
|
+
|
|
454
|
+
return async (...args: Parameters<T>): Promise<ReturnType<T> | undefined> => {
|
|
455
|
+
const now = Date.now();
|
|
456
|
+
|
|
457
|
+
if (now - lastCall >= intervalMs) {
|
|
458
|
+
lastCall = now;
|
|
459
|
+
return fn(...args);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return undefined;
|
|
463
|
+
};
|
|
464
|
+
}
|