alchemy 0.7.0 → 0.7.2

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,147 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ import { CloudflareApi, type CloudflareApiOptions } from "./api";
4
+ /**
5
+ * Settings for a Cloudflare Queue
6
+ */
7
+ export interface QueueSettings {
8
+ /**
9
+ * Delay in seconds before message delivery
10
+ * Queue will not deliver messages until this time has elapsed
11
+ */
12
+ deliveryDelay?: number;
13
+ /**
14
+ * Whether delivery is paused
15
+ * If true, the queue will not deliver messages to consumers
16
+ */
17
+ deliveryPaused?: boolean;
18
+ /**
19
+ * Period in seconds to retain messages
20
+ * Messages will be automatically deleted after this time
21
+ */
22
+ messageRetentionPeriod?: number;
23
+ }
24
+ /**
25
+ * Properties for creating or updating a Cloudflare Queue
26
+ */
27
+ export interface QueueProps extends CloudflareApiOptions {
28
+ /**
29
+ * Name of the queue
30
+ * Required during creation
31
+ * Cannot be changed after creation
32
+ */
33
+ name: string;
34
+ /**
35
+ * Settings for the queue
36
+ * These can be updated after queue creation
37
+ */
38
+ settings?: QueueSettings;
39
+ /**
40
+ * Whether to delete the queue.
41
+ * If set to false, the queue will remain but the resource will be removed from state
42
+ *
43
+ * @default true
44
+ */
45
+ delete?: boolean;
46
+ }
47
+ /**
48
+ * Output returned after Cloudflare Queue creation/update
49
+ */
50
+ export interface Queue extends Resource<"cloudflare::Queue">, QueueProps {
51
+ /**
52
+ * Type identifier for Cloudflare Queue
53
+ */
54
+ type: "queue";
55
+ /**
56
+ * The unique ID of the queue
57
+ */
58
+ id: string;
59
+ /**
60
+ * Time when the queue was created
61
+ */
62
+ createdOn: string;
63
+ /**
64
+ * Modified timestamp
65
+ */
66
+ modifiedOn: string;
67
+ }
68
+ interface CloudflareQueueResponse {
69
+ result: {
70
+ queue_id?: string;
71
+ queue_name: string;
72
+ created_on?: string;
73
+ modified_on?: string;
74
+ settings?: {
75
+ delivery_delay?: number;
76
+ delivery_paused?: boolean;
77
+ message_retention_period?: number;
78
+ };
79
+ };
80
+ success: boolean;
81
+ errors: Array<{
82
+ code: number;
83
+ message: string;
84
+ }>;
85
+ messages: string[];
86
+ }
87
+ /**
88
+ * Creates and manages Cloudflare Queues.
89
+ *
90
+ * Queues provide a managed queue system for reliable message delivery
91
+ * between workers and other systems.
92
+ *
93
+ * @example
94
+ * // Create a basic queue with default settings
95
+ * const basicQueue = await Queue("my-app-queue", {
96
+ * name: "my-app-queue"
97
+ * });
98
+ *
99
+ * @example
100
+ * // Create a queue with custom settings
101
+ * const customQueue = await Queue("delayed-queue", {
102
+ * name: "delayed-queue",
103
+ * settings: {
104
+ * deliveryDelay: 30, // 30 second delay before message delivery
105
+ * messageRetentionPeriod: 86400 // Store messages for 1 day
106
+ * }
107
+ * });
108
+ *
109
+ * @example
110
+ * // Create a paused queue for later activation
111
+ * const pausedQueue = await Queue("paused-queue", {
112
+ * name: "paused-queue",
113
+ * settings: {
114
+ * deliveryPaused: true
115
+ * }
116
+ * });
117
+ *
118
+ * @see https://developers.cloudflare.com/queues/
119
+ */
120
+ export declare const Queue: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Queue>, id: string, props: QueueProps) => Promise<Queue>);
121
+ /**
122
+ * Create a new Cloudflare Queue
123
+ */
124
+ export declare function createQueue(api: CloudflareApi, queueName: string, props: QueueProps): Promise<CloudflareQueueResponse>;
125
+ /**
126
+ * Get a Cloudflare Queue
127
+ */
128
+ export declare function getQueue(api: CloudflareApi, queueId: string): Promise<CloudflareQueueResponse>;
129
+ /**
130
+ * Delete a Cloudflare Queue
131
+ */
132
+ export declare function deleteQueue(api: CloudflareApi, queueId?: string): Promise<void>;
133
+ /**
134
+ * Update a Cloudflare Queue
135
+ *
136
+ * Note: According to Cloudflare API, the queue name cannot be changed after creation.
137
+ * Only the settings can be updated.
138
+ */
139
+ export declare function updateQueue(api: CloudflareApi, queueId: string, props: QueueProps): Promise<CloudflareQueueResponse>;
140
+ /**
141
+ * List all Cloudflare Queues in an account
142
+ */
143
+ export declare function listQueues(api: CloudflareApi): Promise<{
144
+ name: string;
145
+ id: string;
146
+ }[]>;
147
+ export {};
@@ -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
+ }
@@ -74,6 +74,15 @@ export interface WorkerProps<B extends Bindings = Bindings> extends CloudflareAp
74
74
  * Whether to adopt the Worker if it already exists when creating
75
75
  */
76
76
  adopt?: boolean;
77
+ /**
78
+ * The compatibility date for the worker
79
+ * @default "2024-09-09"
80
+ */
81
+ compatibilityDate?: string;
82
+ /**
83
+ * The compatibility flags for the worker
84
+ */
85
+ compatibilityFlags?: string[];
77
86
  }
78
87
  /**
79
88
  * Output returned after Worker creation/update
@@ -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
@@ -238,6 +239,8 @@ class NotFoundError extends Error {
238
239
  async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult) {
239
240
  // Prepare metadata with bindings
240
241
  const meta = {
242
+ compatibility_date: props.compatibilityDate ?? "2024-09-09",
243
+ compatibility_flags: props.compatibilityFlags,
241
244
  bindings: [],
242
245
  observability: {
243
246
  enabled: props.observability?.enabled !== false,
@@ -265,18 +268,25 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
265
268
  // Convert bindings to the format expected by the API
266
269
  for (const [bindingName, binding] of Object.entries(bindings)) {
267
270
  // Create a copy of the binding to avoid modifying the original
268
- if (isKVNamespace(binding)) {
271
+ if (typeof binding === "string") {
269
272
  meta.bindings.push({
270
- type: "kv_namespace",
273
+ type: "plain_text",
271
274
  name: bindingName,
272
- namespace_id: binding.namespaceId,
275
+ text: binding,
273
276
  });
274
277
  }
275
- else if (typeof binding === "string") {
278
+ else if (binding.type === "d1") {
276
279
  meta.bindings.push({
277
- type: "plain_text",
280
+ type: "d1",
278
281
  name: bindingName,
279
- text: binding,
282
+ id: binding.id,
283
+ });
284
+ }
285
+ else if (binding.type === "kv_namespace") {
286
+ meta.bindings.push({
287
+ type: "kv_namespace",
288
+ name: bindingName,
289
+ namespace_id: binding.namespaceId,
280
290
  });
281
291
  }
282
292
  else if (binding.type === "service") {
@@ -329,6 +339,20 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
329
339
  // it's unclear whether this is needed, but it works both ways
330
340
  configureClassMigration(binding, binding.id, binding.className);
331
341
  }
342
+ else if (binding.type === "queue") {
343
+ meta.bindings.push({
344
+ type: "queue",
345
+ name: bindingName,
346
+ queue_name: binding.name,
347
+ });
348
+ }
349
+ else if (binding.type === "pipeline") {
350
+ meta.bindings.push({
351
+ type: "pipelines",
352
+ name: bindingName,
353
+ pipeline: binding.name,
354
+ });
355
+ }
332
356
  else {
333
357
  // @ts-expect-error - we should never reach here
334
358
  throw new Error(`Unsupported binding type: ${binding.type}`);