alchemy 0.16.7 → 0.16.9

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.
@@ -0,0 +1,163 @@
1
+ import { alchemy } from "../alchemy.js";
2
+ import type { Secret } from "../secret.js";
3
+
4
+ /**
5
+ * Options for Vercel API requests
6
+ */
7
+ export interface VercelApiOptions {
8
+ /**
9
+ * Base URL for API
10
+ */
11
+ baseUrl: string;
12
+
13
+ /**
14
+ * API access token to use (overrides environment variable)
15
+ */
16
+ accessToken?: Secret;
17
+ }
18
+
19
+ /**
20
+ * Creates a VercelApi instance with automatic token validation
21
+ *
22
+ * @param options API options
23
+ * @returns Promise resolving to a VercelApi instance
24
+ */
25
+ export async function createVercelApi(
26
+ options: VercelApiOptions,
27
+ ): Promise<VercelApi> {
28
+ const {
29
+ accessToken = await alchemy.secret.env.VERCEL_ACCESS_TOKEN,
30
+ baseUrl,
31
+ } = options;
32
+
33
+ return new VercelApi({
34
+ baseUrl,
35
+ accessToken,
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Vercel API client using raw fetch
41
+ */
42
+ export class VercelApi {
43
+ /** Base URL for API */
44
+ readonly baseUrl: string;
45
+
46
+ /** API access token */
47
+ readonly accessToken: Secret;
48
+
49
+ /**
50
+ * Create a new API client
51
+ * Use createVercelApi factory function instead of direct constructor
52
+ * for automatic token validation.
53
+ *
54
+ * @param options API options
55
+ */
56
+ constructor(options: VercelApiOptions & { accessToken: Secret }) {
57
+ this.baseUrl = options.baseUrl;
58
+ this.accessToken = options.accessToken;
59
+ }
60
+
61
+ /**
62
+ * Make a request to the API
63
+ *
64
+ * @param path API path (without base URL)
65
+ * @param init Fetch init options
66
+ * @returns Raw Response object from fetch
67
+ */
68
+ async fetch(path: string, init: RequestInit = {}): Promise<Response> {
69
+ // Set up authentication headers
70
+ const headers: Record<string, string> = {
71
+ "Content-Type": "application/json",
72
+ Authorization: `Bearer ${this.accessToken.unencrypted}`,
73
+ };
74
+
75
+ // Add headers from init if provided
76
+ if (init.headers) {
77
+ const initHeaders = init.headers as Record<string, string>;
78
+ Object.keys(initHeaders).forEach((key) => {
79
+ headers[key] = initHeaders[key];
80
+ });
81
+ }
82
+
83
+ // For FormData, remove Content-Type
84
+ if (init.body instanceof FormData) {
85
+ delete headers["Content-Type"];
86
+ }
87
+
88
+ const response = await fetch(`${this.baseUrl}${path}`, {
89
+ ...init,
90
+ headers,
91
+ });
92
+
93
+ if (!response.ok) {
94
+ let error = new Error(`API error: ${response.statusText}`, {
95
+ cause: response,
96
+ });
97
+
98
+ try {
99
+ error = Object.assign(
100
+ new Error(response.statusText, { cause: response }),
101
+ ((await response.json()) as { error: Error })?.error,
102
+ );
103
+ } finally {
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ return response;
109
+ }
110
+
111
+ /**
112
+ * Helper for GET requests
113
+ */
114
+ async get(path: string, init: RequestInit = {}): Promise<Response> {
115
+ return this.fetch(path, { ...init, method: "GET" });
116
+ }
117
+
118
+ /**
119
+ * Helper for POST requests
120
+ */
121
+ async post(
122
+ path: string,
123
+ body: any,
124
+ init: RequestInit = {},
125
+ ): Promise<Response> {
126
+ const requestBody = body instanceof FormData ? body : JSON.stringify(body);
127
+ return this.fetch(path, { ...init, method: "POST", body: requestBody });
128
+ }
129
+
130
+ /**
131
+ * Helper for PUT requests
132
+ */
133
+ async put(
134
+ path: string,
135
+ body: any,
136
+ init: RequestInit = {},
137
+ ): Promise<Response> {
138
+ const requestBody = body instanceof FormData ? body : JSON.stringify(body);
139
+ return this.fetch(path, { ...init, method: "PUT", body: requestBody });
140
+ }
141
+
142
+ /**
143
+ * Helper for PATCH requests
144
+ */
145
+ async patch(
146
+ path: string,
147
+ body: any,
148
+ init: RequestInit = {},
149
+ ): Promise<Response> {
150
+ return this.fetch(path, {
151
+ ...init,
152
+ method: "PATCH",
153
+ body: JSON.stringify(body),
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Helper for DELETE requests
159
+ */
160
+ async delete(path: string, init: RequestInit = {}): Promise<Response> {
161
+ return this.fetch(path, { ...init, method: "DELETE" });
162
+ }
163
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./api.js";
2
+ export * from "./project-domain.js";
3
+ export * from "./project.js";
@@ -0,0 +1,172 @@
1
+ import type { Context } from "../context.js";
2
+ import { Resource } from "../resource.js";
3
+ import type { Secret } from "../secret.js";
4
+ import { createVercelApi } from "./api.js";
5
+
6
+ /**
7
+ * Properties for creating or updating a ProjectDomain
8
+ */
9
+ export interface ProjectDomainProps {
10
+ /**
11
+ * The domain name to add to the project
12
+ */
13
+ name: string;
14
+
15
+ /**
16
+ * The unique project identifier or the project name
17
+ */
18
+ project: string;
19
+
20
+ /**
21
+ * The Git branch to link the domain to
22
+ */
23
+ gitBranch?: string;
24
+
25
+ /**
26
+ * The unique custom environment identifier within the project
27
+ */
28
+ customEnvironment?: string;
29
+
30
+ /**
31
+ * The redirect target for the domain
32
+ */
33
+ redirect?: string;
34
+
35
+ /**
36
+ * The redirect status code
37
+ */
38
+ redirectStatusCode?: 301 | 302 | 307 | 308;
39
+ }
40
+
41
+ /**
42
+ * Output returned after ProjectDomain creation/update
43
+ */
44
+ export interface ProjectDomain
45
+ extends Resource<"vercel::ProjectDomain">,
46
+ ProjectDomainProps {
47
+ apexName: string;
48
+
49
+ /**
50
+ * `true` if the domain is verified for use with the project. If `false` it will not be used as an alias on this project until the challenge in `verification` is completed.
51
+ */
52
+ verified: boolean;
53
+
54
+ /**
55
+ * The time at which the domain was created
56
+ */
57
+ createdAt: number;
58
+
59
+ /**
60
+ * The time at which the domain was last updated
61
+ */
62
+ updatedAt: number;
63
+
64
+ /**
65
+ * A list of verification challenges, one of which must be completed to verify the domain for use on the project. After the challenge is complete `POST /projects/:idOrName/domains/:domain/verify` to verify the domain.
66
+ *
67
+ * Possible challenges:
68
+ * - If `verification.type = TXT` the `verification.domain` will be checked for a TXT record matching `verification.value`.
69
+ */
70
+ verification?: {
71
+ /**
72
+ * The type of verification
73
+ */
74
+ type: string;
75
+
76
+ /**
77
+ * The domain being verified
78
+ */
79
+ domain: string;
80
+
81
+ /**
82
+ * The verification value
83
+ */
84
+ value: string;
85
+
86
+ /**
87
+ * The reason for verification
88
+ */
89
+ reason: string;
90
+ }[];
91
+ }
92
+
93
+ /**
94
+ * Add and manage domains for Vercel projects.
95
+ *
96
+ * @example
97
+ * // Minimal
98
+ * const domain = await ProjectDomain("my-app.com", {
99
+ * name: "my-app.com",
100
+ * project: "prj_123",
101
+ * });
102
+ *
103
+ * @example
104
+ * // With accessToken
105
+ * const domain = await ProjectDomain("my-app.com", {
106
+ * accessToken: alchemy.secret(process.env.VERCEL_ACCESS_TOKEN),
107
+ * name: "my-app.com",
108
+ * project: "prj_123",
109
+ * });
110
+ *
111
+ * @example
112
+ * // With redirect
113
+ * const domain = await ProjectDomain("my-app.com", {
114
+ * name: "my-app.com",
115
+ * project: "prj_123",
116
+ * gitBranch: "main",
117
+ * redirect: "https://example.com",
118
+ * redirectStatusCode: 301,
119
+ * });
120
+ */
121
+ export const ProjectDomain = Resource(
122
+ "vercel::ProjectDomain",
123
+ async function (
124
+ this: Context<ProjectDomain>,
125
+ id: string,
126
+ { accessToken, ...props }: ProjectDomainProps & { accessToken?: Secret },
127
+ ): Promise<ProjectDomain> {
128
+ switch (this.phase) {
129
+ case "create": {
130
+ const api = await createVercelApi({
131
+ baseUrl: "https://api.vercel.com/v10",
132
+ accessToken,
133
+ });
134
+ const domain = (await api
135
+ .post(`/projects/${props.project}/domains`, props)
136
+ .then((res) => res.json())) as ProjectDomain;
137
+
138
+ return this.create({ ...props, ...domain });
139
+ }
140
+
141
+ case "update": {
142
+ const { gitBranch, redirect, redirectStatusCode } = props;
143
+
144
+ const api = await createVercelApi({
145
+ baseUrl: "https://api.vercel.com/v9",
146
+ accessToken,
147
+ });
148
+ const domain = (await api
149
+ .patch(`/projects/${props.project}/domains/${this.output.name}`, {
150
+ gitBranch,
151
+ redirect,
152
+ redirectStatusCode,
153
+ })
154
+ .then((res) => res.json())) as ProjectDomain;
155
+
156
+ return this.create({ ...props, ...domain });
157
+ }
158
+
159
+ case "delete": {
160
+ const api = await createVercelApi({
161
+ baseUrl: "https://api.vercel.com/v9",
162
+ accessToken,
163
+ });
164
+ await api.delete(
165
+ `/projects/${props.project}/domains/${this.output.name}`,
166
+ );
167
+
168
+ return this.destroy();
169
+ }
170
+ }
171
+ },
172
+ );