alchemy 0.6.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.
- package/lib/cloudflare/bindings.d.ts +21 -7
- package/lib/cloudflare/bindings.js +0 -9
- package/lib/cloudflare/bound.d.ts +4 -1
- package/lib/cloudflare/d1-database.d.ts +134 -0
- package/lib/cloudflare/d1-database.js +161 -0
- package/lib/cloudflare/index.d.ts +4 -0
- package/lib/cloudflare/index.js +4 -0
- package/lib/cloudflare/kv-namespace.d.ts +0 -1
- package/lib/cloudflare/kv-namespace.js +0 -3
- package/lib/cloudflare/queue.d.ts +147 -0
- package/lib/cloudflare/queue.js +192 -0
- package/lib/cloudflare/worker.js +78 -34
- package/lib/cloudflare/workflow.d.ts +46 -0
- package/lib/cloudflare/workflow.js +29 -0
- package/lib/cloudflare/wrangler.json.d.ts +82 -185
- package/lib/cloudflare/wrangler.json.js +124 -17
- package/lib/internal/docs/providers.js +1 -1
- package/package.json +1 -1
- package/src/cloudflare/bindings.ts +27 -18
- package/src/cloudflare/bound.ts +10 -1
- package/src/cloudflare/d1-database.ts +354 -0
- package/src/cloudflare/durable-object-namespace.ts +1 -1
- package/src/cloudflare/index.ts +4 -0
- package/src/cloudflare/kv-namespace.ts +0 -6
- package/src/cloudflare/queue.ts +371 -0
- package/src/cloudflare/worker.ts +85 -45
- package/src/cloudflare/workflow.ts +79 -0
- package/src/cloudflare/wrangler.json.ts +239 -218
- package/src/internal/docs/providers.ts +1 -1
|
@@ -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
|
+
}
|
package/lib/cloudflare/worker.js
CHANGED
|
@@ -2,13 +2,14 @@ import * as crypto from "crypto";
|
|
|
2
2
|
import * as fs from "fs/promises";
|
|
3
3
|
import { Bundle } from "../esbuild/bundle";
|
|
4
4
|
import { Resource } from "../resource";
|
|
5
|
-
import { isSecret } from "../secret";
|
|
6
5
|
import { getContentType } from "../util/content-type";
|
|
7
6
|
import { withExponentialBackoff } from "../util/retry";
|
|
8
7
|
import { slugify } from "../util/slugify";
|
|
9
8
|
import { createCloudflareApi, } from "./api";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import {} from "./assets";
|
|
10
|
+
import {} from "./bindings";
|
|
11
|
+
import {} from "./durable-object-namespace";
|
|
12
|
+
import { upsertWorkflow } from "./workflow";
|
|
12
13
|
/**
|
|
13
14
|
* A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
|
|
14
15
|
*
|
|
@@ -111,10 +112,16 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
111
112
|
const scriptContent = props.script ?? (await bundleWorkerScript(props));
|
|
112
113
|
// Find any assets bindings
|
|
113
114
|
const assetsBindings = [];
|
|
115
|
+
const workflowsBindings = [];
|
|
114
116
|
if (props.bindings) {
|
|
115
117
|
for (const [bindingName, binding] of Object.entries(props.bindings)) {
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
+
if (typeof binding === "object") {
|
|
119
|
+
if (binding.type === "assets") {
|
|
120
|
+
assetsBindings.push({ name: bindingName, assets: binding });
|
|
121
|
+
}
|
|
122
|
+
else if (binding.type === "workflow") {
|
|
123
|
+
workflowsBindings.push(binding);
|
|
124
|
+
}
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
}
|
|
@@ -129,8 +136,14 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
129
136
|
}
|
|
130
137
|
// Prepare metadata with bindings
|
|
131
138
|
const scriptMetadata = await prepareWorkerMetadata(this, oldBindings, props, assetUploadResult);
|
|
132
|
-
// Upload the worker script
|
|
133
139
|
await putWorker(api, workerName, scriptContent, scriptMetadata);
|
|
140
|
+
for (const workflow of workflowsBindings) {
|
|
141
|
+
await upsertWorkflow(api, {
|
|
142
|
+
workflowName: workflow.workflowName,
|
|
143
|
+
className: workflow.className,
|
|
144
|
+
scriptName: workerName,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
134
147
|
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
135
148
|
await this.set("bindings", props.bindings);
|
|
136
149
|
// Handle worker URL if requested
|
|
@@ -139,8 +152,10 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
139
152
|
const now = Date.now();
|
|
140
153
|
// Construct the output
|
|
141
154
|
return this({
|
|
155
|
+
...props,
|
|
142
156
|
type: "service",
|
|
143
157
|
id,
|
|
158
|
+
entrypoint: props.entrypoint,
|
|
144
159
|
name: workerName,
|
|
145
160
|
script: scriptContent,
|
|
146
161
|
format: props.format || "esm", // Include format in the output
|
|
@@ -251,18 +266,25 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
|
|
|
251
266
|
// Convert bindings to the format expected by the API
|
|
252
267
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
253
268
|
// Create a copy of the binding to avoid modifying the original
|
|
254
|
-
if (
|
|
269
|
+
if (typeof binding === "string") {
|
|
255
270
|
meta.bindings.push({
|
|
256
|
-
type: "
|
|
271
|
+
type: "plain_text",
|
|
257
272
|
name: bindingName,
|
|
258
|
-
|
|
273
|
+
text: binding,
|
|
259
274
|
});
|
|
260
275
|
}
|
|
261
|
-
else if (
|
|
276
|
+
else if (binding.type === "d1") {
|
|
262
277
|
meta.bindings.push({
|
|
263
|
-
type: "
|
|
278
|
+
type: "d1",
|
|
264
279
|
name: bindingName,
|
|
265
|
-
|
|
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,
|
|
266
288
|
});
|
|
267
289
|
}
|
|
268
290
|
else if (binding.type === "service") {
|
|
@@ -273,33 +295,15 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
|
|
|
273
295
|
});
|
|
274
296
|
}
|
|
275
297
|
else if (binding.type === "durable_object_namespace") {
|
|
276
|
-
const stableId = binding.id;
|
|
277
|
-
const className = binding.className;
|
|
278
298
|
meta.bindings.push({
|
|
279
299
|
type: "durable_object_namespace",
|
|
280
300
|
name: bindingName,
|
|
281
|
-
class_name: className,
|
|
301
|
+
class_name: binding.className,
|
|
282
302
|
script_name: binding.scriptName,
|
|
283
303
|
environment: binding.environment,
|
|
284
304
|
namespace_id: binding.namespaceId,
|
|
285
305
|
});
|
|
286
|
-
|
|
287
|
-
?.filter(isDurableObjectNamespace)
|
|
288
|
-
?.find((b) => b.id === stableId);
|
|
289
|
-
if (!oldBinding) {
|
|
290
|
-
if (binding.sqlite) {
|
|
291
|
-
meta.migrations.new_sqlite_classes.push(className);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
meta.migrations.new_classes.push(className);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
else if (oldBinding.className !== className) {
|
|
298
|
-
meta.migrations.renamed_classes.push({
|
|
299
|
-
from: oldBinding.className,
|
|
300
|
-
to: className,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
306
|
+
configureClassMigration(binding, binding.id, binding.className);
|
|
303
307
|
}
|
|
304
308
|
else if (binding.type === "r2_bucket") {
|
|
305
309
|
meta.bindings.push({
|
|
@@ -308,25 +312,65 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
|
|
|
308
312
|
bucket_name: binding.name,
|
|
309
313
|
});
|
|
310
314
|
}
|
|
311
|
-
else if (
|
|
315
|
+
else if (binding.type === "assets") {
|
|
312
316
|
meta.bindings.push({
|
|
313
317
|
type: "assets",
|
|
314
318
|
name: bindingName,
|
|
315
319
|
});
|
|
316
320
|
}
|
|
317
|
-
else if (
|
|
321
|
+
else if (binding.type === "secret") {
|
|
318
322
|
meta.bindings.push({
|
|
319
323
|
type: "secret_text",
|
|
320
324
|
name: bindingName,
|
|
321
325
|
text: binding.unencrypted,
|
|
322
326
|
});
|
|
323
327
|
}
|
|
328
|
+
else if (binding.type === "workflow") {
|
|
329
|
+
meta.bindings.push({
|
|
330
|
+
type: "workflow",
|
|
331
|
+
name: bindingName,
|
|
332
|
+
workflow_name: binding.workflowName,
|
|
333
|
+
class_name: binding.className,
|
|
334
|
+
// this should be set if the Workflow is in another script ...
|
|
335
|
+
// script_name: ??,
|
|
336
|
+
});
|
|
337
|
+
// it's unclear whether this is needed, but it works both ways
|
|
338
|
+
configureClassMigration(binding, binding.id, binding.className);
|
|
339
|
+
}
|
|
340
|
+
else if (binding.type === "queue") {
|
|
341
|
+
meta.bindings.push({
|
|
342
|
+
type: "queue",
|
|
343
|
+
name: bindingName,
|
|
344
|
+
queue_name: binding.name,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
324
347
|
else {
|
|
325
348
|
// @ts-expect-error - we should never reach here
|
|
326
349
|
throw new Error(`Unsupported binding type: ${binding.type}`);
|
|
327
350
|
}
|
|
328
351
|
}
|
|
352
|
+
function configureClassMigration(binding, stableId, className) {
|
|
353
|
+
const oldBinding = Object.values(oldBindings ?? {})
|
|
354
|
+
?.filter((b) => typeof b === "object" &&
|
|
355
|
+
(b.type === "durable_object_namespace" || b.type === "workflow"))
|
|
356
|
+
?.find((b) => b.id === stableId);
|
|
357
|
+
if (!oldBinding) {
|
|
358
|
+
if (binding.type === "durable_object_namespace" && binding.sqlite) {
|
|
359
|
+
meta.migrations.new_sqlite_classes.push(className);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
meta.migrations.new_classes.push(className);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else if (oldBinding.className !== className) {
|
|
366
|
+
meta.migrations.renamed_classes.push({
|
|
367
|
+
from: oldBinding.className,
|
|
368
|
+
to: className,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
329
372
|
// Convert env variables to plain_text bindings
|
|
373
|
+
// TODO(sam): remove Worker.env in favor of always bindings
|
|
330
374
|
if (props.env) {
|
|
331
375
|
for (const [key, value] of Object.entries(props.env)) {
|
|
332
376
|
meta.bindings.push({
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CloudflareApi } from "./api";
|
|
2
|
+
export interface WorkflowProps {
|
|
3
|
+
/**
|
|
4
|
+
* Name of the workflow
|
|
5
|
+
*
|
|
6
|
+
* @maxLength 64
|
|
7
|
+
* @minLength 1
|
|
8
|
+
* @default - className if provided, otherwise id
|
|
9
|
+
*/
|
|
10
|
+
workflowName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Name of the class that implements the workflow
|
|
13
|
+
*
|
|
14
|
+
* @maxLength 255
|
|
15
|
+
* @minLength 1
|
|
16
|
+
* @default - workflowName if provided, otherwise id
|
|
17
|
+
*/
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class Workflow<PARAMS = unknown> {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly type: "workflow";
|
|
23
|
+
/**
|
|
24
|
+
* Phantom property to preserve workflow params at the type level.
|
|
25
|
+
*
|
|
26
|
+
* No value exists.
|
|
27
|
+
*/
|
|
28
|
+
readonly _PARAMS: PARAMS;
|
|
29
|
+
readonly workflowName: string;
|
|
30
|
+
readonly className: string;
|
|
31
|
+
constructor(id: string, props?: WorkflowProps);
|
|
32
|
+
}
|
|
33
|
+
export interface WorkflowMetadata {
|
|
34
|
+
id: string;
|
|
35
|
+
class_name: string;
|
|
36
|
+
created_on: string;
|
|
37
|
+
modified_on: string;
|
|
38
|
+
name: string;
|
|
39
|
+
script_name: string;
|
|
40
|
+
triggered_on: string;
|
|
41
|
+
version_id: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function upsertWorkflow(api: CloudflareApi, props: WorkflowProps & {
|
|
44
|
+
workflowName: string;
|
|
45
|
+
scriptName: string;
|
|
46
|
+
}): Promise<WorkflowMetadata>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { handleApiError } from "./api-error";
|
|
2
|
+
export class Workflow {
|
|
3
|
+
id;
|
|
4
|
+
type = "workflow";
|
|
5
|
+
/**
|
|
6
|
+
* Phantom property to preserve workflow params at the type level.
|
|
7
|
+
*
|
|
8
|
+
* No value exists.
|
|
9
|
+
*/
|
|
10
|
+
_PARAMS = undefined;
|
|
11
|
+
workflowName;
|
|
12
|
+
className;
|
|
13
|
+
constructor(id, props = {}) {
|
|
14
|
+
this.id = id;
|
|
15
|
+
this.workflowName = props.workflowName ?? props.className ?? id;
|
|
16
|
+
this.className = props.className ?? this.workflowName;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function upsertWorkflow(api, props) {
|
|
20
|
+
const response = await api.put(`/accounts/${api.accountId}/workflows/${props.workflowName}`, {
|
|
21
|
+
class_name: props.className,
|
|
22
|
+
script_name: props.scriptName,
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
await handleApiError(response, "create", "workflow", props.workflowName);
|
|
26
|
+
}
|
|
27
|
+
const body = (await response.json());
|
|
28
|
+
return body.result;
|
|
29
|
+
}
|