@intentsolutionsio/vercel-pack 1.0.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/.claude-plugin/plugin.json +17 -0
- package/000-docs/001-BL-LICN-license.txt +3 -0
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/package.json +43 -0
- package/skills/vercel-advanced-troubleshooting/SKILL.md +261 -0
- package/skills/vercel-architecture-variants/SKILL.md +284 -0
- package/skills/vercel-ci-integration/SKILL.md +124 -0
- package/skills/vercel-common-errors/SKILL.md +109 -0
- package/skills/vercel-cost-tuning/SKILL.md +201 -0
- package/skills/vercel-data-handling/SKILL.md +220 -0
- package/skills/vercel-debug-bundle/SKILL.md +111 -0
- package/skills/vercel-deploy-integration/SKILL.md +209 -0
- package/skills/vercel-deploy-preview/SKILL.md +71 -0
- package/skills/vercel-edge-functions/SKILL.md +73 -0
- package/skills/vercel-enterprise-rbac/SKILL.md +222 -0
- package/skills/vercel-hello-world/SKILL.md +96 -0
- package/skills/vercel-incident-runbook/SKILL.md +203 -0
- package/skills/vercel-install-auth/SKILL.md +90 -0
- package/skills/vercel-known-pitfalls/SKILL.md +334 -0
- package/skills/vercel-load-scale/SKILL.md +274 -0
- package/skills/vercel-local-dev-loop/SKILL.md +117 -0
- package/skills/vercel-migration-deep-dive/SKILL.md +244 -0
- package/skills/vercel-multi-env-setup/SKILL.md +222 -0
- package/skills/vercel-observability/SKILL.md +250 -0
- package/skills/vercel-performance-tuning/SKILL.md +214 -0
- package/skills/vercel-policy-guardrails/SKILL.md +257 -0
- package/skills/vercel-prod-checklist/SKILL.md +119 -0
- package/skills/vercel-rate-limits/SKILL.md +149 -0
- package/skills/vercel-reference-architecture/SKILL.md +238 -0
- package/skills/vercel-reliability-patterns/SKILL.md +290 -0
- package/skills/vercel-sdk-patterns/SKILL.md +147 -0
- package/skills/vercel-security-basics/SKILL.md +140 -0
- package/skills/vercel-upgrade-migration/SKILL.md +112 -0
- package/skills/vercel-webhooks-events/SKILL.md +199 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vercel-reference-architecture
|
|
3
|
+
description: |
|
|
4
|
+
Implement Vercel reference architecture with best-practice project layout.
|
|
5
|
+
Use when designing new Vercel integrations, reviewing project structure,
|
|
6
|
+
or establishing architecture standards for Vercel applications.
|
|
7
|
+
Trigger with phrases like "vercel architecture", "vercel best practices",
|
|
8
|
+
"vercel project structure", "how to organize vercel", "vercel layout".
|
|
9
|
+
allowed-tools: Read, Grep
|
|
10
|
+
version: 1.0.0
|
|
11
|
+
license: MIT
|
|
12
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Vercel Reference Architecture
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
Production-ready architecture patterns for Vercel integrations.
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
- Understanding of layered architecture
|
|
22
|
+
- Vercel SDK knowledge
|
|
23
|
+
- TypeScript project setup
|
|
24
|
+
- Testing framework configured
|
|
25
|
+
|
|
26
|
+
## Project Structure
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
my-vercel-project/
|
|
30
|
+
├── src/
|
|
31
|
+
│ ├── vercel/
|
|
32
|
+
│ │ ├── client.ts # Singleton client wrapper
|
|
33
|
+
│ │ ├── config.ts # Environment configuration
|
|
34
|
+
│ │ ├── types.ts # TypeScript types
|
|
35
|
+
│ │ ├── errors.ts # Custom error classes
|
|
36
|
+
│ │ └── handlers/
|
|
37
|
+
│ │ ├── webhooks.ts # Webhook handlers
|
|
38
|
+
│ │ └── events.ts # Event processing
|
|
39
|
+
│ ├── services/
|
|
40
|
+
│ │ └── vercel/
|
|
41
|
+
│ │ ├── index.ts # Service facade
|
|
42
|
+
│ │ ├── sync.ts # Data synchronization
|
|
43
|
+
│ │ └── cache.ts # Caching layer
|
|
44
|
+
│ ├── api/
|
|
45
|
+
│ │ └── vercel/
|
|
46
|
+
│ │ └── webhook.ts # Webhook endpoint
|
|
47
|
+
│ └── jobs/
|
|
48
|
+
│ └── vercel/
|
|
49
|
+
│ └── sync.ts # Background sync job
|
|
50
|
+
├── tests/
|
|
51
|
+
│ ├── unit/
|
|
52
|
+
│ │ └── vercel/
|
|
53
|
+
│ └── integration/
|
|
54
|
+
│ └── vercel/
|
|
55
|
+
├── config/
|
|
56
|
+
│ ├── vercel.development.json
|
|
57
|
+
│ ├── vercel.staging.json
|
|
58
|
+
│ └── vercel.production.json
|
|
59
|
+
└── docs/
|
|
60
|
+
└── vercel/
|
|
61
|
+
├── SETUP.md
|
|
62
|
+
└── RUNBOOK.md
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Layer Architecture
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
┌─────────────────────────────────────────┐
|
|
69
|
+
│ API Layer │
|
|
70
|
+
│ (Controllers, Routes, Webhooks) │
|
|
71
|
+
├─────────────────────────────────────────┤
|
|
72
|
+
│ Service Layer │
|
|
73
|
+
│ (Business Logic, Orchestration) │
|
|
74
|
+
├─────────────────────────────────────────┤
|
|
75
|
+
│ Vercel Layer │
|
|
76
|
+
│ (Client, Types, Error Handling) │
|
|
77
|
+
├─────────────────────────────────────────┤
|
|
78
|
+
│ Infrastructure Layer │
|
|
79
|
+
│ (Cache, Queue, Monitoring) │
|
|
80
|
+
└─────────────────────────────────────────┘
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Key Components
|
|
84
|
+
|
|
85
|
+
### Step 1: Client Wrapper
|
|
86
|
+
```typescript
|
|
87
|
+
// src/vercel/client.ts
|
|
88
|
+
export class VercelService {
|
|
89
|
+
private client: VercelClient;
|
|
90
|
+
private cache: Cache;
|
|
91
|
+
private monitor: Monitor;
|
|
92
|
+
|
|
93
|
+
constructor(config: VercelConfig) {
|
|
94
|
+
this.client = new VercelClient(config);
|
|
95
|
+
this.cache = new Cache(config.cacheOptions);
|
|
96
|
+
this.monitor = new Monitor('vercel');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async get(id: string): Promise<Resource> {
|
|
100
|
+
return this.cache.getOrFetch(id, () =>
|
|
101
|
+
this.monitor.track('get', () => this.client.get(id))
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Step 2: Error Boundary
|
|
108
|
+
```typescript
|
|
109
|
+
// src/vercel/errors.ts
|
|
110
|
+
export class VercelServiceError extends Error {
|
|
111
|
+
constructor(
|
|
112
|
+
message: string,
|
|
113
|
+
public readonly code: string,
|
|
114
|
+
public readonly retryable: boolean,
|
|
115
|
+
public readonly originalError?: Error
|
|
116
|
+
) {
|
|
117
|
+
super(message);
|
|
118
|
+
this.name = 'VercelServiceError';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function wrapVercelError(error: unknown): VercelServiceError {
|
|
123
|
+
// Transform SDK errors to application errors
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Step 3: Health Check
|
|
128
|
+
```typescript
|
|
129
|
+
// src/vercel/health.ts
|
|
130
|
+
export async function checkVercelHealth(): Promise<HealthStatus> {
|
|
131
|
+
try {
|
|
132
|
+
const start = Date.now();
|
|
133
|
+
await vercelClient.ping();
|
|
134
|
+
return {
|
|
135
|
+
status: 'healthy',
|
|
136
|
+
latencyMs: Date.now() - start,
|
|
137
|
+
};
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return { status: 'unhealthy', error: error.message };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Data Flow Diagram
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
User Request
|
|
148
|
+
│
|
|
149
|
+
▼
|
|
150
|
+
┌─────────────┐
|
|
151
|
+
│ API │
|
|
152
|
+
│ Gateway │
|
|
153
|
+
└──────┬──────┘
|
|
154
|
+
│
|
|
155
|
+
▼
|
|
156
|
+
┌─────────────┐ ┌─────────────┐
|
|
157
|
+
│ Service │───▶│ Cache │
|
|
158
|
+
│ Layer │ │ (Redis) │
|
|
159
|
+
└──────┬──────┘ └─────────────┘
|
|
160
|
+
│
|
|
161
|
+
▼
|
|
162
|
+
┌─────────────┐
|
|
163
|
+
│ Vercel │
|
|
164
|
+
│ Client │
|
|
165
|
+
└──────┬──────┘
|
|
166
|
+
│
|
|
167
|
+
▼
|
|
168
|
+
┌─────────────┐
|
|
169
|
+
│ Vercel │
|
|
170
|
+
│ API │
|
|
171
|
+
└─────────────┘
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Configuration Management
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// config/vercel.ts
|
|
178
|
+
export interface VercelConfig {
|
|
179
|
+
apiKey: string;
|
|
180
|
+
environment: 'development' | 'staging' | 'production';
|
|
181
|
+
timeout: number;
|
|
182
|
+
retries: number;
|
|
183
|
+
cache: {
|
|
184
|
+
enabled: boolean;
|
|
185
|
+
ttlSeconds: number;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function loadVercelConfig(): VercelConfig {
|
|
190
|
+
const env = process.env.NODE_ENV || 'development';
|
|
191
|
+
return require(`./vercel.${env}.json`);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Instructions
|
|
196
|
+
|
|
197
|
+
### Step 1: Create Directory Structure
|
|
198
|
+
Set up the project layout following the reference structure above.
|
|
199
|
+
|
|
200
|
+
### Step 2: Implement Client Wrapper
|
|
201
|
+
Create the singleton client with caching and monitoring.
|
|
202
|
+
|
|
203
|
+
### Step 3: Add Error Handling
|
|
204
|
+
Implement custom error classes for Vercel operations.
|
|
205
|
+
|
|
206
|
+
### Step 4: Configure Health Checks
|
|
207
|
+
Add health check endpoint for Vercel connectivity.
|
|
208
|
+
|
|
209
|
+
## Output
|
|
210
|
+
- Structured project layout
|
|
211
|
+
- Client wrapper with caching
|
|
212
|
+
- Error boundary implemented
|
|
213
|
+
- Health checks configured
|
|
214
|
+
|
|
215
|
+
## Error Handling
|
|
216
|
+
| Issue | Cause | Solution |
|
|
217
|
+
|-------|-------|----------|
|
|
218
|
+
| Circular dependencies | Wrong layering | Separate concerns by layer |
|
|
219
|
+
| Config not loading | Wrong paths | Verify config file locations |
|
|
220
|
+
| Type errors | Missing types | Add Vercel types |
|
|
221
|
+
| Test isolation | Shared state | Use dependency injection |
|
|
222
|
+
|
|
223
|
+
## Examples
|
|
224
|
+
|
|
225
|
+
### Quick Setup Script
|
|
226
|
+
```bash
|
|
227
|
+
# Create reference structure
|
|
228
|
+
mkdir -p src/vercel/{handlers} src/services/vercel src/api/vercel
|
|
229
|
+
touch src/vercel/{client,config,types,errors}.ts
|
|
230
|
+
touch src/services/vercel/{index,sync,cache}.ts
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Resources
|
|
234
|
+
- [Vercel SDK Documentation](https://vercel.com/docs/sdk)
|
|
235
|
+
- [Vercel Best Practices](https://vercel.com/docs/best-practices)
|
|
236
|
+
|
|
237
|
+
## Flagship Skills
|
|
238
|
+
For multi-environment setup, see `vercel-multi-env-setup`.
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vercel-reliability-patterns
|
|
3
|
+
description: |
|
|
4
|
+
Implement Vercel reliability patterns including circuit breakers, idempotency, and graceful degradation.
|
|
5
|
+
Use when building fault-tolerant Vercel integrations, implementing retry strategies,
|
|
6
|
+
or adding resilience to production Vercel services.
|
|
7
|
+
Trigger with phrases like "vercel reliability", "vercel circuit breaker",
|
|
8
|
+
"vercel idempotent", "vercel resilience", "vercel fallback", "vercel bulkhead".
|
|
9
|
+
allowed-tools: Read, Write, Edit
|
|
10
|
+
version: 1.0.0
|
|
11
|
+
license: MIT
|
|
12
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Vercel Reliability Patterns
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
Production-grade reliability patterns for Vercel integrations.
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
- Understanding of circuit breaker pattern
|
|
22
|
+
- opossum or similar library installed
|
|
23
|
+
- Queue infrastructure for DLQ
|
|
24
|
+
- Caching layer for fallbacks
|
|
25
|
+
|
|
26
|
+
## Circuit Breaker
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import CircuitBreaker from 'opossum';
|
|
30
|
+
|
|
31
|
+
const vercelBreaker = new CircuitBreaker(
|
|
32
|
+
async (operation: () => Promise<any>) => operation(),
|
|
33
|
+
{
|
|
34
|
+
timeout: 10000,
|
|
35
|
+
errorThresholdPercentage: 50,
|
|
36
|
+
resetTimeout: 30000,
|
|
37
|
+
volumeThreshold: 10,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Events
|
|
42
|
+
vercelBreaker.on('open', () => {
|
|
43
|
+
console.warn('Vercel circuit OPEN - requests failing fast');
|
|
44
|
+
alertOps('Vercel circuit breaker opened');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
vercelBreaker.on('halfOpen', () => {
|
|
48
|
+
console.info('Vercel circuit HALF-OPEN - testing recovery');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
vercelBreaker.on('close', () => {
|
|
52
|
+
console.info('Vercel circuit CLOSED - normal operation');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Usage
|
|
56
|
+
async function safeVercelCall<T>(fn: () => Promise<T>): Promise<T> {
|
|
57
|
+
return vercelBreaker.fire(fn);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Idempotency Keys
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
65
|
+
import crypto from 'crypto';
|
|
66
|
+
|
|
67
|
+
// Generate deterministic idempotency key from input
|
|
68
|
+
function generateIdempotencyKey(
|
|
69
|
+
operation: string,
|
|
70
|
+
params: Record<string, any>
|
|
71
|
+
): string {
|
|
72
|
+
const data = JSON.stringify({ operation, params });
|
|
73
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Or use random key with storage
|
|
77
|
+
class IdempotencyManager {
|
|
78
|
+
private store: Map<string, { key: string; expiresAt: Date }> = new Map();
|
|
79
|
+
|
|
80
|
+
getOrCreate(operationId: string): string {
|
|
81
|
+
const existing = this.store.get(operationId);
|
|
82
|
+
if (existing && existing.expiresAt > new Date()) {
|
|
83
|
+
return existing.key;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const key = uuidv4();
|
|
87
|
+
this.store.set(operationId, {
|
|
88
|
+
key,
|
|
89
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
90
|
+
});
|
|
91
|
+
return key;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Bulkhead Pattern
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import PQueue from 'p-queue';
|
|
100
|
+
|
|
101
|
+
// Separate queues for different operations
|
|
102
|
+
const vercelQueues = {
|
|
103
|
+
critical: new PQueue({ concurrency: 10 }),
|
|
104
|
+
normal: new PQueue({ concurrency: 5 }),
|
|
105
|
+
bulk: new PQueue({ concurrency: 2 }),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
async function prioritizedVercelCall<T>(
|
|
109
|
+
priority: 'critical' | 'normal' | 'bulk',
|
|
110
|
+
fn: () => Promise<T>
|
|
111
|
+
): Promise<T> {
|
|
112
|
+
return vercelQueues[priority].add(fn);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Usage
|
|
116
|
+
await prioritizedVercelCall('critical', () =>
|
|
117
|
+
vercelClient.processPayment(order)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await prioritizedVercelCall('bulk', () =>
|
|
121
|
+
vercelClient.syncCatalog(products)
|
|
122
|
+
);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Timeout Hierarchy
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
const TIMEOUT_CONFIG = {
|
|
129
|
+
connect: 5000, // Initial connection
|
|
130
|
+
request: 30000, // Standard requests
|
|
131
|
+
upload: 120000, // File uploads
|
|
132
|
+
longPoll: 300000, // Webhook long-polling
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
async function timedoutVercelCall<T>(
|
|
136
|
+
operation: 'connect' | 'request' | 'upload' | 'longPoll',
|
|
137
|
+
fn: () => Promise<T>
|
|
138
|
+
): Promise<T> {
|
|
139
|
+
const timeout = TIMEOUT_CONFIG[operation];
|
|
140
|
+
|
|
141
|
+
return Promise.race([
|
|
142
|
+
fn(),
|
|
143
|
+
new Promise<never>((_, reject) =>
|
|
144
|
+
setTimeout(() => reject(new Error(`Vercel ${operation} timeout`)), timeout)
|
|
145
|
+
),
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Graceful Degradation
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface VercelFallback {
|
|
154
|
+
enabled: boolean;
|
|
155
|
+
data: any;
|
|
156
|
+
staleness: 'fresh' | 'stale' | 'very_stale';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function withVercelFallback<T>(
|
|
160
|
+
fn: () => Promise<T>,
|
|
161
|
+
fallbackFn: () => Promise<T>
|
|
162
|
+
): Promise<{ data: T; fallback: boolean }> {
|
|
163
|
+
try {
|
|
164
|
+
const data = await fn();
|
|
165
|
+
// Update cache for future fallback
|
|
166
|
+
await updateFallbackCache(data);
|
|
167
|
+
return { data, fallback: false };
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.warn('Vercel failed, using fallback:', error.message);
|
|
170
|
+
const data = await fallbackFn();
|
|
171
|
+
return { data, fallback: true };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Dead Letter Queue
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
interface DeadLetterEntry {
|
|
180
|
+
id: string;
|
|
181
|
+
operation: string;
|
|
182
|
+
payload: any;
|
|
183
|
+
error: string;
|
|
184
|
+
attempts: number;
|
|
185
|
+
lastAttempt: Date;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class VercelDeadLetterQueue {
|
|
189
|
+
private queue: DeadLetterEntry[] = [];
|
|
190
|
+
|
|
191
|
+
add(entry: Omit<DeadLetterEntry, 'id' | 'lastAttempt'>): void {
|
|
192
|
+
this.queue.push({
|
|
193
|
+
...entry,
|
|
194
|
+
id: uuidv4(),
|
|
195
|
+
lastAttempt: new Date(),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async processOne(): Promise<boolean> {
|
|
200
|
+
const entry = this.queue.shift();
|
|
201
|
+
if (!entry) return false;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await vercelClient[entry.operation](entry.payload);
|
|
205
|
+
console.log(`DLQ: Successfully reprocessed ${entry.id}`);
|
|
206
|
+
return true;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
entry.attempts++;
|
|
209
|
+
entry.lastAttempt = new Date();
|
|
210
|
+
|
|
211
|
+
if (entry.attempts < 5) {
|
|
212
|
+
this.queue.push(entry);
|
|
213
|
+
} else {
|
|
214
|
+
console.error(`DLQ: Giving up on ${entry.id} after 5 attempts`);
|
|
215
|
+
await alertOnPermanentFailure(entry);
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Health Check with Degraded State
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';
|
|
227
|
+
|
|
228
|
+
async function vercelHealthCheck(): Promise<{
|
|
229
|
+
status: HealthStatus;
|
|
230
|
+
details: Record<string, any>;
|
|
231
|
+
}> {
|
|
232
|
+
const checks = {
|
|
233
|
+
api: await checkApiConnectivity(),
|
|
234
|
+
circuitBreaker: vercelBreaker.stats(),
|
|
235
|
+
dlqSize: deadLetterQueue.size(),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const status: HealthStatus =
|
|
239
|
+
!checks.api.connected ? 'unhealthy' :
|
|
240
|
+
checks.circuitBreaker.state === 'open' ? 'degraded' :
|
|
241
|
+
checks.dlqSize > 100 ? 'degraded' :
|
|
242
|
+
'healthy';
|
|
243
|
+
|
|
244
|
+
return { status, details: checks };
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Instructions
|
|
249
|
+
|
|
250
|
+
### Step 1: Implement Circuit Breaker
|
|
251
|
+
Wrap Vercel calls with circuit breaker.
|
|
252
|
+
|
|
253
|
+
### Step 2: Add Idempotency Keys
|
|
254
|
+
Generate deterministic keys for operations.
|
|
255
|
+
|
|
256
|
+
### Step 3: Configure Bulkheads
|
|
257
|
+
Separate queues for different priorities.
|
|
258
|
+
|
|
259
|
+
### Step 4: Set Up Dead Letter Queue
|
|
260
|
+
Handle permanent failures gracefully.
|
|
261
|
+
|
|
262
|
+
## Output
|
|
263
|
+
- Circuit breaker protecting Vercel calls
|
|
264
|
+
- Idempotency preventing duplicates
|
|
265
|
+
- Bulkhead isolation implemented
|
|
266
|
+
- DLQ for failed operations
|
|
267
|
+
|
|
268
|
+
## Error Handling
|
|
269
|
+
| Issue | Cause | Solution |
|
|
270
|
+
|-------|-------|----------|
|
|
271
|
+
| Circuit stays open | Threshold too low | Adjust error percentage |
|
|
272
|
+
| Duplicate operations | Missing idempotency | Add idempotency key |
|
|
273
|
+
| Queue full | Rate too high | Increase concurrency |
|
|
274
|
+
| DLQ growing | Persistent failures | Investigate root cause |
|
|
275
|
+
|
|
276
|
+
## Examples
|
|
277
|
+
|
|
278
|
+
### Quick Circuit Check
|
|
279
|
+
```typescript
|
|
280
|
+
const state = vercelBreaker.stats().state;
|
|
281
|
+
console.log('Vercel circuit:', state);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Resources
|
|
285
|
+
- [Circuit Breaker Pattern](https://martinfowler.com/bliki/CircuitBreaker.html)
|
|
286
|
+
- [Opossum Documentation](https://nodeshift.dev/opossum/)
|
|
287
|
+
- [Vercel Reliability Guide](https://vercel.com/docs/reliability)
|
|
288
|
+
|
|
289
|
+
## Next Steps
|
|
290
|
+
For policy enforcement, see `vercel-policy-guardrails`.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vercel-sdk-patterns
|
|
3
|
+
description: |
|
|
4
|
+
Apply production-ready Vercel SDK patterns for TypeScript and Python.
|
|
5
|
+
Use when implementing Vercel integrations, refactoring SDK usage,
|
|
6
|
+
or establishing team coding standards for Vercel.
|
|
7
|
+
Trigger with phrases like "vercel SDK patterns", "vercel best practices",
|
|
8
|
+
"vercel code patterns", "idiomatic vercel".
|
|
9
|
+
allowed-tools: Read, Write, Edit
|
|
10
|
+
version: 1.0.0
|
|
11
|
+
license: MIT
|
|
12
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Vercel SDK Patterns
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
Production-ready patterns for Vercel SDK usage in TypeScript and Python.
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
- Completed `vercel-install-auth` setup
|
|
22
|
+
- Familiarity with async/await patterns
|
|
23
|
+
- Understanding of error handling best practices
|
|
24
|
+
|
|
25
|
+
## Instructions
|
|
26
|
+
|
|
27
|
+
### Step 1: Implement Singleton Pattern (Recommended)
|
|
28
|
+
```typescript
|
|
29
|
+
// src/vercel/client.ts
|
|
30
|
+
import { VercelClient } from 'vercel';
|
|
31
|
+
|
|
32
|
+
let instance: VercelClient | null = null;
|
|
33
|
+
|
|
34
|
+
export function getVercelClient(): VercelClient {
|
|
35
|
+
if (!instance) {
|
|
36
|
+
instance = new VercelClient({
|
|
37
|
+
apiKey: process.env.VERCEL_API_KEY!,
|
|
38
|
+
// Additional options
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return instance;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 2: Add Error Handling Wrapper
|
|
46
|
+
```typescript
|
|
47
|
+
import { VercelError } from 'vercel';
|
|
48
|
+
|
|
49
|
+
async function safeVercelCall<T>(
|
|
50
|
+
operation: () => Promise<T>
|
|
51
|
+
): Promise<{ data: T | null; error: Error | null }> {
|
|
52
|
+
try {
|
|
53
|
+
const data = await operation();
|
|
54
|
+
return { data, error: null };
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (err instanceof VercelError) {
|
|
57
|
+
console.error({
|
|
58
|
+
code: err.code,
|
|
59
|
+
message: err.message,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return { data: null, error: err as Error };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 3: Implement Retry Logic
|
|
68
|
+
```typescript
|
|
69
|
+
async function withRetry<T>(
|
|
70
|
+
operation: () => Promise<T>,
|
|
71
|
+
maxRetries = 3,
|
|
72
|
+
backoffMs = 1000
|
|
73
|
+
): Promise<T> {
|
|
74
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
75
|
+
try {
|
|
76
|
+
return await operation();
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (attempt === maxRetries) throw err;
|
|
79
|
+
const delay = backoffMs * Math.pow(2, attempt - 1);
|
|
80
|
+
await new Promise(r => setTimeout(r, delay));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error('Unreachable');
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Output
|
|
88
|
+
- Type-safe client singleton
|
|
89
|
+
- Robust error handling with structured logging
|
|
90
|
+
- Automatic retry with exponential backoff
|
|
91
|
+
- Runtime validation for API responses
|
|
92
|
+
|
|
93
|
+
## Error Handling
|
|
94
|
+
| Pattern | Use Case | Benefit |
|
|
95
|
+
|---------|----------|---------|
|
|
96
|
+
| Safe wrapper | All API calls | Prevents uncaught exceptions |
|
|
97
|
+
| Retry logic | Transient failures | Improves reliability |
|
|
98
|
+
| Type guards | Response validation | Catches API changes |
|
|
99
|
+
| Logging | All operations | Debugging and monitoring |
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
### Factory Pattern (Multi-tenant)
|
|
104
|
+
```typescript
|
|
105
|
+
const clients = new Map<string, VercelClient>();
|
|
106
|
+
|
|
107
|
+
export function getClientForTenant(tenantId: string): VercelClient {
|
|
108
|
+
if (!clients.has(tenantId)) {
|
|
109
|
+
const apiKey = getTenantApiKey(tenantId);
|
|
110
|
+
clients.set(tenantId, new VercelClient({ apiKey }));
|
|
111
|
+
}
|
|
112
|
+
return clients.get(tenantId)!;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Python Context Manager
|
|
117
|
+
```python
|
|
118
|
+
from contextlib import asynccontextmanager
|
|
119
|
+
from None import VercelClient
|
|
120
|
+
|
|
121
|
+
@asynccontextmanager
|
|
122
|
+
async def get_vercel_client():
|
|
123
|
+
client = VercelClient()
|
|
124
|
+
try:
|
|
125
|
+
yield client
|
|
126
|
+
finally:
|
|
127
|
+
await client.close()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Zod Validation
|
|
131
|
+
```typescript
|
|
132
|
+
import { z } from 'zod';
|
|
133
|
+
|
|
134
|
+
const vercelResponseSchema = z.object({
|
|
135
|
+
id: z.string(),
|
|
136
|
+
status: z.enum(['active', 'inactive']),
|
|
137
|
+
createdAt: z.string().datetime(),
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Resources
|
|
142
|
+
- [Vercel SDK Reference](https://vercel.com/docs/sdk)
|
|
143
|
+
- [Vercel API Types](https://vercel.com/docs/types)
|
|
144
|
+
- [Zod Documentation](https://zod.dev/)
|
|
145
|
+
|
|
146
|
+
## Next Steps
|
|
147
|
+
Apply patterns in `vercel-core-workflow-a` for real-world usage.
|