@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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Idempotency Keys
|
|
2
|
+
|
|
3
|
+
## Idempotency Keys
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
|
|
9
|
+
// Generate deterministic idempotency key from input
|
|
10
|
+
function generateIdempotencyKey(
|
|
11
|
+
operation: string,
|
|
12
|
+
params: Record<string, any>
|
|
13
|
+
): string {
|
|
14
|
+
const data = JSON.stringify({ operation, params });
|
|
15
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Or use random key with storage
|
|
19
|
+
class IdempotencyManager {
|
|
20
|
+
private store: Map<string, { key: string; expiresAt: Date }> = new Map();
|
|
21
|
+
|
|
22
|
+
getOrCreate(operationId: string): string {
|
|
23
|
+
const existing = this.store.get(operationId);
|
|
24
|
+
if (existing && existing.expiresAt > new Date()) {
|
|
25
|
+
return existing.key;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const key = uuidv4();
|
|
29
|
+
this.store.set(operationId, {
|
|
30
|
+
key,
|
|
31
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
32
|
+
});
|
|
33
|
+
return key;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
@@ -1,147 +1,319 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vercel-sdk-patterns
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
description: 'Production-ready Vercel REST API patterns with typed fetch wrappers
|
|
4
|
+
and error handling.
|
|
5
|
+
|
|
6
|
+
Use when integrating with the Vercel API programmatically, building deployment tools,
|
|
7
|
+
|
|
8
|
+
or establishing team coding standards for Vercel API calls.
|
|
9
|
+
|
|
10
|
+
Trigger with phrases like "vercel SDK patterns", "vercel API wrapper",
|
|
11
|
+
|
|
12
|
+
"vercel REST API client", "vercel best practices", "idiomatic vercel API".
|
|
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
|
+
- api
|
|
23
|
+
- typescript
|
|
24
|
+
- patterns
|
|
25
|
+
compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
|
|
13
26
|
---
|
|
14
|
-
|
|
15
27
|
# Vercel SDK Patterns
|
|
16
28
|
|
|
17
29
|
## Overview
|
|
18
|
-
|
|
30
|
+
|
|
31
|
+
Build a typed, production-ready wrapper around the Vercel REST API (`api.vercel.com`). Covers authentication, pagination, error handling, retry logic, and common endpoint patterns for deployments, projects, and environment variables.
|
|
19
32
|
|
|
20
33
|
## Prerequisites
|
|
34
|
+
|
|
21
35
|
- Completed `vercel-install-auth` setup
|
|
22
|
-
-
|
|
23
|
-
-
|
|
36
|
+
- TypeScript project with `strict` mode enabled
|
|
37
|
+
- Vercel access token with appropriate scope
|
|
24
38
|
|
|
25
39
|
## Instructions
|
|
26
40
|
|
|
27
|
-
### Step 1:
|
|
41
|
+
### Step 1: Create Typed API Client
|
|
42
|
+
|
|
28
43
|
```typescript
|
|
29
|
-
//
|
|
30
|
-
|
|
44
|
+
// lib/vercel-client.ts
|
|
45
|
+
interface VercelClientConfig {
|
|
46
|
+
token: string;
|
|
47
|
+
teamId?: string;
|
|
48
|
+
baseUrl?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface VercelError {
|
|
52
|
+
error: { code: string; message: string };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class VercelClient {
|
|
56
|
+
private token: string;
|
|
57
|
+
private teamId?: string;
|
|
58
|
+
private baseUrl: string;
|
|
31
59
|
|
|
32
|
-
|
|
60
|
+
constructor(config: VercelClientConfig) {
|
|
61
|
+
this.token = config.token;
|
|
62
|
+
this.teamId = config.teamId;
|
|
63
|
+
this.baseUrl = config.baseUrl ?? 'https://api.vercel.com';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private async request<T>(
|
|
67
|
+
method: string,
|
|
68
|
+
path: string,
|
|
69
|
+
body?: unknown
|
|
70
|
+
): Promise<T> {
|
|
71
|
+
const url = new URL(path, this.baseUrl);
|
|
72
|
+
if (this.teamId) url.searchParams.set('teamId', this.teamId);
|
|
33
73
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
74
|
+
const res = await fetch(url.toString(), {
|
|
75
|
+
method,
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${this.token}`,
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
},
|
|
80
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
39
81
|
});
|
|
82
|
+
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
const err: VercelError = await res.json();
|
|
85
|
+
throw new VercelApiError(res.status, err.error.code, err.error.message);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 204 No Content
|
|
89
|
+
if (res.status === 204) return undefined as T;
|
|
90
|
+
return res.json() as Promise<T>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- Projects ---
|
|
94
|
+
async listProjects(limit = 20) {
|
|
95
|
+
return this.request<{ projects: VercelProject[] }>(
|
|
96
|
+
'GET', `/v9/projects?limit=${limit}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getProject(idOrName: string) {
|
|
101
|
+
return this.request<VercelProject>('GET', `/v9/projects/${idOrName}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- Deployments ---
|
|
105
|
+
async listDeployments(projectId?: string, limit = 20) {
|
|
106
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
107
|
+
if (projectId) params.set('projectId', projectId);
|
|
108
|
+
return this.request<{ deployments: VercelDeployment[] }>(
|
|
109
|
+
'GET', `/v6/deployments?${params}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async getDeployment(idOrUrl: string) {
|
|
114
|
+
return this.request<VercelDeployment>(
|
|
115
|
+
'GET', `/v13/deployments/${idOrUrl}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// --- Environment Variables ---
|
|
120
|
+
async listEnvVars(projectId: string) {
|
|
121
|
+
return this.request<{ envs: VercelEnvVar[] }>(
|
|
122
|
+
'GET', `/v9/projects/${projectId}/env`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async createEnvVar(projectId: string, envVar: CreateEnvVarInput) {
|
|
127
|
+
return this.request<VercelEnvVar>(
|
|
128
|
+
'POST', `/v9/projects/${projectId}/env`, envVar
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- Domains ---
|
|
133
|
+
async listDomains(projectId: string) {
|
|
134
|
+
return this.request<{ domains: VercelDomain[] }>(
|
|
135
|
+
'GET', `/v9/projects/${projectId}/domains`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async addDomain(projectId: string, domain: string) {
|
|
140
|
+
return this.request<VercelDomain>(
|
|
141
|
+
'POST', `/v9/projects/${projectId}/domains`, { name: domain }
|
|
142
|
+
);
|
|
40
143
|
}
|
|
41
|
-
return instance;
|
|
42
144
|
}
|
|
43
145
|
```
|
|
44
146
|
|
|
45
|
-
### Step 2:
|
|
147
|
+
### Step 2: Define Types
|
|
148
|
+
|
|
46
149
|
```typescript
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
150
|
+
// lib/vercel-types.ts
|
|
151
|
+
interface VercelProject {
|
|
152
|
+
id: string;
|
|
153
|
+
name: string;
|
|
154
|
+
framework: string | null;
|
|
155
|
+
latestDeployments: VercelDeployment[];
|
|
156
|
+
targets: Record<string, VercelDeployment>;
|
|
157
|
+
createdAt: number;
|
|
158
|
+
updatedAt: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface VercelDeployment {
|
|
162
|
+
uid: string;
|
|
163
|
+
name: string;
|
|
164
|
+
url: string;
|
|
165
|
+
state: 'BUILDING' | 'ERROR' | 'INITIALIZING' | 'QUEUED' | 'READY' | 'CANCELED';
|
|
166
|
+
target: 'production' | 'preview' | null;
|
|
167
|
+
createdAt: number;
|
|
168
|
+
buildingAt: number;
|
|
169
|
+
ready: number;
|
|
170
|
+
meta: Record<string, string>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
interface VercelEnvVar {
|
|
174
|
+
id: string;
|
|
175
|
+
key: string;
|
|
176
|
+
value: string;
|
|
177
|
+
type: 'system' | 'encrypted' | 'plain' | 'sensitive';
|
|
178
|
+
target: ('production' | 'preview' | 'development')[];
|
|
179
|
+
createdAt: number;
|
|
180
|
+
updatedAt: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface CreateEnvVarInput {
|
|
184
|
+
key: string;
|
|
185
|
+
value: string;
|
|
186
|
+
type: 'encrypted' | 'plain' | 'sensitive';
|
|
187
|
+
target: ('production' | 'preview' | 'development')[];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface VercelDomain {
|
|
191
|
+
name: string;
|
|
192
|
+
verified: boolean;
|
|
193
|
+
redirect: string | null;
|
|
194
|
+
gitBranch: string | null;
|
|
195
|
+
createdAt: number;
|
|
196
|
+
updatedAt: number;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Step 3: Custom Error Class
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// lib/vercel-errors.ts
|
|
204
|
+
class VercelApiError extends Error {
|
|
205
|
+
constructor(
|
|
206
|
+
public status: number,
|
|
207
|
+
public code: string,
|
|
208
|
+
message: string
|
|
209
|
+
) {
|
|
210
|
+
super(`Vercel API ${status}: [${code}] ${message}`);
|
|
211
|
+
this.name = 'VercelApiError';
|
|
63
212
|
}
|
|
213
|
+
|
|
214
|
+
get isRateLimit(): boolean { return this.status === 429; }
|
|
215
|
+
get isNotFound(): boolean { return this.status === 404; }
|
|
216
|
+
get isUnauthorized(): boolean { return this.status === 401 || this.status === 403; }
|
|
64
217
|
}
|
|
65
218
|
```
|
|
66
219
|
|
|
67
|
-
### Step
|
|
220
|
+
### Step 4: Retry with Exponential Backoff
|
|
221
|
+
|
|
68
222
|
```typescript
|
|
223
|
+
// lib/vercel-retry.ts
|
|
69
224
|
async function withRetry<T>(
|
|
70
|
-
|
|
225
|
+
fn: () => Promise<T>,
|
|
71
226
|
maxRetries = 3,
|
|
72
|
-
|
|
227
|
+
baseDelayMs = 1000
|
|
73
228
|
): Promise<T> {
|
|
74
|
-
for (let attempt =
|
|
229
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
75
230
|
try {
|
|
76
|
-
return await
|
|
231
|
+
return await fn();
|
|
77
232
|
} catch (err) {
|
|
78
|
-
if (attempt
|
|
79
|
-
|
|
80
|
-
|
|
233
|
+
if (err instanceof VercelApiError && err.isRateLimit && attempt < maxRetries) {
|
|
234
|
+
const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * 500;
|
|
235
|
+
console.warn(`Rate limited. Retrying in ${Math.round(delay)}ms...`);
|
|
236
|
+
await new Promise(r => setTimeout(r, delay));
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
throw err;
|
|
81
240
|
}
|
|
82
241
|
}
|
|
83
242
|
throw new Error('Unreachable');
|
|
84
243
|
}
|
|
244
|
+
|
|
245
|
+
// Usage:
|
|
246
|
+
// const projects = await withRetry(() => client.listProjects());
|
|
85
247
|
```
|
|
86
248
|
|
|
87
|
-
|
|
88
|
-
- Type-safe client singleton
|
|
89
|
-
- Robust error handling with structured logging
|
|
90
|
-
- Automatic retry with exponential backoff
|
|
91
|
-
- Runtime validation for API responses
|
|
249
|
+
### Step 5: Paginated Fetching
|
|
92
250
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
251
|
+
```typescript
|
|
252
|
+
// lib/vercel-pagination.ts
|
|
253
|
+
async function* paginateDeployments(
|
|
254
|
+
client: VercelClient,
|
|
255
|
+
projectId: string,
|
|
256
|
+
pageSize = 100
|
|
257
|
+
): AsyncGenerator<VercelDeployment[]> {
|
|
258
|
+
let until: number | undefined;
|
|
100
259
|
|
|
101
|
-
|
|
260
|
+
while (true) {
|
|
261
|
+
const params = new URLSearchParams({ limit: String(pageSize) });
|
|
262
|
+
if (until) params.set('until', String(until));
|
|
263
|
+
if (projectId) params.set('projectId', projectId);
|
|
102
264
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const clients = new Map<string, VercelClient>();
|
|
265
|
+
const { deployments } = await client.listDeployments(projectId, pageSize);
|
|
266
|
+
if (deployments.length === 0) break;
|
|
106
267
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
clients.set(tenantId, new VercelClient({ apiKey }));
|
|
268
|
+
yield deployments;
|
|
269
|
+
until = deployments[deployments.length - 1].createdAt;
|
|
270
|
+
if (deployments.length < pageSize) break;
|
|
111
271
|
}
|
|
112
|
-
return clients.get(tenantId)!;
|
|
113
272
|
}
|
|
114
273
|
```
|
|
115
274
|
|
|
116
|
-
|
|
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
|
-
```
|
|
275
|
+
## API Endpoint Quick Reference
|
|
129
276
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
277
|
+
| Operation | Method | Endpoint |
|
|
278
|
+
|-----------|--------|----------|
|
|
279
|
+
| List projects | GET | `/v9/projects` |
|
|
280
|
+
| Get project | GET | `/v9/projects/{idOrName}` |
|
|
281
|
+
| Delete project | DELETE | `/v9/projects/{idOrName}` |
|
|
282
|
+
| List deployments | GET | `/v6/deployments` |
|
|
283
|
+
| Create deployment | POST | `/v13/deployments` |
|
|
284
|
+
| Get deployment | GET | `/v13/deployments/{id}` |
|
|
285
|
+
| Delete deployment | DELETE | `/v13/deployments/{id}` |
|
|
286
|
+
| List env vars | GET | `/v9/projects/{id}/env` |
|
|
287
|
+
| Create env var | POST | `/v9/projects/{id}/env` |
|
|
288
|
+
| Edit env var | PATCH | `/v9/projects/{id}/env/{envId}` |
|
|
289
|
+
| Delete env var | DELETE | `/v9/projects/{id}/env/{envId}` |
|
|
290
|
+
| Add domain | POST | `/v9/projects/{id}/domains` |
|
|
291
|
+
| Verify domain | POST | `/v9/projects/{id}/domains/{domain}/verify` |
|
|
292
|
+
| List teams | GET | `/v2/teams` |
|
|
133
293
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
294
|
+
## Output
|
|
295
|
+
|
|
296
|
+
- Type-safe Vercel API client with full TypeScript coverage
|
|
297
|
+
- Custom error class with semantic helpers (isRateLimit, isNotFound)
|
|
298
|
+
- Automatic retry with exponential backoff for 429 responses
|
|
299
|
+
- Paginated data fetching for large result sets
|
|
300
|
+
|
|
301
|
+
## Error Handling
|
|
302
|
+
|
|
303
|
+
| Error | Status | Solution |
|
|
304
|
+
|-------|--------|----------|
|
|
305
|
+
| `forbidden` | 403 | Token lacks scope — regenerate with correct permissions |
|
|
306
|
+
| `not_found` | 404 | Check project/deployment ID is correct |
|
|
307
|
+
| `rate_limited` | 429 | Use `withRetry()` wrapper — waits and retries automatically |
|
|
308
|
+
| `team_not_found` | 404 | Verify `teamId` parameter matches your team |
|
|
309
|
+
| `bad_request` | 400 | Validate request body matches API schema |
|
|
140
310
|
|
|
141
311
|
## Resources
|
|
142
|
-
|
|
143
|
-
- [Vercel API
|
|
144
|
-
- [
|
|
312
|
+
|
|
313
|
+
- [Vercel REST API Reference](https://vercel.com/docs/rest-api)
|
|
314
|
+
- [Vercel REST API Endpoints](https://vercel.com/docs/rest-api/reference)
|
|
315
|
+
- [Authentication](https://vercel.com/docs/rest-api#creating-an-access-token)
|
|
145
316
|
|
|
146
317
|
## Next Steps
|
|
147
|
-
|
|
318
|
+
|
|
319
|
+
Proceed to `vercel-deploy-preview` for preview deployment workflows.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Error Handling Reference
|
|
2
|
+
|
|
3
|
+
| Pattern | Use Case | Benefit |
|
|
4
|
+
|---------|----------|---------|
|
|
5
|
+
| Safe wrapper | All API calls | Prevents uncaught exceptions |
|
|
6
|
+
| Retry logic | Transient failures | Improves reliability |
|
|
7
|
+
| Type guards | Response validation | Catches API changes |
|
|
8
|
+
| Logging | All operations | Debugging and monitoring |
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
### Factory Pattern (Multi-tenant)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const clients = new Map<string, VercelClient>();
|
|
7
|
+
|
|
8
|
+
export function getClientForTenant(tenantId: string): VercelClient {
|
|
9
|
+
if (!clients.has(tenantId)) {
|
|
10
|
+
const apiKey = getTenantApiKey(tenantId);
|
|
11
|
+
clients.set(tenantId, new VercelClient({ apiKey }));
|
|
12
|
+
}
|
|
13
|
+
return clients.get(tenantId)!;
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Python Context Manager
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from contextlib import asynccontextmanager
|
|
21
|
+
from None import VercelClient
|
|
22
|
+
|
|
23
|
+
@asynccontextmanager
|
|
24
|
+
async def get_vercel_client():
|
|
25
|
+
client = VercelClient()
|
|
26
|
+
try:
|
|
27
|
+
yield client
|
|
28
|
+
finally:
|
|
29
|
+
await client.close()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Zod Validation
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
|
|
37
|
+
const vercelResponseSchema = z.object({
|
|
38
|
+
id: z.string(),
|
|
39
|
+
status: z.enum(['active', 'inactive']),
|
|
40
|
+
createdAt: z.string().datetime(),
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
## Implementation Guide
|
|
2
|
+
|
|
3
|
+
### Step 1: Implement Singleton Pattern (Recommended)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// src/vercel/client.ts
|
|
7
|
+
import { VercelClient } from 'vercel';
|
|
8
|
+
|
|
9
|
+
let instance: VercelClient | null = null;
|
|
10
|
+
|
|
11
|
+
export function getVercelClient(): VercelClient {
|
|
12
|
+
if (!instance) {
|
|
13
|
+
instance = new VercelClient({
|
|
14
|
+
apiKey: process.env.VERCEL_API_KEY!,
|
|
15
|
+
// Additional options
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return instance;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Step 2: Add Error Handling Wrapper
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { VercelError } from 'vercel';
|
|
26
|
+
|
|
27
|
+
async function safeVercelCall<T>(
|
|
28
|
+
operation: () => Promise<T>
|
|
29
|
+
): Promise<{ data: T | null; error: Error | null }> {
|
|
30
|
+
try {
|
|
31
|
+
const data = await operation();
|
|
32
|
+
return { data, error: null };
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err instanceof VercelError) {
|
|
35
|
+
console.error({
|
|
36
|
+
code: err.code,
|
|
37
|
+
message: err.message,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return { data: null, error: err as Error };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 3: Implement Retry Logic
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
async function withRetry<T>(
|
|
49
|
+
operation: () => Promise<T>,
|
|
50
|
+
maxRetries = 3,
|
|
51
|
+
backoffMs = 1000
|
|
52
|
+
): Promise<T> {
|
|
53
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
return await operation();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (attempt === maxRetries) throw err;
|
|
58
|
+
const delay = backoffMs * Math.pow(2, attempt - 1);
|
|
59
|
+
await new Promise(r => setTimeout(r, delay));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error('Unreachable');
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|