alchemy 0.7.0 → 0.7.1

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,192 @@
1
+ import { Resource } from "../resource";
2
+ import { CloudflareApi, createCloudflareApi, } from "./api";
3
+ import { CloudflareApiError, handleApiError } from "./api-error";
4
+ /**
5
+ * Creates and manages Cloudflare Queues.
6
+ *
7
+ * Queues provide a managed queue system for reliable message delivery
8
+ * between workers and other systems.
9
+ *
10
+ * @example
11
+ * // Create a basic queue with default settings
12
+ * const basicQueue = await Queue("my-app-queue", {
13
+ * name: "my-app-queue"
14
+ * });
15
+ *
16
+ * @example
17
+ * // Create a queue with custom settings
18
+ * const customQueue = await Queue("delayed-queue", {
19
+ * name: "delayed-queue",
20
+ * settings: {
21
+ * deliveryDelay: 30, // 30 second delay before message delivery
22
+ * messageRetentionPeriod: 86400 // Store messages for 1 day
23
+ * }
24
+ * });
25
+ *
26
+ * @example
27
+ * // Create a paused queue for later activation
28
+ * const pausedQueue = await Queue("paused-queue", {
29
+ * name: "paused-queue",
30
+ * settings: {
31
+ * deliveryPaused: true
32
+ * }
33
+ * });
34
+ *
35
+ * @see https://developers.cloudflare.com/queues/
36
+ */
37
+ export const Queue = Resource("cloudflare::Queue", async function (id, props) {
38
+ const api = await createCloudflareApi(props);
39
+ const queueName = props.name || id;
40
+ if (this.phase === "delete") {
41
+ console.log("Deleting Cloudflare Queue:", queueName);
42
+ if (props.delete !== false) {
43
+ // Delete Queue
44
+ await deleteQueue(api, this.output?.id);
45
+ }
46
+ // Return void (a deleted queue has no content)
47
+ return this.destroy();
48
+ }
49
+ else {
50
+ let queueData;
51
+ if (this.phase === "create") {
52
+ console.log("Creating Cloudflare Queue:", queueName);
53
+ queueData = await createQueue(api, queueName, props);
54
+ }
55
+ else {
56
+ // Update operation
57
+ if (this.output?.id) {
58
+ console.log("Updating Cloudflare Queue:", queueName);
59
+ // Check if name is being changed, which is not allowed
60
+ if (props.name !== this.output.name) {
61
+ throw new Error("Cannot update Queue name after creation. Queue name is immutable.");
62
+ }
63
+ // Update the queue with new settings
64
+ queueData = await updateQueue(api, this.output.id, props);
65
+ }
66
+ else {
67
+ // If no ID exists, fall back to creating a new queue
68
+ console.log("No existing Queue ID found, creating new Cloudflare Queue:", queueName);
69
+ queueData = await createQueue(api, queueName, props);
70
+ }
71
+ }
72
+ return this({
73
+ type: "queue",
74
+ id: queueData.result.queue_id || "",
75
+ name: queueName,
76
+ settings: queueData.result.settings
77
+ ? {
78
+ deliveryDelay: queueData.result.settings.delivery_delay,
79
+ deliveryPaused: queueData.result.settings.delivery_paused,
80
+ messageRetentionPeriod: queueData.result.settings.message_retention_period,
81
+ }
82
+ : undefined,
83
+ createdOn: queueData.result.created_on || new Date().toISOString(),
84
+ modifiedOn: queueData.result.modified_on || new Date().toISOString(),
85
+ accountId: api.accountId,
86
+ });
87
+ }
88
+ });
89
+ /**
90
+ * Create a new Cloudflare Queue
91
+ */
92
+ export async function createQueue(api, queueName, props) {
93
+ // Prepare the create payload
94
+ const createPayload = {
95
+ queue_name: queueName,
96
+ };
97
+ // Add settings if provided
98
+ if (props.settings) {
99
+ createPayload.settings = {};
100
+ if (props.settings.deliveryDelay !== undefined) {
101
+ createPayload.settings.delivery_delay = props.settings.deliveryDelay;
102
+ }
103
+ if (props.settings.deliveryPaused !== undefined) {
104
+ createPayload.settings.delivery_paused = props.settings.deliveryPaused;
105
+ }
106
+ if (props.settings.messageRetentionPeriod !== undefined) {
107
+ createPayload.settings.message_retention_period =
108
+ props.settings.messageRetentionPeriod;
109
+ }
110
+ }
111
+ const createResponse = await api.post(`/accounts/${api.accountId}/queues`, createPayload);
112
+ if (!createResponse.ok) {
113
+ return await handleApiError(createResponse, "creating", "Queue", queueName);
114
+ }
115
+ return (await createResponse.json());
116
+ }
117
+ /**
118
+ * Get a Cloudflare Queue
119
+ */
120
+ export async function getQueue(api, queueId) {
121
+ const response = await api.get(`/accounts/${api.accountId}/queues/${queueId}`);
122
+ if (!response.ok) {
123
+ return await handleApiError(response, "getting", "Queue", queueId);
124
+ }
125
+ return (await response.json());
126
+ }
127
+ /**
128
+ * Delete a Cloudflare Queue
129
+ */
130
+ export async function deleteQueue(api, queueId) {
131
+ if (!queueId) {
132
+ console.log("No Queue ID provided, skipping delete");
133
+ return;
134
+ }
135
+ // Delete Queue
136
+ const deleteResponse = await api.delete(`/accounts/${api.accountId}/queues/${queueId}`);
137
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
138
+ const errorData = await deleteResponse.json().catch(() => ({
139
+ errors: [{ message: deleteResponse.statusText }],
140
+ }));
141
+ throw new CloudflareApiError(`Error deleting Cloudflare Queue '${queueId}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`, deleteResponse);
142
+ }
143
+ }
144
+ /**
145
+ * Update a Cloudflare Queue
146
+ *
147
+ * Note: According to Cloudflare API, the queue name cannot be changed after creation.
148
+ * Only the settings can be updated.
149
+ */
150
+ export async function updateQueue(api, queueId, props) {
151
+ // Prepare the update payload - only include settings
152
+ const updatePayload = {};
153
+ // Add settings if provided
154
+ if (props.settings) {
155
+ updatePayload.settings = {};
156
+ if (props.settings.deliveryDelay !== undefined) {
157
+ updatePayload.settings.delivery_delay = props.settings.deliveryDelay;
158
+ }
159
+ if (props.settings.deliveryPaused !== undefined) {
160
+ updatePayload.settings.delivery_paused = props.settings.deliveryPaused;
161
+ }
162
+ if (props.settings.messageRetentionPeriod !== undefined) {
163
+ updatePayload.settings.message_retention_period =
164
+ props.settings.messageRetentionPeriod;
165
+ }
166
+ }
167
+ // Use PATCH for partial updates (only settings can be updated)
168
+ const updateResponse = await api.patch(`/accounts/${api.accountId}/queues/${queueId}`, updatePayload);
169
+ if (!updateResponse.ok) {
170
+ return await handleApiError(updateResponse, "updating", "Queue", queueId);
171
+ }
172
+ return (await updateResponse.json());
173
+ }
174
+ /**
175
+ * List all Cloudflare Queues in an account
176
+ */
177
+ export async function listQueues(api) {
178
+ const response = await api.get(`/accounts/${api.accountId}/queues`);
179
+ if (!response.ok) {
180
+ throw new CloudflareApiError(`Failed to list queues: ${response.statusText}`, response);
181
+ }
182
+ const data = (await response.json());
183
+ if (!data.success) {
184
+ const errorMessage = data.errors?.[0]?.message || "Unknown error";
185
+ throw new Error(`Failed to list queues: ${errorMessage}`);
186
+ }
187
+ // Transform API response
188
+ return (data.result || []).map((queue) => ({
189
+ name: queue.queue_name,
190
+ id: queue.queue_id,
191
+ }));
192
+ }
@@ -9,7 +9,6 @@ import { createCloudflareApi, } from "./api";
9
9
  import {} from "./assets";
10
10
  import {} from "./bindings";
11
11
  import {} from "./durable-object-namespace";
12
- import { isKVNamespace } from "./kv-namespace";
13
12
  import { upsertWorkflow } from "./workflow";
14
13
  /**
15
14
  * A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
@@ -153,8 +152,10 @@ export const Worker = Resource("cloudflare::Worker", {
153
152
  const now = Date.now();
154
153
  // Construct the output
155
154
  return this({
155
+ ...props,
156
156
  type: "service",
157
157
  id,
158
+ entrypoint: props.entrypoint,
158
159
  name: workerName,
159
160
  script: scriptContent,
160
161
  format: props.format || "esm", // Include format in the output
@@ -265,18 +266,25 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
265
266
  // Convert bindings to the format expected by the API
266
267
  for (const [bindingName, binding] of Object.entries(bindings)) {
267
268
  // Create a copy of the binding to avoid modifying the original
268
- if (isKVNamespace(binding)) {
269
+ if (typeof binding === "string") {
269
270
  meta.bindings.push({
270
- type: "kv_namespace",
271
+ type: "plain_text",
271
272
  name: bindingName,
272
- namespace_id: binding.namespaceId,
273
+ text: binding,
273
274
  });
274
275
  }
275
- else if (typeof binding === "string") {
276
+ else if (binding.type === "d1") {
276
277
  meta.bindings.push({
277
- type: "plain_text",
278
+ type: "d1",
278
279
  name: bindingName,
279
- text: binding,
280
+ id: binding.id,
281
+ });
282
+ }
283
+ else if (binding.type === "kv_namespace") {
284
+ meta.bindings.push({
285
+ type: "kv_namespace",
286
+ name: bindingName,
287
+ namespace_id: binding.namespaceId,
280
288
  });
281
289
  }
282
290
  else if (binding.type === "service") {
@@ -329,6 +337,13 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
329
337
  // it's unclear whether this is needed, but it works both ways
330
338
  configureClassMigration(binding, binding.id, binding.className);
331
339
  }
340
+ else if (binding.type === "queue") {
341
+ meta.bindings.push({
342
+ type: "queue",
343
+ name: bindingName,
344
+ queue_name: binding.name,
345
+ });
346
+ }
332
347
  else {
333
348
  // @ts-expect-error - we should never reach here
334
349
  throw new Error(`Unsupported binding type: ${binding.type}`);
@@ -1,243 +1,140 @@
1
1
  import type { Context } from "../context";
2
2
  import { Resource } from "../resource";
3
+ import type { Worker } from "./worker";
3
4
  /**
4
5
  * Properties for wrangler.json configuration file
5
6
  */
6
7
  export interface WranglerJsonProps {
8
+ name?: string;
7
9
  /**
8
- * The name of your worker
10
+ * The worker to generate the wrangler.json file for
9
11
  */
10
- name: string;
12
+ worker: Worker;
11
13
  /**
12
- * The directory containing your worker entry point
13
- * @default "src"
14
+ * Path to write the wrangler.json file to
15
+ *
16
+ * @default cwd/wrangler.json
14
17
  */
15
- main?: string;
18
+ path?: string;
19
+ }
20
+ /**
21
+ * Output returned after WranglerJson creation/update
22
+ */
23
+ export interface WranglerJson extends Resource<"cloudflare::WranglerJson">, WranglerJsonProps {
16
24
  /**
17
- * The entry point for your worker
18
- * @default "index.ts" or "index.js"
25
+ * Time at which the file was created
19
26
  */
20
- entrypoint?: string;
27
+ createdAt: number;
21
28
  /**
22
- * The directory to store build artifacts
23
- * @default "dist"
29
+ * Time at which the file was last updated
24
30
  */
25
- outdir?: string;
31
+ updatedAt: number;
26
32
  /**
27
- * The directory to serve static assets from
33
+ * Path to the wrangler.json file
28
34
  */
29
- assets?: string;
35
+ path: string;
36
+ }
37
+ /**
38
+ * Resource for managing wrangler.json configuration files
39
+ */
40
+ export declare const WranglerJson: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<WranglerJson>, id: string, props: WranglerJsonProps) => Promise<WranglerJson>);
41
+ /**
42
+ * Wrangler.json configuration specification based on Cloudflare's schema
43
+ */
44
+ export interface WranglerJsonSpec {
30
45
  /**
31
- * Minify the worker script
32
- * @default true
46
+ * The name of the worker
33
47
  */
34
- minify?: boolean;
48
+ name: string;
35
49
  /**
36
- * Node.js compatibility mode
37
- * @default false
50
+ * Main entry point for the worker
38
51
  */
39
- node_compat?: boolean;
52
+ main?: string;
40
53
  /**
41
- * First-party worker service bindings
54
+ * A date in the form yyyy-mm-dd used to determine Workers runtime version
42
55
  */
43
- services?: Array<{
44
- /**
45
- * Binding name
46
- */
47
- name: string;
48
- /**
49
- * Service environment
50
- */
51
- environment?: string;
52
- }>;
56
+ compatibility_date?: string;
53
57
  /**
54
- * Worker environment variables
58
+ * A list of flags that enable features from upcoming Workers runtime
55
59
  */
56
- vars?: Record<string, string>;
60
+ compatibility_flags?: string[];
57
61
  /**
58
- * KV Namespace bindings
62
+ * Whether to enable a workers.dev URL for this worker
59
63
  */
60
- kv_namespaces?: Array<{
61
- /**
62
- * Binding name
63
- */
64
- binding: string;
65
- /**
66
- * KV namespace ID
67
- */
68
- id: string;
69
- /**
70
- * Preview KV namespace ID
71
- */
72
- preview_id?: string;
73
- }>;
64
+ workers_dev?: boolean;
74
65
  /**
75
- * R2 bucket bindings
66
+ * Routes to attach to the worker
76
67
  */
77
- r2_buckets?: Array<{
78
- /**
79
- * Binding name
80
- */
81
- binding: string;
82
- /**
83
- * Bucket name
84
- */
85
- bucket_name: string;
86
- /**
87
- * Preview bucket name
88
- */
89
- preview_bucket_name?: string;
90
- }>;
91
- /**
92
- * D1 database bindings
93
- */
94
- d1_databases?: Array<{
95
- /**
96
- * Binding name
97
- */
68
+ routes?: string[];
69
+ /**
70
+ * KV Namespace bindings
71
+ */
72
+ kv_namespaces?: {
98
73
  binding: string;
99
- /**
100
- * Database name
101
- */
102
- database_name: string;
103
- /**
104
- * Database ID
105
- */
106
- database_id: string;
107
- /**
108
- * Preview database ID
109
- */
110
- preview_database_id?: string;
111
- }>;
74
+ id: string;
75
+ }[];
112
76
  /**
113
77
  * Durable Object bindings
114
78
  */
115
79
  durable_objects?: {
116
- /**
117
- * Durable Object bindings
118
- */
119
- bindings: Array<{
120
- /**
121
- * Binding name
122
- */
80
+ bindings: {
123
81
  name: string;
124
- /**
125
- * Class name
126
- */
127
82
  class_name: string;
128
- /**
129
- * Script name
130
- */
131
83
  script_name?: string;
132
- /**
133
- * Environment name
134
- */
135
84
  environment?: string;
136
- }>;
137
- };
138
- /**
139
- * Queue bindings
140
- */
141
- queues?: {
142
- /**
143
- * Producer bindings
144
- */
145
- producers?: Array<{
146
- /**
147
- * Binding name
148
- */
149
- binding: string;
150
- /**
151
- * Queue name
152
- */
153
- queue: string;
154
- }>;
155
- /**
156
- * Consumer configuration
157
- */
158
- consumers?: Array<{
159
- /**
160
- * Queue name
161
- */
162
- queue: string;
163
- /**
164
- * Maximum batch size
165
- */
166
- max_batch_size?: number;
167
- /**
168
- * Maximum batch timeout
169
- */
170
- max_batch_timeout?: number;
171
- /**
172
- * Maximum retries
173
- */
174
- max_retries?: number;
175
- /**
176
- * Dead letter queue
177
- */
178
- dead_letter_queue?: string;
179
- }>;
85
+ }[];
180
86
  };
181
87
  /**
182
- * Analytics Engine bindings
88
+ * R2 bucket bindings
183
89
  */
184
- analytics_engine_datasets?: Array<{
185
- /**
186
- * Binding name
187
- */
90
+ r2_buckets?: {
188
91
  binding: string;
189
- /**
190
- * Dataset name
191
- */
192
- dataset?: string;
193
- }>;
92
+ bucket_name: string;
93
+ }[];
194
94
  /**
195
- * Route configuration
95
+ * Queue bindings
196
96
  */
197
- routes?: Array<string>;
97
+ queues?: {
98
+ binding: string;
99
+ queue: string;
100
+ }[];
198
101
  /**
199
- * Triggers configuration
102
+ * Service bindings
200
103
  */
201
- triggers?: {
202
- /**
203
- * Cron triggers
204
- */
205
- crons: Array<string>;
206
- };
104
+ services?: {
105
+ binding: string;
106
+ service: string;
107
+ environment?: string;
108
+ }[];
207
109
  /**
208
- * Worker compatibility date
110
+ * Workflow bindings
209
111
  */
210
- compatibility_date?: string;
112
+ workflows?: {
113
+ name: string;
114
+ binding: string;
115
+ class_name: string;
116
+ }[];
211
117
  /**
212
- * Worker compatibility flags
118
+ * Plain text bindings (vars)
213
119
  */
214
- compatibility_flags?: Array<string>;
120
+ vars?: Record<string, string>;
215
121
  /**
216
- * Worker usage model
217
- * "bundled" - Includes resources like CPU and memory. Better for consistent workloads.
218
- * "unbound" - Pay only for what you use. Better for sporadic workloads.
122
+ * Assets bindings
219
123
  */
220
- usage_model?: "bundled" | "unbound";
124
+ assets?: {
125
+ directory: string;
126
+ binding: string;
127
+ };
221
128
  /**
222
- * Path to the wrangler.json file
223
- * @internal
129
+ * Workflow bindings
224
130
  */
225
- path?: string;
226
- }
227
- /**
228
- * Output returned after WranglerJson creation/update
229
- */
230
- export interface WranglerJson extends Resource<"cloudflare::WranglerJson">, WranglerJsonProps {
131
+ wasm_modules?: Record<string, string>;
231
132
  /**
232
- * Time at which the file was created
133
+ * Safe mode configuration
233
134
  */
234
- createdAt: number;
135
+ node_compat?: boolean;
235
136
  /**
236
- * Time at which the file was last updated
137
+ * Whether to minify the worker script
237
138
  */
238
- updatedAt: number;
139
+ minify?: boolean;
239
140
  }
240
- /**
241
- * Resource for managing wrangler.json configuration files
242
- */
243
- export declare const WranglerJson: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<WranglerJson>, id: string, props: WranglerJsonProps) => Promise<WranglerJson>);