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.
- package/lib/cloudflare/bindings.d.ts +16 -2
- package/lib/cloudflare/bound.d.ts +5 -1
- package/lib/cloudflare/bucket.js +1 -0
- package/lib/cloudflare/d1-database.d.ts +151 -0
- package/lib/cloudflare/d1-database.js +200 -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/pipeline.d.ts +335 -0
- package/lib/cloudflare/pipeline.js +250 -0
- package/lib/cloudflare/queue.d.ts +147 -0
- package/lib/cloudflare/queue.js +192 -0
- package/lib/cloudflare/worker.d.ts +9 -0
- package/lib/cloudflare/worker.js +31 -7
- package/lib/cloudflare/wrangler.json.d.ts +82 -185
- package/lib/cloudflare/wrangler.json.js +124 -17
- package/package.json +1 -1
- package/src/cloudflare/bindings.ts +22 -3
- package/src/cloudflare/bound.ts +11 -1
- package/src/cloudflare/bucket.ts +1 -0
- package/src/cloudflare/d1-database.ts +412 -0
- package/src/cloudflare/index.ts +4 -0
- package/src/cloudflare/kv-namespace.ts +0 -6
- package/src/cloudflare/pipeline.ts +642 -0
- package/src/cloudflare/queue.ts +371 -0
- package/src/cloudflare/worker.ts +41 -7
- package/src/cloudflare/wrangler.json.ts +239 -218
|
@@ -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
|
package/lib/cloudflare/worker.js
CHANGED
|
@@ -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 (
|
|
271
|
+
if (typeof binding === "string") {
|
|
269
272
|
meta.bindings.push({
|
|
270
|
-
type: "
|
|
273
|
+
type: "plain_text",
|
|
271
274
|
name: bindingName,
|
|
272
|
-
|
|
275
|
+
text: binding,
|
|
273
276
|
});
|
|
274
277
|
}
|
|
275
|
-
else if (
|
|
278
|
+
else if (binding.type === "d1") {
|
|
276
279
|
meta.bindings.push({
|
|
277
|
-
type: "
|
|
280
|
+
type: "d1",
|
|
278
281
|
name: bindingName,
|
|
279
|
-
|
|
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}`);
|