@revealui/mcp 0.1.0 → 0.1.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/LICENSE +22 -0
- package/LICENSE.commercial +111 -0
- package/README.md +3 -0
- package/dist/{packages/mcp/src/adapters → adapters}/db.d.ts +1 -1
- package/dist/adapters/db.d.ts.map +1 -0
- package/dist/adapters/db.js.map +1 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js.map +1 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js.map +1 -0
- package/dist/{packages/mcp/src/hypervisor.d.ts → hypervisor.d.ts} +56 -0
- package/dist/hypervisor.d.ts.map +1 -0
- package/dist/{packages/mcp/src/hypervisor.js → hypervisor.js} +209 -1
- package/dist/hypervisor.js.map +1 -0
- package/dist/{packages/mcp/src/index.d.ts → index.d.ts} +9 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/{packages/mcp/src/index.js → index.js} +8 -4
- package/dist/index.js.map +1 -0
- package/dist/{packages/mcp/src/servers → servers}/adapter.d.ts +11 -1
- package/dist/servers/adapter.d.ts.map +1 -0
- package/dist/{packages/mcp/src/servers → servers}/adapter.js +20 -4
- package/dist/servers/adapter.js.map +1 -0
- package/dist/servers/revealui-content.d.ts +21 -0
- package/dist/servers/revealui-content.d.ts.map +1 -0
- package/dist/servers/revealui-content.js +211 -0
- package/dist/servers/revealui-content.js.map +1 -0
- package/dist/servers/revealui-email.d.ts +18 -0
- package/dist/servers/revealui-email.d.ts.map +1 -0
- package/dist/servers/revealui-email.js +190 -0
- package/dist/servers/revealui-email.js.map +1 -0
- package/dist/servers/revealui-stripe.d.ts +19 -0
- package/dist/servers/revealui-stripe.d.ts.map +1 -0
- package/dist/servers/revealui-stripe.js +228 -0
- package/dist/servers/revealui-stripe.js.map +1 -0
- package/package.json +50 -11
- package/.env.example +0 -9
- package/MCP_MAINTENANCE.md +0 -265
- package/__tests__/crdt.integration.test.ts +0 -156
- package/configs/README.md +0 -77
- package/configs/claude-template.json +0 -54
- package/dist/packages/core/src/database/ssl-config.d.ts +0 -9
- package/dist/packages/core/src/database/ssl-config.d.ts.map +0 -1
- package/dist/packages/core/src/database/ssl-config.js +0 -8
- package/dist/packages/core/src/database/ssl-config.js.map +0 -1
- package/dist/packages/core/src/features.d.ts +0 -86
- package/dist/packages/core/src/features.d.ts.map +0 -1
- package/dist/packages/core/src/features.js +0 -93
- package/dist/packages/core/src/features.js.map +0 -1
- package/dist/packages/core/src/license.d.ts +0 -75
- package/dist/packages/core/src/license.d.ts.map +0 -1
- package/dist/packages/core/src/license.js +0 -174
- package/dist/packages/core/src/license.js.map +0 -1
- package/dist/packages/core/src/monitoring/alerts.d.ts +0 -118
- package/dist/packages/core/src/monitoring/alerts.d.ts.map +0 -1
- package/dist/packages/core/src/monitoring/alerts.js +0 -325
- package/dist/packages/core/src/monitoring/alerts.js.map +0 -1
- package/dist/packages/core/src/monitoring/cleanup-manager.d.ts +0 -71
- package/dist/packages/core/src/monitoring/cleanup-manager.d.ts.map +0 -1
- package/dist/packages/core/src/monitoring/cleanup-manager.js +0 -227
- package/dist/packages/core/src/monitoring/cleanup-manager.js.map +0 -1
- package/dist/packages/core/src/monitoring/health-monitor.d.ts +0 -22
- package/dist/packages/core/src/monitoring/health-monitor.d.ts.map +0 -1
- package/dist/packages/core/src/monitoring/health-monitor.js +0 -143
- package/dist/packages/core/src/monitoring/health-monitor.js.map +0 -1
- package/dist/packages/core/src/monitoring/index.d.ts +0 -14
- package/dist/packages/core/src/monitoring/index.d.ts.map +0 -1
- package/dist/packages/core/src/monitoring/index.js +0 -18
- package/dist/packages/core/src/monitoring/index.js.map +0 -1
- package/dist/packages/core/src/monitoring/process-registry.d.ts +0 -97
- package/dist/packages/core/src/monitoring/process-registry.d.ts.map +0 -1
- package/dist/packages/core/src/monitoring/process-registry.js +0 -223
- package/dist/packages/core/src/monitoring/process-registry.js.map +0 -1
- package/dist/packages/core/src/monitoring/types.d.ts +0 -231
- package/dist/packages/core/src/monitoring/types.d.ts.map +0 -1
- package/dist/packages/core/src/monitoring/types.js +0 -43
- package/dist/packages/core/src/monitoring/types.js.map +0 -1
- package/dist/packages/core/src/monitoring/zombie-detector.d.ts +0 -81
- package/dist/packages/core/src/monitoring/zombie-detector.d.ts.map +0 -1
- package/dist/packages/core/src/monitoring/zombie-detector.js +0 -232
- package/dist/packages/core/src/monitoring/zombie-detector.js.map +0 -1
- package/dist/packages/core/src/observability/logger.d.ts +0 -47
- package/dist/packages/core/src/observability/logger.d.ts.map +0 -1
- package/dist/packages/core/src/observability/logger.js +0 -141
- package/dist/packages/core/src/observability/logger.js.map +0 -1
- package/dist/packages/core/src/utils/logger-server.d.ts +0 -32
- package/dist/packages/core/src/utils/logger-server.d.ts.map +0 -1
- package/dist/packages/core/src/utils/logger-server.js +0 -69
- package/dist/packages/core/src/utils/logger-server.js.map +0 -1
- package/dist/packages/core/src/utils/request-context.d.ts +0 -143
- package/dist/packages/core/src/utils/request-context.d.ts.map +0 -1
- package/dist/packages/core/src/utils/request-context.js +0 -169
- package/dist/packages/core/src/utils/request-context.js.map +0 -1
- package/dist/packages/dev/src/code-validator/index.d.ts +0 -20
- package/dist/packages/dev/src/code-validator/index.d.ts.map +0 -1
- package/dist/packages/dev/src/code-validator/index.js +0 -20
- package/dist/packages/dev/src/code-validator/index.js.map +0 -1
- package/dist/packages/dev/src/code-validator/types.d.ts +0 -67
- package/dist/packages/dev/src/code-validator/types.d.ts.map +0 -1
- package/dist/packages/dev/src/code-validator/types.js +0 -7
- package/dist/packages/dev/src/code-validator/types.js.map +0 -1
- package/dist/packages/dev/src/code-validator/validator.d.ts +0 -48
- package/dist/packages/dev/src/code-validator/validator.d.ts.map +0 -1
- package/dist/packages/dev/src/code-validator/validator.js +0 -176
- package/dist/packages/dev/src/code-validator/validator.js.map +0 -1
- package/dist/packages/mcp/src/adapters/db.d.ts.map +0 -1
- package/dist/packages/mcp/src/adapters/db.js.map +0 -1
- package/dist/packages/mcp/src/config/index.d.ts.map +0 -1
- package/dist/packages/mcp/src/config/index.js.map +0 -1
- package/dist/packages/mcp/src/contracts.d.ts.map +0 -1
- package/dist/packages/mcp/src/contracts.js.map +0 -1
- package/dist/packages/mcp/src/hypervisor.d.ts.map +0 -1
- package/dist/packages/mcp/src/hypervisor.js.map +0 -1
- package/dist/packages/mcp/src/index.d.ts.map +0 -1
- package/dist/packages/mcp/src/index.js.map +0 -1
- package/dist/packages/mcp/src/servers/adapter.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/adapter.js.map +0 -1
- package/dist/packages/mcp/src/servers/code-validator.d.ts +0 -24
- package/dist/packages/mcp/src/servers/code-validator.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/code-validator.js +0 -156
- package/dist/packages/mcp/src/servers/code-validator.js.map +0 -1
- package/dist/packages/mcp/src/servers/neon.d.ts +0 -11
- package/dist/packages/mcp/src/servers/neon.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/neon.js +0 -90
- package/dist/packages/mcp/src/servers/neon.js.map +0 -1
- package/dist/packages/mcp/src/servers/next-devtools.d.ts +0 -11
- package/dist/packages/mcp/src/servers/next-devtools.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/next-devtools.js +0 -215
- package/dist/packages/mcp/src/servers/next-devtools.js.map +0 -1
- package/dist/packages/mcp/src/servers/playwright.d.ts +0 -11
- package/dist/packages/mcp/src/servers/playwright.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/playwright.js +0 -68
- package/dist/packages/mcp/src/servers/playwright.js.map +0 -1
- package/dist/packages/mcp/src/servers/stripe.d.ts +0 -11
- package/dist/packages/mcp/src/servers/stripe.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/stripe.js +0 -86
- package/dist/packages/mcp/src/servers/stripe.js.map +0 -1
- package/dist/packages/mcp/src/servers/supabase.d.ts +0 -11
- package/dist/packages/mcp/src/servers/supabase.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/supabase.js +0 -144
- package/dist/packages/mcp/src/servers/supabase.js.map +0 -1
- package/dist/packages/mcp/src/servers/vercel.d.ts +0 -11
- package/dist/packages/mcp/src/servers/vercel.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/vercel.js +0 -87
- package/dist/packages/mcp/src/servers/vercel.js.map +0 -1
- package/dist/packages/mcp/src/servers/vultr-test.d.ts +0 -3
- package/dist/packages/mcp/src/servers/vultr-test.d.ts.map +0 -1
- package/dist/packages/mcp/src/servers/vultr-test.js +0 -82
- package/dist/packages/mcp/src/servers/vultr-test.js.map +0 -1
- package/dist/scripts/lib/analyzers/console-analyzer.d.ts +0 -188
- package/dist/scripts/lib/analyzers/console-analyzer.d.ts.map +0 -1
- package/dist/scripts/lib/analyzers/console-analyzer.js +0 -432
- package/dist/scripts/lib/analyzers/console-analyzer.js.map +0 -1
- package/dist/scripts/lib/analyzers/index.d.ts +0 -11
- package/dist/scripts/lib/analyzers/index.d.ts.map +0 -1
- package/dist/scripts/lib/analyzers/index.js +0 -11
- package/dist/scripts/lib/analyzers/index.js.map +0 -1
- package/dist/scripts/lib/args.d.ts +0 -104
- package/dist/scripts/lib/args.d.ts.map +0 -1
- package/dist/scripts/lib/args.js +0 -304
- package/dist/scripts/lib/args.js.map +0 -1
- package/dist/scripts/lib/cache.d.ts +0 -185
- package/dist/scripts/lib/cache.d.ts.map +0 -1
- package/dist/scripts/lib/cache.js +0 -390
- package/dist/scripts/lib/cache.js.map +0 -1
- package/dist/scripts/lib/cli/dispatch.d.ts +0 -116
- package/dist/scripts/lib/cli/dispatch.d.ts.map +0 -1
- package/dist/scripts/lib/cli/dispatch.js +0 -206
- package/dist/scripts/lib/cli/dispatch.js.map +0 -1
- package/dist/scripts/lib/cli/index.d.ts +0 -10
- package/dist/scripts/lib/cli/index.d.ts.map +0 -1
- package/dist/scripts/lib/cli/index.js +0 -10
- package/dist/scripts/lib/cli/index.js.map +0 -1
- package/dist/scripts/lib/database/ssl-config.d.ts +0 -26
- package/dist/scripts/lib/database/ssl-config.d.ts.map +0 -1
- package/dist/scripts/lib/database/ssl-config.js +0 -47
- package/dist/scripts/lib/database/ssl-config.js.map +0 -1
- package/dist/scripts/lib/errors.d.ts +0 -218
- package/dist/scripts/lib/errors.d.ts.map +0 -1
- package/dist/scripts/lib/errors.js +0 -543
- package/dist/scripts/lib/errors.js.map +0 -1
- package/dist/scripts/lib/exec.d.ts +0 -107
- package/dist/scripts/lib/exec.d.ts.map +0 -1
- package/dist/scripts/lib/exec.js +0 -232
- package/dist/scripts/lib/exec.js.map +0 -1
- package/dist/scripts/lib/index.d.ts +0 -50
- package/dist/scripts/lib/index.d.ts.map +0 -1
- package/dist/scripts/lib/index.js +0 -65
- package/dist/scripts/lib/index.js.map +0 -1
- package/dist/scripts/lib/logger.d.ts +0 -50
- package/dist/scripts/lib/logger.d.ts.map +0 -1
- package/dist/scripts/lib/logger.js +0 -159
- package/dist/scripts/lib/logger.js.map +0 -1
- package/dist/scripts/lib/output.d.ts +0 -149
- package/dist/scripts/lib/output.d.ts.map +0 -1
- package/dist/scripts/lib/output.js +0 -263
- package/dist/scripts/lib/output.js.map +0 -1
- package/dist/scripts/lib/parallel.d.ts +0 -164
- package/dist/scripts/lib/parallel.d.ts.map +0 -1
- package/dist/scripts/lib/parallel.js +0 -355
- package/dist/scripts/lib/parallel.js.map +0 -1
- package/dist/scripts/lib/paths.d.ts +0 -92
- package/dist/scripts/lib/paths.d.ts.map +0 -1
- package/dist/scripts/lib/paths.js +0 -171
- package/dist/scripts/lib/paths.js.map +0 -1
- package/dist/scripts/lib/state/adapters/memory.d.ts +0 -42
- package/dist/scripts/lib/state/adapters/memory.d.ts.map +0 -1
- package/dist/scripts/lib/state/adapters/memory.js +0 -110
- package/dist/scripts/lib/state/adapters/memory.js.map +0 -1
- package/dist/scripts/lib/state/adapters/pglite.d.ts +0 -46
- package/dist/scripts/lib/state/adapters/pglite.d.ts.map +0 -1
- package/dist/scripts/lib/state/adapters/pglite.js +0 -256
- package/dist/scripts/lib/state/adapters/pglite.js.map +0 -1
- package/dist/scripts/lib/state/index.d.ts +0 -16
- package/dist/scripts/lib/state/index.d.ts.map +0 -1
- package/dist/scripts/lib/state/index.js +0 -16
- package/dist/scripts/lib/state/index.js.map +0 -1
- package/dist/scripts/lib/state/types.d.ts +0 -111
- package/dist/scripts/lib/state/types.d.ts.map +0 -1
- package/dist/scripts/lib/state/types.js +0 -8
- package/dist/scripts/lib/state/types.js.map +0 -1
- package/dist/scripts/lib/state/workflow-state.d.ts +0 -110
- package/dist/scripts/lib/state/workflow-state.d.ts.map +0 -1
- package/dist/scripts/lib/state/workflow-state.js +0 -331
- package/dist/scripts/lib/state/workflow-state.js.map +0 -1
- package/dist/scripts/lib/telemetry.d.ts +0 -194
- package/dist/scripts/lib/telemetry.d.ts.map +0 -1
- package/dist/scripts/lib/telemetry.js +0 -394
- package/dist/scripts/lib/telemetry.js.map +0 -1
- package/dist/scripts/lib/utils.d.ts +0 -270
- package/dist/scripts/lib/utils.d.ts.map +0 -1
- package/dist/scripts/lib/utils.js +0 -473
- package/dist/scripts/lib/utils.js.map +0 -1
- package/dist/scripts/lib/validation/database.d.ts +0 -83
- package/dist/scripts/lib/validation/database.d.ts.map +0 -1
- package/dist/scripts/lib/validation/database.js +0 -199
- package/dist/scripts/lib/validation/database.js.map +0 -1
- package/dist/scripts/lib/validation/env.d.ts +0 -80
- package/dist/scripts/lib/validation/env.d.ts.map +0 -1
- package/dist/scripts/lib/validation/env.js +0 -246
- package/dist/scripts/lib/validation/env.js.map +0 -1
- package/dist/scripts/lib/validation/index.d.ts +0 -16
- package/dist/scripts/lib/validation/index.d.ts.map +0 -1
- package/dist/scripts/lib/validation/index.js +0 -16
- package/dist/scripts/lib/validation/index.js.map +0 -1
- package/dist/scripts/lib/validation/post-execution.d.ts +0 -74
- package/dist/scripts/lib/validation/post-execution.d.ts.map +0 -1
- package/dist/scripts/lib/validation/post-execution.js +0 -110
- package/dist/scripts/lib/validation/post-execution.js.map +0 -1
- package/dist/scripts/lib/validation/pre-execution.d.ts +0 -165
- package/dist/scripts/lib/validation/pre-execution.d.ts.map +0 -1
- package/dist/scripts/lib/validation/pre-execution.js +0 -466
- package/dist/scripts/lib/validation/pre-execution.js.map +0 -1
- package/dist/scripts/lib/validators/documentation-validator.d.ts +0 -242
- package/dist/scripts/lib/validators/documentation-validator.d.ts.map +0 -1
- package/dist/scripts/lib/validators/documentation-validator.js +0 -584
- package/dist/scripts/lib/validators/documentation-validator.js.map +0 -1
- package/dist/scripts/lib/validators/index.d.ts +0 -11
- package/dist/scripts/lib/validators/index.d.ts.map +0 -1
- package/dist/scripts/lib/validators/index.js +0 -11
- package/dist/scripts/lib/validators/index.js.map +0 -1
- package/docker-compose.yml +0 -46
- package/docs/INDEX.md +0 -88
- package/docs/README.md +0 -774
- package/docs/SETUP.md +0 -264
- package/docs/servers/code-validator.md +0 -586
- package/eslint.config.js +0 -7
- package/migrations/0001_add_crdt_columns.sql +0 -8
- package/migrations/0001_rollback.sql +0 -6
- package/migrations/005_performance_indexes.sql +0 -190
- package/migrations/backfill_crdt_meta.js +0 -45
- package/src/__tests__/hypervisor.test.ts +0 -212
- package/src/adapters/db.ts +0 -180
- package/src/config/config.json +0 -49
- package/src/config/index.ts +0 -30
- package/src/contracts.ts +0 -221
- package/src/hypervisor.ts +0 -464
- package/src/index.ts +0 -87
- package/src/servers/adapter.ts +0 -643
- package/src/servers/code-validator.ts +0 -188
- package/src/servers/neon.ts +0 -103
- package/src/servers/next-devtools.ts +0 -230
- package/src/servers/playwright.ts +0 -77
- package/src/servers/stripe.ts +0 -99
- package/src/servers/supabase.ts +0 -161
- package/src/servers/vercel.ts +0 -100
- package/src/servers/vultr-test.ts +0 -97
- package/tsconfig.json +0 -12
- package/vitest.config.ts +0 -22
- /package/dist/{packages/mcp/src/adapters → adapters}/db.js +0 -0
- /package/dist/{packages/mcp/src/config → config}/index.d.ts +0 -0
- /package/dist/{packages/mcp/src/config → config}/index.js +0 -0
- /package/dist/{packages/mcp/src/contracts.d.ts → contracts.d.ts} +0 -0
- /package/dist/{packages/mcp/src/contracts.js → contracts.js} +0 -0
package/src/servers/adapter.ts
DELETED
|
@@ -1,643 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MCP Adapter - Generic Model Context Protocol Integration
|
|
5
|
-
*
|
|
6
|
-
* Provides a unified interface for all MCP server integrations,
|
|
7
|
-
* eliminating code duplication across different service adapters.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* const adapter = new MCPAdapter('vercel', config)
|
|
11
|
-
* await adapter.execute(request)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { randomBytes } from 'node:crypto'
|
|
15
|
-
import { registerCleanupHandler } from '@revealui/core/monitoring'
|
|
16
|
-
import { logger as coreLogger } from '@revealui/core/observability/logger'
|
|
17
|
-
import { createLogger } from '@revealui/scripts'
|
|
18
|
-
import { ErrorCode, ScriptError } from '@revealui/scripts/errors'
|
|
19
|
-
|
|
20
|
-
// =============================================================================
|
|
21
|
-
// Global Adapter Registry
|
|
22
|
-
// =============================================================================
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Track all active MCP adapters for cleanup
|
|
26
|
-
*/
|
|
27
|
-
const activeAdapters: Set<MCPAdapter> = new Set()
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Dispose all active MCP adapters (cleanup on shutdown)
|
|
31
|
-
*/
|
|
32
|
-
export function disposeAllAdapters(): void {
|
|
33
|
-
for (const adapter of activeAdapters) {
|
|
34
|
-
try {
|
|
35
|
-
adapter.dispose()
|
|
36
|
-
} catch (error) {
|
|
37
|
-
coreLogger.error(
|
|
38
|
-
'Failed to dispose adapter',
|
|
39
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
activeAdapters.clear()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Register cleanup handler
|
|
47
|
-
let cleanupHandlerRegistered = false
|
|
48
|
-
function registerAdapterCleanup() {
|
|
49
|
-
if (cleanupHandlerRegistered) return
|
|
50
|
-
|
|
51
|
-
registerCleanupHandler(
|
|
52
|
-
'mcp-adapters',
|
|
53
|
-
() => {
|
|
54
|
-
disposeAllAdapters()
|
|
55
|
-
},
|
|
56
|
-
'Dispose all MCP adapters',
|
|
57
|
-
90, // High priority
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
cleanupHandlerRegistered = true
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface MCPRequest {
|
|
64
|
-
action: string
|
|
65
|
-
parameters?: Record<string, unknown>
|
|
66
|
-
options?: {
|
|
67
|
-
timeout?: number
|
|
68
|
-
retries?: number
|
|
69
|
-
dryRun?: boolean
|
|
70
|
-
/** Idempotency key to prevent duplicate operations */
|
|
71
|
-
idempotencyKey?: string
|
|
72
|
-
/** TTL for idempotency cache in milliseconds (default: 5 minutes) */
|
|
73
|
-
idempotencyTTL?: number
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface MCPResponse {
|
|
78
|
-
success: boolean
|
|
79
|
-
data?: unknown
|
|
80
|
-
error?: string
|
|
81
|
-
metadata?: {
|
|
82
|
-
duration: number
|
|
83
|
-
retries: number
|
|
84
|
-
service: string
|
|
85
|
-
/** Indicates this response was served from idempotency cache */
|
|
86
|
-
cached?: boolean
|
|
87
|
-
/** The idempotency key that was used */
|
|
88
|
-
idempotencyKey?: string
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// =============================================================================
|
|
93
|
-
// Idempotency Cache
|
|
94
|
-
// =============================================================================
|
|
95
|
-
|
|
96
|
-
interface CachedResponse {
|
|
97
|
-
response: MCPResponse
|
|
98
|
-
expiresAt: number
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* In-memory idempotency cache for preventing duplicate operations.
|
|
103
|
-
* Each adapter instance has its own cache.
|
|
104
|
-
*/
|
|
105
|
-
class IdempotencyCache {
|
|
106
|
-
private cache = new Map<string, CachedResponse>()
|
|
107
|
-
private cleanupInterval: ReturnType<typeof setInterval> | null = null
|
|
108
|
-
// biome-ignore lint/style/useNamingConvention: Constant should be uppercase
|
|
109
|
-
private readonly DEFAULT_TTL = 5 * 60 * 1000 // 5 minutes
|
|
110
|
-
|
|
111
|
-
constructor() {
|
|
112
|
-
// Cleanup expired entries every minute
|
|
113
|
-
this.cleanupInterval = setInterval(() => this.cleanup(), 60 * 1000)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Get a cached response by idempotency key
|
|
118
|
-
*/
|
|
119
|
-
get(key: string): MCPResponse | null {
|
|
120
|
-
const cached = this.cache.get(key)
|
|
121
|
-
if (!cached) return null
|
|
122
|
-
|
|
123
|
-
if (Date.now() > cached.expiresAt) {
|
|
124
|
-
this.cache.delete(key)
|
|
125
|
-
return null
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
...cached.response,
|
|
130
|
-
metadata: {
|
|
131
|
-
...cached.response.metadata,
|
|
132
|
-
cached: true,
|
|
133
|
-
idempotencyKey: key,
|
|
134
|
-
},
|
|
135
|
-
} as MCPResponse
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Store a response with idempotency key
|
|
140
|
-
*/
|
|
141
|
-
set(key: string, response: MCPResponse, ttl?: number): void {
|
|
142
|
-
const expiresAt = Date.now() + (ttl || this.DEFAULT_TTL)
|
|
143
|
-
this.cache.set(key, { response, expiresAt })
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Check if a key exists in the cache
|
|
148
|
-
*/
|
|
149
|
-
has(key: string): boolean {
|
|
150
|
-
const cached = this.cache.get(key)
|
|
151
|
-
if (!cached) return false
|
|
152
|
-
|
|
153
|
-
if (Date.now() > cached.expiresAt) {
|
|
154
|
-
this.cache.delete(key)
|
|
155
|
-
return false
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return true
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Remove expired entries
|
|
163
|
-
*/
|
|
164
|
-
private cleanup(): void {
|
|
165
|
-
const now = Date.now()
|
|
166
|
-
for (const [key, value] of this.cache.entries()) {
|
|
167
|
-
if (now > value.expiresAt) {
|
|
168
|
-
this.cache.delete(key)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Clear all cached entries
|
|
175
|
-
*/
|
|
176
|
-
clear(): void {
|
|
177
|
-
this.cache.clear()
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Stop the cleanup interval
|
|
182
|
-
*/
|
|
183
|
-
dispose(): void {
|
|
184
|
-
if (this.cleanupInterval) {
|
|
185
|
-
clearInterval(this.cleanupInterval)
|
|
186
|
-
this.cleanupInterval = null
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Get cache statistics
|
|
192
|
-
*/
|
|
193
|
-
stats(): { size: number; keys: string[] } {
|
|
194
|
-
return {
|
|
195
|
-
size: this.cache.size,
|
|
196
|
-
keys: Array.from(this.cache.keys()),
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export interface MCPConfig {
|
|
202
|
-
apiKey?: string
|
|
203
|
-
baseUrl?: string
|
|
204
|
-
timeout?: number
|
|
205
|
-
retries?: number
|
|
206
|
-
environment?: 'development' | 'production'
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export abstract class MCPAdapter {
|
|
210
|
-
protected serviceName: string
|
|
211
|
-
protected config: MCPConfig
|
|
212
|
-
protected logger = createLogger()
|
|
213
|
-
protected idempotencyCache = new IdempotencyCache()
|
|
214
|
-
|
|
215
|
-
constructor(serviceName: string, config: MCPConfig) {
|
|
216
|
-
this.serviceName = serviceName
|
|
217
|
-
this.config = {
|
|
218
|
-
timeout: 30000,
|
|
219
|
-
retries: 3,
|
|
220
|
-
environment: 'development',
|
|
221
|
-
...config,
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Register adapter for cleanup
|
|
225
|
-
activeAdapters.add(this)
|
|
226
|
-
registerAdapterCleanup()
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Dispose of adapter resources (cleanup idempotency cache)
|
|
231
|
-
*/
|
|
232
|
-
dispose(): void {
|
|
233
|
-
this.idempotencyCache.dispose()
|
|
234
|
-
activeAdapters.delete(this)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Get idempotency cache statistics
|
|
239
|
-
*/
|
|
240
|
-
getCacheStats(): { size: number; keys: string[] } {
|
|
241
|
-
return this.idempotencyCache.stats()
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Clear the idempotency cache
|
|
246
|
-
*/
|
|
247
|
-
clearCache(): void {
|
|
248
|
-
this.idempotencyCache.clear()
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Execute an MCP request
|
|
253
|
-
*
|
|
254
|
-
* If an idempotencyKey is provided in options, the request will be checked
|
|
255
|
-
* against the cache. If a cached response exists, it will be returned
|
|
256
|
-
* immediately. Otherwise, the request will be executed and the response
|
|
257
|
-
* will be cached for future duplicate requests.
|
|
258
|
-
*/
|
|
259
|
-
async execute(request: MCPRequest): Promise<MCPResponse> {
|
|
260
|
-
const startTime = Date.now()
|
|
261
|
-
let attempts = 0
|
|
262
|
-
const idempotencyKey = request.options?.idempotencyKey
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
this.validateRequest(request)
|
|
266
|
-
|
|
267
|
-
// Check idempotency cache first
|
|
268
|
-
if (idempotencyKey) {
|
|
269
|
-
const cached = this.idempotencyCache.get(idempotencyKey)
|
|
270
|
-
if (cached) {
|
|
271
|
-
this.logger.info(
|
|
272
|
-
`[${this.serviceName}] Returning cached response for idempotency key: ${idempotencyKey}`,
|
|
273
|
-
)
|
|
274
|
-
return cached
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (request.options?.dryRun) {
|
|
279
|
-
return this.createDryRunResponse(request)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
while (attempts < (request.options?.retries || this.config.retries || 3)) {
|
|
283
|
-
attempts++
|
|
284
|
-
|
|
285
|
-
try {
|
|
286
|
-
this.logger.info(
|
|
287
|
-
`[${this.serviceName}] Executing ${request.action} (attempt ${attempts})`,
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
const result = await this.executeRequest(request)
|
|
291
|
-
|
|
292
|
-
const duration = Date.now() - startTime
|
|
293
|
-
const response: MCPResponse = {
|
|
294
|
-
success: true,
|
|
295
|
-
data: result,
|
|
296
|
-
metadata: {
|
|
297
|
-
duration,
|
|
298
|
-
retries: attempts - 1,
|
|
299
|
-
service: this.serviceName,
|
|
300
|
-
...(idempotencyKey && { idempotencyKey }),
|
|
301
|
-
},
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Cache successful response if idempotency key provided
|
|
305
|
-
if (idempotencyKey) {
|
|
306
|
-
this.idempotencyCache.set(idempotencyKey, response, request.options?.idempotencyTTL)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return response
|
|
310
|
-
} catch (error) {
|
|
311
|
-
this.logger.warning(`[${this.serviceName}] Attempt ${attempts} failed: ${error}`)
|
|
312
|
-
|
|
313
|
-
if (attempts >= (request.options?.retries || this.config.retries || 3)) {
|
|
314
|
-
throw error
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Wait before retry (exponential backoff)
|
|
318
|
-
const delay = Math.min(1000 * 2 ** (attempts - 1), 10000)
|
|
319
|
-
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
throw new ScriptError('All retry attempts exhausted', ErrorCode.TIMEOUT_ERROR)
|
|
324
|
-
} catch (error) {
|
|
325
|
-
const duration = Date.now() - startTime
|
|
326
|
-
const response: MCPResponse = {
|
|
327
|
-
success: false,
|
|
328
|
-
error: error instanceof Error ? error.message : String(error),
|
|
329
|
-
metadata: {
|
|
330
|
-
duration,
|
|
331
|
-
retries: attempts,
|
|
332
|
-
service: this.serviceName,
|
|
333
|
-
...(idempotencyKey && { idempotencyKey }),
|
|
334
|
-
},
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Also cache failed responses to prevent duplicate attempts
|
|
338
|
-
// This prevents the same failing request from being retried repeatedly
|
|
339
|
-
if (idempotencyKey) {
|
|
340
|
-
this.idempotencyCache.set(idempotencyKey, response, request.options?.idempotencyTTL)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return response
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Validate the incoming request
|
|
349
|
-
*/
|
|
350
|
-
protected validateRequest(request: MCPRequest): void {
|
|
351
|
-
if (!request.action) {
|
|
352
|
-
throw new ScriptError('Request must include an action', ErrorCode.VALIDATION_ERROR)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (!this.isValidAction(request.action)) {
|
|
356
|
-
throw new ScriptError(`Unsupported action: ${request.action}`, ErrorCode.VALIDATION_ERROR)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Check if an action is supported by this adapter
|
|
362
|
-
*/
|
|
363
|
-
protected abstract isValidAction(action: string): boolean
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Execute the actual request (implemented by subclasses)
|
|
367
|
-
*/
|
|
368
|
-
protected abstract executeRequest(request: MCPRequest): Promise<unknown>
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Create a dry-run response
|
|
372
|
-
*/
|
|
373
|
-
protected createDryRunResponse(request: MCPRequest): MCPResponse {
|
|
374
|
-
return {
|
|
375
|
-
success: true,
|
|
376
|
-
data: {
|
|
377
|
-
dryRun: true,
|
|
378
|
-
action: request.action,
|
|
379
|
-
parameters: request.parameters,
|
|
380
|
-
message: `Would execute ${request.action} on ${this.serviceName}`,
|
|
381
|
-
},
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Get authentication headers for API calls
|
|
387
|
-
*/
|
|
388
|
-
protected getAuthHeaders(): Record<string, string> {
|
|
389
|
-
const headers: Record<string, string> = {
|
|
390
|
-
'Content-Type': 'application/json',
|
|
391
|
-
'User-Agent': `RevealUI-MCP/${this.serviceName}`,
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (this.config.apiKey) {
|
|
395
|
-
// Different services use different header names
|
|
396
|
-
const headerName = this.getAuthHeaderName()
|
|
397
|
-
headers[headerName] = this.config.apiKey
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return headers
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Get the authentication header name for this service
|
|
405
|
-
*/
|
|
406
|
-
protected abstract getAuthHeaderName(): string
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Make an HTTP request with proper error handling
|
|
410
|
-
*/
|
|
411
|
-
protected async makeRequest(
|
|
412
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
413
|
-
url: string,
|
|
414
|
-
data?: unknown,
|
|
415
|
-
): Promise<unknown> {
|
|
416
|
-
const headers = this.getAuthHeaders()
|
|
417
|
-
const timeout = this.config.timeout || 30000
|
|
418
|
-
|
|
419
|
-
const controller = new AbortController()
|
|
420
|
-
const timer = setTimeout(() => controller.abort(), timeout)
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
const response = await fetch(url, {
|
|
424
|
-
method,
|
|
425
|
-
headers,
|
|
426
|
-
body: data && (method === 'POST' || method === 'PUT') ? JSON.stringify(data) : undefined,
|
|
427
|
-
signal: controller.signal,
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
if (!response.ok) {
|
|
431
|
-
throw new ScriptError(
|
|
432
|
-
`HTTP ${response.status} ${response.statusText}`,
|
|
433
|
-
ErrorCode.NETWORK_ERROR,
|
|
434
|
-
)
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const contentType = response.headers.get('content-type') ?? ''
|
|
438
|
-
if (contentType.includes('application/json')) {
|
|
439
|
-
return await response.json()
|
|
440
|
-
}
|
|
441
|
-
return await response.text()
|
|
442
|
-
} catch (error) {
|
|
443
|
-
if (error instanceof ScriptError) throw error
|
|
444
|
-
throw new ScriptError(
|
|
445
|
-
`Request to ${this.serviceName} failed: ${error}`,
|
|
446
|
-
ErrorCode.NETWORK_ERROR,
|
|
447
|
-
)
|
|
448
|
-
} finally {
|
|
449
|
-
clearTimeout(timer)
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Specific service adapters
|
|
455
|
-
|
|
456
|
-
export class VercelAdapter extends MCPAdapter {
|
|
457
|
-
constructor(config: MCPConfig) {
|
|
458
|
-
super('vercel', config)
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
protected isValidAction(action: string): boolean {
|
|
462
|
-
return ['deploy', 'list-deployments', 'get-deployment', 'delete-deployment'].includes(action)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
protected getAuthHeaderName(): string {
|
|
466
|
-
return 'Authorization'
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
protected async executeRequest(request: MCPRequest): Promise<unknown> {
|
|
470
|
-
const baseUrl = this.config.baseUrl || 'https://api.vercel.com'
|
|
471
|
-
|
|
472
|
-
switch (request.action) {
|
|
473
|
-
case 'deploy':
|
|
474
|
-
return this.makeRequest('POST', `${baseUrl}/v13/deployments`, request.parameters)
|
|
475
|
-
|
|
476
|
-
case 'list-deployments':
|
|
477
|
-
return this.makeRequest('GET', `${baseUrl}/v6/deployments`)
|
|
478
|
-
|
|
479
|
-
case 'get-deployment': {
|
|
480
|
-
const { id } = request.parameters || {}
|
|
481
|
-
if (!id) throw new ScriptError('Deployment ID required', ErrorCode.VALIDATION_ERROR)
|
|
482
|
-
return this.makeRequest('GET', `${baseUrl}/v13/deployments/${String(id)}`)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
case 'delete-deployment': {
|
|
486
|
-
const { deploymentId } = request.parameters || {}
|
|
487
|
-
if (!deploymentId)
|
|
488
|
-
throw new ScriptError('Deployment ID required', ErrorCode.VALIDATION_ERROR)
|
|
489
|
-
return this.makeRequest('DELETE', `${baseUrl}/v13/deployments/${String(deploymentId)}`)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
default:
|
|
493
|
-
throw new ScriptError(`Unsupported action: ${request.action}`, ErrorCode.VALIDATION_ERROR)
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
export class StripeAdapter extends MCPAdapter {
|
|
499
|
-
constructor(config: MCPConfig) {
|
|
500
|
-
super('stripe', config)
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
protected isValidAction(action: string): boolean {
|
|
504
|
-
return [
|
|
505
|
-
'create-payment-intent',
|
|
506
|
-
'list-payment-intents',
|
|
507
|
-
'create-customer',
|
|
508
|
-
'list-customers',
|
|
509
|
-
].includes(action)
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
protected getAuthHeaderName(): string {
|
|
513
|
-
return 'Authorization'
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
protected async executeRequest(request: MCPRequest): Promise<unknown> {
|
|
517
|
-
const baseUrl = this.config.baseUrl || 'https://api.stripe.com/v1'
|
|
518
|
-
|
|
519
|
-
switch (request.action) {
|
|
520
|
-
case 'create-payment-intent':
|
|
521
|
-
return this.makeRequest('POST', `${baseUrl}/payment_intents`, request.parameters)
|
|
522
|
-
|
|
523
|
-
case 'list-payment-intents':
|
|
524
|
-
return this.makeRequest('GET', `${baseUrl}/payment_intents`)
|
|
525
|
-
|
|
526
|
-
case 'create-customer':
|
|
527
|
-
return this.makeRequest('POST', `${baseUrl}/customers`, request.parameters)
|
|
528
|
-
|
|
529
|
-
case 'list-customers':
|
|
530
|
-
return this.makeRequest('GET', `${baseUrl}/customers`)
|
|
531
|
-
|
|
532
|
-
default:
|
|
533
|
-
throw new ScriptError(`Unsupported action: ${request.action}`, ErrorCode.VALIDATION_ERROR)
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
export class NeonAdapter extends MCPAdapter {
|
|
539
|
-
constructor(config: MCPConfig) {
|
|
540
|
-
super('neon', config)
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
protected isValidAction(action: string): boolean {
|
|
544
|
-
return ['list-projects', 'create-project', 'get-project', 'delete-project'].includes(action)
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
protected getAuthHeaderName(): string {
|
|
548
|
-
return 'Authorization'
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
protected async executeRequest(request: MCPRequest): Promise<unknown> {
|
|
552
|
-
const baseUrl = this.config.baseUrl || 'https://console.neon.tech/api/v2'
|
|
553
|
-
|
|
554
|
-
switch (request.action) {
|
|
555
|
-
case 'list-projects':
|
|
556
|
-
return this.makeRequest('GET', `${baseUrl}/projects`)
|
|
557
|
-
|
|
558
|
-
case 'create-project':
|
|
559
|
-
return this.makeRequest('POST', `${baseUrl}/projects`, request.parameters)
|
|
560
|
-
|
|
561
|
-
case 'get-project': {
|
|
562
|
-
const { id } = request.parameters || {}
|
|
563
|
-
if (!id) throw new ScriptError('Project ID required', ErrorCode.VALIDATION_ERROR)
|
|
564
|
-
return this.makeRequest('GET', `${baseUrl}/projects/${String(id)}`)
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
case 'delete-project': {
|
|
568
|
-
const { projectId } = request.parameters || {}
|
|
569
|
-
if (!projectId) throw new ScriptError('Project ID required', ErrorCode.VALIDATION_ERROR)
|
|
570
|
-
return this.makeRequest('DELETE', `${baseUrl}/projects/${String(projectId)}`)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
default:
|
|
574
|
-
throw new ScriptError(`Unsupported action: ${request.action}`, ErrorCode.VALIDATION_ERROR)
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Factory function to create adapters
|
|
580
|
-
export function createMCPAdapter(service: string, config: MCPConfig): MCPAdapter {
|
|
581
|
-
switch (service.toLowerCase()) {
|
|
582
|
-
case 'vercel':
|
|
583
|
-
return new VercelAdapter(config)
|
|
584
|
-
case 'stripe':
|
|
585
|
-
return new StripeAdapter(config)
|
|
586
|
-
case 'neon':
|
|
587
|
-
return new NeonAdapter(config)
|
|
588
|
-
default:
|
|
589
|
-
throw new ScriptError(`Unsupported MCP service: ${service}`, ErrorCode.CONFIG_ERROR)
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
/**
|
|
594
|
-
* Generate an idempotency key based on the request content.
|
|
595
|
-
*
|
|
596
|
-
* This creates a deterministic key from the action and parameters,
|
|
597
|
-
* so identical requests will get the same key.
|
|
598
|
-
*
|
|
599
|
-
* @example
|
|
600
|
-
* ```typescript
|
|
601
|
-
* const key = generateIdempotencyKey({
|
|
602
|
-
* action: 'deploy',
|
|
603
|
-
* parameters: { projectId: '123' }
|
|
604
|
-
* })
|
|
605
|
-
* // Returns: "deploy:a1b2c3d4e5f6..."
|
|
606
|
-
* ```
|
|
607
|
-
*/
|
|
608
|
-
export function generateIdempotencyKey(request: MCPRequest): string {
|
|
609
|
-
const content = JSON.stringify({
|
|
610
|
-
action: request.action,
|
|
611
|
-
parameters: request.parameters || {},
|
|
612
|
-
})
|
|
613
|
-
|
|
614
|
-
// Simple hash function for generating consistent keys
|
|
615
|
-
let hash = 0
|
|
616
|
-
for (let i = 0; i < content.length; i++) {
|
|
617
|
-
const char = content.charCodeAt(i)
|
|
618
|
-
hash = (hash << 5) - hash + char
|
|
619
|
-
hash = hash & hash // Convert to 32-bit integer
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Convert to positive hex string
|
|
623
|
-
const hashHex = Math.abs(hash).toString(16).padStart(8, '0')
|
|
624
|
-
return `${request.action}:${hashHex}`
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* Generate a unique idempotency key with timestamp.
|
|
629
|
-
*
|
|
630
|
-
* Use this when you want each request to be unique but still
|
|
631
|
-
* want idempotency protection against rapid duplicate submissions.
|
|
632
|
-
*
|
|
633
|
-
* @example
|
|
634
|
-
* ```typescript
|
|
635
|
-
* const key = generateUniqueIdempotencyKey('deploy')
|
|
636
|
-
* // Returns: "deploy:1706536800000:a1b2c3d4"
|
|
637
|
-
* ```
|
|
638
|
-
*/
|
|
639
|
-
export function generateUniqueIdempotencyKey(action: string): string {
|
|
640
|
-
const timestamp = Date.now()
|
|
641
|
-
const random = randomBytes(6).toString('hex')
|
|
642
|
-
return `${action}:${timestamp}:${random}`
|
|
643
|
-
}
|