@intentsolutionsio/vercel-pack 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +67 -44
- package/package.json +4 -4
- package/skills/vercel-advanced-troubleshooting/SKILL.md +185 -195
- package/skills/vercel-advanced-troubleshooting/references/errors.md +11 -0
- package/skills/vercel-advanced-troubleshooting/references/evidence-collection-framework.md +34 -0
- package/skills/vercel-advanced-troubleshooting/references/examples.md +11 -0
- package/skills/vercel-advanced-troubleshooting/references/systematic-isolation.md +56 -0
- package/skills/vercel-advanced-troubleshooting/references/timing-analysis.md +35 -0
- package/skills/vercel-architecture-variants/SKILL.md +227 -216
- package/skills/vercel-architecture-variants/references/errors.md +11 -0
- package/skills/vercel-architecture-variants/references/examples.md +12 -0
- package/skills/vercel-architecture-variants/references/variant-a-monolith-(simple).md +44 -0
- package/skills/vercel-architecture-variants/references/variant-b-service-layer-(moderate).md +72 -0
- package/skills/vercel-architecture-variants/references/variant-c-microservice-(complex).md +81 -0
- package/skills/vercel-ci-integration/SKILL.md +183 -73
- package/skills/vercel-ci-integration/references/errors.md +10 -0
- package/skills/vercel-ci-integration/references/examples.md +36 -0
- package/skills/vercel-ci-integration/references/implementation.md +54 -0
- package/skills/vercel-common-errors/SKILL.md +164 -60
- package/skills/vercel-common-errors/references/errors.md +53 -0
- package/skills/vercel-common-errors/references/examples.md +23 -0
- package/skills/vercel-cost-tuning/SKILL.md +158 -145
- package/skills/vercel-cost-tuning/references/cost-estimation.md +34 -0
- package/skills/vercel-cost-tuning/references/cost-reduction-strategies.md +40 -0
- package/skills/vercel-cost-tuning/references/errors.md +11 -0
- package/skills/vercel-cost-tuning/references/examples.md +15 -0
- package/skills/vercel-data-handling/SKILL.md +202 -155
- package/skills/vercel-data-handling/references/errors.md +11 -0
- package/skills/vercel-data-handling/references/examples.md +27 -0
- package/skills/vercel-data-handling/references/implementation.md +223 -0
- package/skills/vercel-debug-bundle/SKILL.md +163 -67
- package/skills/vercel-debug-bundle/references/errors.md +12 -0
- package/skills/vercel-debug-bundle/references/examples.md +24 -0
- package/skills/vercel-debug-bundle/references/implementation.md +54 -0
- package/skills/vercel-deploy-integration/SKILL.md +163 -156
- package/skills/vercel-deploy-integration/references/errors.md +11 -0
- package/skills/vercel-deploy-integration/references/examples.md +21 -0
- package/skills/vercel-deploy-integration/references/google-cloud-run.md +36 -0
- package/skills/vercel-deploy-integration/references/vercel-deployment.md +35 -0
- package/skills/vercel-deploy-preview/SKILL.md +164 -39
- package/skills/vercel-edge-functions/SKILL.md +185 -37
- package/skills/vercel-enterprise-rbac/SKILL.md +185 -170
- package/skills/vercel-enterprise-rbac/references/errors.md +11 -0
- package/skills/vercel-enterprise-rbac/references/examples.md +12 -0
- package/skills/vercel-enterprise-rbac/references/role-implementation.md +33 -0
- package/skills/vercel-enterprise-rbac/references/sso-integration.md +35 -0
- package/skills/vercel-hello-world/SKILL.md +141 -55
- package/skills/vercel-incident-runbook/SKILL.md +186 -138
- package/skills/vercel-incident-runbook/references/errors.md +11 -0
- package/skills/vercel-incident-runbook/references/examples.md +10 -0
- package/skills/vercel-incident-runbook/references/immediate-actions-by-error-type.md +41 -0
- package/skills/vercel-install-auth/SKILL.md +130 -53
- package/skills/vercel-known-pitfalls/SKILL.md +235 -233
- package/skills/vercel-known-pitfalls/references/errors.md +11 -0
- package/skills/vercel-known-pitfalls/references/examples.md +12 -0
- package/skills/vercel-load-scale/SKILL.md +197 -204
- package/skills/vercel-load-scale/references/capacity-planning.md +47 -0
- package/skills/vercel-load-scale/references/errors.md +11 -0
- package/skills/vercel-load-scale/references/examples.md +26 -0
- package/skills/vercel-load-scale/references/load-testing-with-k6.md +59 -0
- package/skills/vercel-load-scale/references/scaling-patterns.md +65 -0
- package/skills/vercel-local-dev-loop/SKILL.md +159 -71
- package/skills/vercel-local-dev-loop/references/errors.md +11 -0
- package/skills/vercel-local-dev-loop/references/examples.md +21 -0
- package/skills/vercel-local-dev-loop/references/implementation.md +60 -0
- package/skills/vercel-migration-deep-dive/SKILL.md +202 -187
- package/skills/vercel-migration-deep-dive/references/errors.md +11 -0
- package/skills/vercel-migration-deep-dive/references/examples.md +12 -0
- package/skills/vercel-migration-deep-dive/references/implementation-plan.md +80 -0
- package/skills/vercel-migration-deep-dive/references/pre-migration-assessment.md +39 -0
- package/skills/vercel-multi-env-setup/SKILL.md +167 -164
- package/skills/vercel-multi-env-setup/references/configuration-structure.md +59 -0
- package/skills/vercel-multi-env-setup/references/errors.md +11 -0
- package/skills/vercel-multi-env-setup/references/examples.md +11 -0
- package/skills/vercel-observability/SKILL.md +205 -195
- package/skills/vercel-observability/references/alert-configuration.md +40 -0
- package/skills/vercel-observability/references/errors.md +11 -0
- package/skills/vercel-observability/references/examples.md +13 -0
- package/skills/vercel-observability/references/metrics-collection.md +65 -0
- package/skills/vercel-performance-tuning/SKILL.md +212 -156
- package/skills/vercel-performance-tuning/references/caching-strategy.md +49 -0
- package/skills/vercel-performance-tuning/references/errors.md +11 -0
- package/skills/vercel-performance-tuning/references/examples.md +13 -0
- package/skills/vercel-policy-guardrails/SKILL.md +276 -193
- package/skills/vercel-policy-guardrails/references/errors.md +11 -0
- package/skills/vercel-policy-guardrails/references/eslint-rules.md +46 -0
- package/skills/vercel-policy-guardrails/references/examples.md +10 -0
- package/skills/vercel-prod-checklist/SKILL.md +219 -94
- package/skills/vercel-prod-checklist/references/errors.md +11 -0
- package/skills/vercel-prod-checklist/references/examples.md +25 -0
- package/skills/vercel-prod-checklist/references/implementation.md +60 -0
- package/skills/vercel-rate-limits/SKILL.md +187 -100
- package/skills/vercel-rate-limits/references/errors.md +11 -0
- package/skills/vercel-rate-limits/references/examples.md +46 -0
- package/skills/vercel-rate-limits/references/implementation.md +66 -0
- package/skills/vercel-reference-architecture/SKILL.md +226 -180
- package/skills/vercel-reference-architecture/references/errors.md +11 -0
- package/skills/vercel-reference-architecture/references/examples.md +13 -0
- package/skills/vercel-reference-architecture/references/key-components.md +65 -0
- package/skills/vercel-reference-architecture/references/project-structure.md +40 -0
- package/skills/vercel-reliability-patterns/SKILL.md +272 -211
- package/skills/vercel-reliability-patterns/references/circuit-breaker.md +36 -0
- package/skills/vercel-reliability-patterns/references/dead-letter-queue.md +48 -0
- package/skills/vercel-reliability-patterns/references/errors.md +11 -0
- package/skills/vercel-reliability-patterns/references/examples.md +11 -0
- package/skills/vercel-reliability-patterns/references/idempotency-keys.md +36 -0
- package/skills/vercel-sdk-patterns/SKILL.md +264 -92
- package/skills/vercel-sdk-patterns/references/errors.md +11 -0
- package/skills/vercel-sdk-patterns/references/examples.md +45 -0
- package/skills/vercel-sdk-patterns/references/implementation.md +67 -0
- package/skills/vercel-security-basics/SKILL.md +186 -96
- package/skills/vercel-security-basics/references/errors.md +10 -0
- package/skills/vercel-security-basics/references/examples.md +70 -0
- package/skills/vercel-security-basics/references/implementation.md +39 -0
- package/skills/vercel-upgrade-migration/SKILL.md +167 -67
- package/skills/vercel-upgrade-migration/references/errors.md +10 -0
- package/skills/vercel-upgrade-migration/references/examples.md +51 -0
- package/skills/vercel-upgrade-migration/references/implementation.md +29 -0
- package/skills/vercel-webhooks-events/SKILL.md +208 -132
- package/skills/vercel-webhooks-events/references/errors.md +11 -0
- package/skills/vercel-webhooks-events/references/event-handler-pattern.md +37 -0
- package/skills/vercel-webhooks-events/references/examples.md +16 -0
- package/skills/vercel-webhooks-events/references/signature-verification.md +33 -0
|
@@ -1,290 +1,351 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vercel-reliability-patterns
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
description: 'Implement reliability patterns for Vercel deployments including circuit
|
|
4
|
+
breakers, retry logic, and graceful degradation.
|
|
5
|
+
|
|
6
|
+
Use when building fault-tolerant serverless functions, implementing retry strategies,
|
|
7
|
+
|
|
6
8
|
or adding resilience to production Vercel services.
|
|
9
|
+
|
|
7
10
|
Trigger with phrases like "vercel reliability", "vercel circuit breaker",
|
|
8
|
-
|
|
11
|
+
|
|
12
|
+
"vercel resilience", "vercel fallback", "vercel graceful degradation".
|
|
13
|
+
|
|
14
|
+
'
|
|
9
15
|
allowed-tools: Read, Write, Edit
|
|
10
16
|
version: 1.0.0
|
|
11
17
|
license: MIT
|
|
12
18
|
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
19
|
+
tags:
|
|
20
|
+
- saas
|
|
21
|
+
- vercel
|
|
22
|
+
- reliability
|
|
23
|
+
- resilience
|
|
24
|
+
- patterns
|
|
25
|
+
compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
|
|
13
26
|
---
|
|
14
|
-
|
|
15
27
|
# Vercel Reliability Patterns
|
|
16
28
|
|
|
17
29
|
## Overview
|
|
18
|
-
|
|
30
|
+
|
|
31
|
+
Build fault-tolerant Vercel deployments with circuit breakers, retry logic, graceful degradation, and instant rollback integration. Addresses reliability at two levels: function-level resilience (protecting against dependency failures) and deployment-level resilience (protecting against bad deploys).
|
|
19
32
|
|
|
20
33
|
## Prerequisites
|
|
21
|
-
- Understanding of circuit breaker pattern
|
|
22
|
-
- opossum or similar library installed
|
|
23
|
-
- Queue infrastructure for DLQ
|
|
24
|
-
- Caching layer for fallbacks
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
- Vercel project deployed to production
|
|
36
|
+
- Understanding of failure modes in serverless
|
|
37
|
+
- External dependencies (databases, APIs) identified
|
|
38
|
+
|
|
39
|
+
## Instructions
|
|
40
|
+
|
|
41
|
+
### Step 1: Circuit Breaker for External Dependencies
|
|
27
42
|
|
|
28
43
|
```typescript
|
|
29
|
-
|
|
44
|
+
// lib/circuit-breaker.ts
|
|
45
|
+
type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
|
|
46
|
+
|
|
47
|
+
class CircuitBreaker {
|
|
48
|
+
private state: CircuitState = 'CLOSED';
|
|
49
|
+
private failures = 0;
|
|
50
|
+
private lastFailure = 0;
|
|
51
|
+
private readonly threshold: number;
|
|
52
|
+
private readonly resetTimeMs: number;
|
|
53
|
+
|
|
54
|
+
constructor(threshold = 5, resetTimeMs = 30000) {
|
|
55
|
+
this.threshold = threshold;
|
|
56
|
+
this.resetTimeMs = resetTimeMs;
|
|
57
|
+
}
|
|
30
58
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
async call<T>(fn: () => Promise<T>, fallback: () => T): Promise<T> {
|
|
60
|
+
if (this.state === 'OPEN') {
|
|
61
|
+
if (Date.now() - this.lastFailure > this.resetTimeMs) {
|
|
62
|
+
this.state = 'HALF_OPEN';
|
|
63
|
+
} else {
|
|
64
|
+
console.warn('Circuit OPEN — returning fallback');
|
|
65
|
+
return fallback();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const result = await fn();
|
|
71
|
+
this.onSuccess();
|
|
72
|
+
return result;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.onFailure();
|
|
75
|
+
console.error('Circuit breaker caught error:', error);
|
|
76
|
+
return fallback();
|
|
77
|
+
}
|
|
38
78
|
}
|
|
39
|
-
);
|
|
40
79
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
80
|
+
private onSuccess(): void {
|
|
81
|
+
this.failures = 0;
|
|
82
|
+
this.state = 'CLOSED';
|
|
83
|
+
}
|
|
46
84
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
85
|
+
private onFailure(): void {
|
|
86
|
+
this.failures++;
|
|
87
|
+
this.lastFailure = Date.now();
|
|
88
|
+
if (this.failures >= this.threshold) {
|
|
89
|
+
this.state = 'OPEN';
|
|
90
|
+
console.warn(`Circuit OPENED after ${this.failures} failures`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
50
94
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
95
|
+
// Usage in a serverless function:
|
|
96
|
+
const dbCircuit = new CircuitBreaker(3, 30000);
|
|
54
97
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
98
|
+
export default async function handler(req, res) {
|
|
99
|
+
const users = await dbCircuit.call(
|
|
100
|
+
() => db.user.findMany({ take: 10 }),
|
|
101
|
+
() => [] // Fallback: empty array when DB is down
|
|
102
|
+
);
|
|
103
|
+
res.json({ users, degraded: users.length === 0 });
|
|
58
104
|
}
|
|
59
105
|
```
|
|
60
106
|
|
|
61
|
-
|
|
107
|
+
**Important for serverless:** Circuit breaker state lives in a single function instance. Different instances have independent circuits. For global circuit state, use Vercel KV or Edge Config.
|
|
108
|
+
|
|
109
|
+
### Step 2: Retry with Exponential Backoff
|
|
62
110
|
|
|
63
111
|
```typescript
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
params: Record<string, any>
|
|
71
|
-
): string {
|
|
72
|
-
const data = JSON.stringify({ operation, params });
|
|
73
|
-
return crypto.createHash('sha256').update(data).digest('hex');
|
|
112
|
+
// lib/retry.ts
|
|
113
|
+
interface RetryOptions {
|
|
114
|
+
maxRetries?: number;
|
|
115
|
+
baseDelayMs?: number;
|
|
116
|
+
maxDelayMs?: number;
|
|
117
|
+
retryOn?: (error: unknown) => boolean;
|
|
74
118
|
}
|
|
75
119
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
120
|
+
async function withRetry<T>(
|
|
121
|
+
fn: () => Promise<T>,
|
|
122
|
+
options: RetryOptions = {}
|
|
123
|
+
): Promise<T> {
|
|
124
|
+
const { maxRetries = 3, baseDelayMs = 200, maxDelayMs = 5000, retryOn } = options;
|
|
79
125
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
126
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
127
|
+
try {
|
|
128
|
+
return await fn();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (attempt === maxRetries) throw error;
|
|
131
|
+
if (retryOn && !retryOn(error)) throw error;
|
|
132
|
+
|
|
133
|
+
const delay = Math.min(
|
|
134
|
+
baseDelayMs * Math.pow(2, attempt) + Math.random() * 200,
|
|
135
|
+
maxDelayMs
|
|
136
|
+
);
|
|
137
|
+
await new Promise(r => setTimeout(r, delay));
|
|
84
138
|
}
|
|
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
139
|
}
|
|
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);
|
|
140
|
+
throw new Error('Unreachable');
|
|
113
141
|
}
|
|
114
142
|
|
|
115
|
-
// Usage
|
|
116
|
-
await
|
|
117
|
-
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
// Usage:
|
|
144
|
+
const data = await withRetry(
|
|
145
|
+
() => fetch('https://api.example.com/data').then(r => {
|
|
146
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
147
|
+
return r.json();
|
|
148
|
+
}),
|
|
149
|
+
{
|
|
150
|
+
maxRetries: 3,
|
|
151
|
+
retryOn: (err) => {
|
|
152
|
+
// Only retry on network errors and 5xx, not 4xx
|
|
153
|
+
if (err instanceof TypeError) return true; // network error
|
|
154
|
+
return err.message?.includes('5');
|
|
155
|
+
},
|
|
156
|
+
}
|
|
122
157
|
);
|
|
123
158
|
```
|
|
124
159
|
|
|
125
|
-
|
|
160
|
+
### Step 3: Graceful Degradation with Stale Cache
|
|
126
161
|
|
|
127
162
|
```typescript
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
163
|
+
// api/products.ts — serve stale data when primary source is down
|
|
164
|
+
import { get, set } from '@vercel/kv';
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
enabled: boolean;
|
|
155
|
-
data: any;
|
|
156
|
-
staleness: 'fresh' | 'stale' | 'very_stale';
|
|
157
|
-
}
|
|
166
|
+
export default async function handler(req, res) {
|
|
167
|
+
const cacheKey = 'products:latest';
|
|
158
168
|
|
|
159
|
-
async function withVercelFallback<T>(
|
|
160
|
-
fn: () => Promise<T>,
|
|
161
|
-
fallbackFn: () => Promise<T>
|
|
162
|
-
): Promise<{ data: T; fallback: boolean }> {
|
|
163
169
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
// Try primary data source
|
|
171
|
+
const freshData = await fetchProductsFromDB();
|
|
172
|
+
|
|
173
|
+
// Update cache with fresh data
|
|
174
|
+
await set(cacheKey, JSON.stringify(freshData), { ex: 3600 });
|
|
175
|
+
|
|
176
|
+
res.setHeader('x-data-source', 'live');
|
|
177
|
+
res.json(freshData);
|
|
168
178
|
} catch (error) {
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
179
|
+
// Primary failed — serve stale cache
|
|
180
|
+
const cachedData = await get(cacheKey);
|
|
181
|
+
|
|
182
|
+
if (cachedData) {
|
|
183
|
+
console.warn('Serving stale cache — primary source unavailable');
|
|
184
|
+
res.setHeader('x-data-source', 'cache-stale');
|
|
185
|
+
res.json(JSON.parse(cachedData as string));
|
|
186
|
+
} else {
|
|
187
|
+
// No cache available — return degraded response
|
|
188
|
+
res.setHeader('x-data-source', 'degraded');
|
|
189
|
+
res.status(503).json({
|
|
190
|
+
error: 'Service temporarily unavailable',
|
|
191
|
+
degraded: true,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
172
194
|
}
|
|
173
195
|
}
|
|
174
196
|
```
|
|
175
197
|
|
|
176
|
-
|
|
198
|
+
### Step 4: Idempotency Keys for Mutations
|
|
177
199
|
|
|
178
200
|
```typescript
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
201
|
+
// api/orders/route.ts — idempotent order creation
|
|
202
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
203
|
+
import { db } from '@/lib/db';
|
|
204
|
+
|
|
205
|
+
export async function POST(request: NextRequest) {
|
|
206
|
+
const idempotencyKey = request.headers.get('idempotency-key');
|
|
207
|
+
if (!idempotencyKey) {
|
|
208
|
+
return NextResponse.json(
|
|
209
|
+
{ error: 'idempotency-key header required' },
|
|
210
|
+
{ status: 400 }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
187
213
|
|
|
188
|
-
|
|
189
|
-
|
|
214
|
+
// Check if this request was already processed
|
|
215
|
+
const existing = await db.idempotencyRecord.findUnique({
|
|
216
|
+
where: { key: idempotencyKey },
|
|
217
|
+
});
|
|
190
218
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
219
|
+
if (existing) {
|
|
220
|
+
// Return the cached response — same status and body
|
|
221
|
+
return NextResponse.json(JSON.parse(existing.responseBody), {
|
|
222
|
+
status: existing.responseStatus,
|
|
223
|
+
headers: { 'x-idempotent-replay': 'true' },
|
|
196
224
|
});
|
|
197
225
|
}
|
|
198
226
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
227
|
+
// Process the order
|
|
228
|
+
const body = await request.json();
|
|
229
|
+
const order = await db.order.create({ data: body });
|
|
202
230
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
entry.attempts++;
|
|
209
|
-
entry.lastAttempt = new Date();
|
|
231
|
+
// Cache the response for idempotency
|
|
232
|
+
const responseBody = JSON.stringify({ order });
|
|
233
|
+
await db.idempotencyRecord.create({
|
|
234
|
+
data: { key: idempotencyKey, responseStatus: 201, responseBody },
|
|
235
|
+
});
|
|
210
236
|
|
|
211
|
-
|
|
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
|
-
}
|
|
237
|
+
return NextResponse.json({ order }, { status: 201 });
|
|
220
238
|
}
|
|
221
239
|
```
|
|
222
240
|
|
|
223
|
-
|
|
241
|
+
### Step 5: Health Check with Dependency Status
|
|
224
242
|
|
|
225
243
|
```typescript
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
// api/health/route.ts
|
|
245
|
+
export const dynamic = 'force-dynamic';
|
|
246
|
+
|
|
247
|
+
interface HealthCheck {
|
|
248
|
+
name: string;
|
|
249
|
+
check: () => Promise<boolean>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const checks: HealthCheck[] = [
|
|
253
|
+
{
|
|
254
|
+
name: 'database',
|
|
255
|
+
check: async () => {
|
|
256
|
+
await db.$queryRaw`SELECT 1`;
|
|
257
|
+
return true;
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'cache',
|
|
262
|
+
check: async () => {
|
|
263
|
+
await kv.ping();
|
|
264
|
+
return true;
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: 'external-api',
|
|
269
|
+
check: async () => {
|
|
270
|
+
const r = await fetch('https://api.example.com/health', { signal: AbortSignal.timeout(3000) });
|
|
271
|
+
return r.ok;
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
export async function GET() {
|
|
277
|
+
const results: Record<string, 'ok' | 'error'> = {};
|
|
278
|
+
|
|
279
|
+
await Promise.all(
|
|
280
|
+
checks.map(async ({ name, check }) => {
|
|
281
|
+
try {
|
|
282
|
+
await check();
|
|
283
|
+
results[name] = 'ok';
|
|
284
|
+
} catch {
|
|
285
|
+
results[name] = 'error';
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const healthy = Object.values(results).every(v => v === 'ok');
|
|
291
|
+
return Response.json(
|
|
292
|
+
{ status: healthy ? 'healthy' : 'degraded', checks: results },
|
|
293
|
+
{ status: healthy ? 200 : 503 }
|
|
294
|
+
);
|
|
245
295
|
}
|
|
246
296
|
```
|
|
247
297
|
|
|
248
|
-
|
|
298
|
+
### Step 6: Deployment-Level Resilience
|
|
249
299
|
|
|
250
|
-
|
|
251
|
-
|
|
300
|
+
```bash
|
|
301
|
+
# Instant rollback on health check failure (CI integration)
|
|
302
|
+
DEPLOY_URL=$(vercel --prod)
|
|
303
|
+
HEALTH=$(curl -s -o /dev/null -w "%{http_code}" "$DEPLOY_URL/api/health")
|
|
252
304
|
|
|
253
|
-
|
|
254
|
-
|
|
305
|
+
if [ "$HEALTH" != "200" ]; then
|
|
306
|
+
echo "Health check failed ($HEALTH) — rolling back"
|
|
307
|
+
vercel rollback
|
|
308
|
+
exit 1
|
|
309
|
+
fi
|
|
310
|
+
echo "Deployment healthy"
|
|
311
|
+
```
|
|
255
312
|
|
|
256
|
-
|
|
257
|
-
Separate queues for different priorities.
|
|
313
|
+
## Reliability Patterns Summary
|
|
258
314
|
|
|
259
|
-
|
|
260
|
-
|
|
315
|
+
| Pattern | Protects Against | Vercel Implementation |
|
|
316
|
+
|---------|-----------------|----------------------|
|
|
317
|
+
| Circuit breaker | Dependency degradation | In-function state or Edge Config |
|
|
318
|
+
| Retry + backoff | Transient failures | withRetry wrapper |
|
|
319
|
+
| Stale cache | Primary source outage | Vercel KV with TTL |
|
|
320
|
+
| Idempotency | Duplicate mutations | Database record per request |
|
|
321
|
+
| Health checks | Bad deployments | `/api/health` + rollback automation |
|
|
322
|
+
| Instant rollback | Deployment regression | `vercel rollback` in CI |
|
|
261
323
|
|
|
262
324
|
## Output
|
|
263
|
-
- Circuit breaker protecting Vercel calls
|
|
264
|
-
- Idempotency preventing duplicates
|
|
265
|
-
- Bulkhead isolation implemented
|
|
266
|
-
- DLQ for failed operations
|
|
267
325
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
| Queue full | Rate too high | Increase concurrency |
|
|
274
|
-
| DLQ growing | Persistent failures | Investigate root cause |
|
|
326
|
+
- Circuit breaker protecting all external dependency calls
|
|
327
|
+
- Retry logic with exponential backoff for transient failures
|
|
328
|
+
- Graceful degradation serving stale data when primary fails
|
|
329
|
+
- Idempotency preventing duplicate mutations
|
|
330
|
+
- Automated health check + rollback pipeline
|
|
275
331
|
|
|
276
|
-
##
|
|
332
|
+
## Error Handling
|
|
277
333
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
334
|
+
| Error | Cause | Solution |
|
|
335
|
+
|-------|-------|----------|
|
|
336
|
+
| Circuit opens too aggressively | Threshold too low | Increase failure threshold (e.g., 5 → 10) |
|
|
337
|
+
| Retry causes duplicate side effects | No idempotency | Add idempotency-key to mutation endpoints |
|
|
338
|
+
| Stale cache expired | TTL too short or never populated | Increase TTL, seed cache on deploy |
|
|
339
|
+
| Health check false positive | Timeout too short | Increase AbortSignal timeout to 5s |
|
|
340
|
+
| Rollback reverts good deployment | Flaky health check | Add retry to health check before rollback |
|
|
283
341
|
|
|
284
342
|
## Resources
|
|
343
|
+
|
|
344
|
+
- [Vercel Instant Rollback](https://vercel.com/docs/instant-rollback)
|
|
345
|
+
- [Vercel KV](https://vercel.com/docs/storage/vercel-kv)
|
|
346
|
+
- [Edge Config](https://vercel.com/docs/edge-config)
|
|
285
347
|
- [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
348
|
|
|
289
349
|
## Next Steps
|
|
290
|
-
|
|
350
|
+
|
|
351
|
+
For policy guardrails, see `vercel-policy-guardrails`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Circuit Breaker
|
|
2
|
+
|
|
3
|
+
## Circuit Breaker
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import CircuitBreaker from 'opossum';
|
|
7
|
+
|
|
8
|
+
const vercelBreaker = new CircuitBreaker(
|
|
9
|
+
async (operation: () => Promise<any>) => operation(),
|
|
10
|
+
{
|
|
11
|
+
timeout: 10000,
|
|
12
|
+
errorThresholdPercentage: 50,
|
|
13
|
+
resetTimeout: 30000,
|
|
14
|
+
volumeThreshold: 10,
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// Events
|
|
19
|
+
vercelBreaker.on('open', () => {
|
|
20
|
+
console.warn('Vercel circuit OPEN - requests failing fast');
|
|
21
|
+
alertOps('Vercel circuit breaker opened');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
vercelBreaker.on('halfOpen', () => {
|
|
25
|
+
console.info('Vercel circuit HALF-OPEN - testing recovery');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
vercelBreaker.on('close', () => {
|
|
29
|
+
console.info('Vercel circuit CLOSED - normal operation');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Usage
|
|
33
|
+
async function safeVercelCall<T>(fn: () => Promise<T>): Promise<T> {
|
|
34
|
+
return vercelBreaker.fire(fn);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Dead Letter Queue
|
|
2
|
+
|
|
3
|
+
## Dead Letter Queue
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
interface DeadLetterEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
operation: string;
|
|
9
|
+
payload: any;
|
|
10
|
+
error: string;
|
|
11
|
+
attempts: number;
|
|
12
|
+
lastAttempt: Date;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class VercelDeadLetterQueue {
|
|
16
|
+
private queue: DeadLetterEntry[] = [];
|
|
17
|
+
|
|
18
|
+
add(entry: Omit<DeadLetterEntry, 'id' | 'lastAttempt'>): void {
|
|
19
|
+
this.queue.push({
|
|
20
|
+
...entry,
|
|
21
|
+
id: uuidv4(),
|
|
22
|
+
lastAttempt: new Date(),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async processOne(): Promise<boolean> {
|
|
27
|
+
const entry = this.queue.shift();
|
|
28
|
+
if (!entry) return false;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await vercelCliententry.operation;
|
|
32
|
+
console.log(`DLQ: Successfully reprocessed ${entry.id}`);
|
|
33
|
+
return true;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
entry.attempts++;
|
|
36
|
+
entry.lastAttempt = new Date();
|
|
37
|
+
|
|
38
|
+
if (entry.attempts < 5) {
|
|
39
|
+
this.queue.push(entry);
|
|
40
|
+
} else {
|
|
41
|
+
console.error(`DLQ: Giving up on ${entry.id} after 5 attempts`);
|
|
42
|
+
await alertOnPermanentFailure(entry);
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Error Handling Reference
|
|
2
|
+
|
|
3
|
+
| Issue | Cause | Solution |
|
|
4
|
+
|-------|-------|----------|
|
|
5
|
+
| Circuit stays open | Threshold too low | Adjust error percentage |
|
|
6
|
+
| Duplicate operations | Missing idempotency | Add idempotency key |
|
|
7
|
+
| Queue full | Rate too high | Increase concurrency |
|
|
8
|
+
| DLQ growing | Persistent failures | Investigate root cause |
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
### Quick Circuit Check
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const state = vercelBreaker.stats().state;
|
|
7
|
+
console.log('Vercel circuit:', state);
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|