alchemy 0.58.0 → 0.59.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/bin/alchemy.js +2 -2
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +10 -2
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/assets.d.ts +1 -1
- package/lib/cloudflare/assets.d.ts.map +1 -1
- package/lib/cloudflare/bucket.d.ts +21 -3
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +83 -53
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/container.d.ts +8 -0
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +7 -1
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/custom-domain.d.ts +8 -0
- package/lib/cloudflare/custom-domain.d.ts.map +1 -1
- package/lib/cloudflare/custom-domain.js +11 -0
- package/lib/cloudflare/custom-domain.js.map +1 -1
- package/lib/cloudflare/d1-database.d.ts +15 -1
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +24 -17
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/hyperdrive.d.ts +37 -9
- package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
- package/lib/cloudflare/hyperdrive.js +91 -5
- package/lib/cloudflare/hyperdrive.js.map +1 -1
- package/lib/cloudflare/kv-namespace.d.ts +23 -4
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +70 -58
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts +2 -0
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +9 -7
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/delete.d.ts +21 -0
- package/lib/cloudflare/miniflare/delete.d.ts.map +1 -0
- package/lib/cloudflare/miniflare/delete.js +91 -0
- package/lib/cloudflare/miniflare/delete.js.map +1 -0
- package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-controller.js +26 -9
- package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts +2 -0
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +3 -4
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/pipeline.d.ts +3 -0
- package/lib/cloudflare/pipeline.d.ts.map +1 -1
- package/lib/cloudflare/pipeline.js +14 -1
- package/lib/cloudflare/pipeline.js.map +1 -1
- package/lib/cloudflare/queue-consumer.d.ts +8 -0
- package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
- package/lib/cloudflare/queue-consumer.js +15 -4
- package/lib/cloudflare/queue-consumer.js.map +1 -1
- package/lib/cloudflare/queue.d.ts +22 -4
- package/lib/cloudflare/queue.d.ts.map +1 -1
- package/lib/cloudflare/queue.js +32 -9
- package/lib/cloudflare/queue.js.map +1 -1
- package/lib/cloudflare/route.d.ts +8 -0
- package/lib/cloudflare/route.d.ts.map +1 -1
- package/lib/cloudflare/route.js +13 -0
- package/lib/cloudflare/route.js.map +1 -1
- package/lib/cloudflare/worker-subdomain.d.ts +8 -0
- package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
- package/lib/cloudflare/worker-subdomain.js +9 -1
- package/lib/cloudflare/worker-subdomain.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +7 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +405 -375
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +1 -1
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +24 -12
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/esbuild/bundle.d.ts +1 -1
- package/lib/secret.d.ts +9 -1
- package/lib/secret.d.ts.map +1 -1
- package/lib/secret.js +12 -0
- package/lib/secret.js.map +1 -1
- package/package.json +3 -3
- package/src/cloudflare/api.ts +13 -2
- package/src/cloudflare/bucket.ts +126 -68
- package/src/cloudflare/container.ts +18 -1
- package/src/cloudflare/custom-domain.ts +22 -0
- package/src/cloudflare/d1-database.ts +44 -19
- package/src/cloudflare/hyperdrive.ts +170 -14
- package/src/cloudflare/kv-namespace.ts +117 -73
- package/src/cloudflare/miniflare/build-worker-options.ts +12 -8
- package/src/cloudflare/miniflare/delete.ts +136 -0
- package/src/cloudflare/miniflare/miniflare-controller.ts +26 -9
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +5 -4
- package/src/cloudflare/pipeline.ts +17 -1
- package/src/cloudflare/queue-consumer.ts +27 -5
- package/src/cloudflare/queue.ts +62 -12
- package/src/cloudflare/route.ts +24 -0
- package/src/cloudflare/worker-subdomain.ts +29 -6
- package/src/cloudflare/worker.ts +484 -465
- package/src/cloudflare/wrangler.json.ts +34 -13
- package/src/secret.ts +15 -1
- package/templates/astro/README.md +2 -0
- package/templates/nuxt/README.md +2 -0
- package/templates/react-router/README.md +2 -0
- package/templates/rwsdk/README.md +2 -0
- package/templates/sveltekit/README.md +2 -0
- package/templates/tanstack-start/README.md +2 -0
- package/templates/typescript/README.md +2 -0
- package/templates/vite/README.md +2 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import type { RmDirOptions } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "pathe";
|
|
5
|
+
import type { DurableObjectNamespace } from "../durable-object-namespace.ts";
|
|
6
|
+
import type { Workflow } from "../workflow.ts";
|
|
7
|
+
import { DEFAULT_PERSIST_PATH } from "./paths.ts";
|
|
8
|
+
|
|
9
|
+
const PERSIST_ROOT = path.resolve(DEFAULT_PERSIST_PATH);
|
|
10
|
+
const BINDING_TYPE_KEY = {
|
|
11
|
+
d1: "miniflare-D1DatabaseObject",
|
|
12
|
+
kv: "miniflare-KVNamespaceObject",
|
|
13
|
+
r2: "miniflare-R2BucketObject",
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
type BindingType = keyof typeof BINDING_TYPE_KEY;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Delete a binding from the miniflare directory.
|
|
20
|
+
*/
|
|
21
|
+
export async function deleteMiniflareBinding(type: BindingType, id: string) {
|
|
22
|
+
const bindingPath = path.join(PERSIST_ROOT, type, BINDING_TYPE_KEY[type]);
|
|
23
|
+
|
|
24
|
+
// delete durable object/sqlite files
|
|
25
|
+
const namespaceId = durableObjectNamespaceIdFromName(
|
|
26
|
+
BINDING_TYPE_KEY[type],
|
|
27
|
+
id,
|
|
28
|
+
);
|
|
29
|
+
const files = await fs
|
|
30
|
+
.readdir(bindingPath)
|
|
31
|
+
.then((files) => files.filter((file) => file.startsWith(namespaceId)))
|
|
32
|
+
.catch(() => []);
|
|
33
|
+
await Promise.all(files.map((file) => fs.rm(path.join(bindingPath, file))));
|
|
34
|
+
|
|
35
|
+
// delete data directory, if present
|
|
36
|
+
await removeDirectory(path.join(PERSIST_ROOT, type, id), {
|
|
37
|
+
recursive: true,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// delete the miniflare directory if it is empty
|
|
41
|
+
await cleanMiniflareDirectory();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Delete local durable object and workflow data for a given Worker script.
|
|
46
|
+
*/
|
|
47
|
+
export async function deleteMiniflareWorkerData(
|
|
48
|
+
scriptName: string,
|
|
49
|
+
input: {
|
|
50
|
+
durableObjects: DurableObjectNamespace[];
|
|
51
|
+
workflows: Workflow[];
|
|
52
|
+
},
|
|
53
|
+
) {
|
|
54
|
+
await Promise.all(
|
|
55
|
+
input.durableObjects.map((durableObject) =>
|
|
56
|
+
removeDirectory(
|
|
57
|
+
path.join(
|
|
58
|
+
PERSIST_ROOT,
|
|
59
|
+
"do",
|
|
60
|
+
`${scriptName}-${durableObject.className}`,
|
|
61
|
+
),
|
|
62
|
+
{
|
|
63
|
+
recursive: true,
|
|
64
|
+
},
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
await Promise.all(
|
|
69
|
+
input.workflows.map((workflow) =>
|
|
70
|
+
removeDirectory(
|
|
71
|
+
path.join(
|
|
72
|
+
PERSIST_ROOT,
|
|
73
|
+
"workflows",
|
|
74
|
+
`miniflare-workflows-${workflow.workflowName}`,
|
|
75
|
+
),
|
|
76
|
+
{
|
|
77
|
+
recursive: true,
|
|
78
|
+
},
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
await cleanMiniflareDirectory();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Delete the miniflare directory iff it is empty.
|
|
87
|
+
*/
|
|
88
|
+
async function cleanMiniflareDirectory() {
|
|
89
|
+
const directory = path.resolve(".alchemy/miniflare");
|
|
90
|
+
const files = await fs
|
|
91
|
+
.readdir(directory, {
|
|
92
|
+
recursive: true,
|
|
93
|
+
withFileTypes: true,
|
|
94
|
+
})
|
|
95
|
+
.catch((error) => {
|
|
96
|
+
if (!isENOENT(error)) {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
return [];
|
|
100
|
+
});
|
|
101
|
+
if (!files.some((file) => !file.isDirectory())) {
|
|
102
|
+
await removeDirectory(directory, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const removeDirectory = async (path: string, options?: RmDirOptions) => {
|
|
107
|
+
try {
|
|
108
|
+
await fs.rmdir(path, options);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (!isENOENT(error)) {
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const isENOENT = (error: unknown): error is { code: "ENOENT" } =>
|
|
117
|
+
!!error &&
|
|
118
|
+
typeof error === "object" &&
|
|
119
|
+
"code" in error &&
|
|
120
|
+
error.code === "ENOENT";
|
|
121
|
+
|
|
122
|
+
// copied from https://github.com/cloudflare/workers-sdk/blob/a5892915e173ca8666c04fb394e70711e87b46d8/packages/miniflare/src/plugins/shared/index.ts#L236
|
|
123
|
+
const durableObjectNamespaceIdFromName = (uniqueKey: string, name: string) => {
|
|
124
|
+
const key = crypto.createHash("sha256").update(uniqueKey).digest();
|
|
125
|
+
const nameHmac = crypto
|
|
126
|
+
.createHmac("sha256", key)
|
|
127
|
+
.update(name)
|
|
128
|
+
.digest()
|
|
129
|
+
.subarray(0, 16);
|
|
130
|
+
const hmac = crypto
|
|
131
|
+
.createHmac("sha256", key)
|
|
132
|
+
.update(nameHmac)
|
|
133
|
+
.digest()
|
|
134
|
+
.subarray(0, 16);
|
|
135
|
+
return Buffer.concat([nameHmac, hmac]).toString("hex");
|
|
136
|
+
};
|
|
@@ -76,21 +76,38 @@ export class MiniflareController {
|
|
|
76
76
|
private async update() {
|
|
77
77
|
return await this.mutex.lock(async () => {
|
|
78
78
|
const options: miniflare.MiniflareOptions = {
|
|
79
|
-
workers:
|
|
79
|
+
workers: [],
|
|
80
80
|
defaultPersistRoot: path.resolve(DEFAULT_PERSIST_PATH),
|
|
81
81
|
unsafeDevRegistryPath: miniflare.getDefaultDevRegistryPath(),
|
|
82
|
-
analyticsEngineDatasetsPersist: true,
|
|
83
|
-
cachePersist: true,
|
|
84
|
-
d1Persist: true,
|
|
85
|
-
durableObjectsPersist: true,
|
|
86
|
-
kvPersist: true,
|
|
87
|
-
r2Persist: true,
|
|
88
|
-
secretsStorePersist: true,
|
|
89
|
-
workflowsPersist: true,
|
|
90
82
|
log: process.env.DEBUG
|
|
91
83
|
? new miniflare.Log(miniflare.LogLevel.DEBUG)
|
|
92
84
|
: undefined,
|
|
93
85
|
};
|
|
86
|
+
for (const worker of this.options.values()) {
|
|
87
|
+
options.workers.push(worker);
|
|
88
|
+
// avoid creating unnecessary directories
|
|
89
|
+
if (worker.analyticsEngineDatasets) {
|
|
90
|
+
options.analyticsEngineDatasetsPersist = true;
|
|
91
|
+
}
|
|
92
|
+
if (worker.d1Databases) {
|
|
93
|
+
options.d1Persist = true;
|
|
94
|
+
}
|
|
95
|
+
if (worker.durableObjects) {
|
|
96
|
+
options.durableObjectsPersist = true;
|
|
97
|
+
}
|
|
98
|
+
if (worker.kvNamespaces) {
|
|
99
|
+
options.kvPersist = true;
|
|
100
|
+
}
|
|
101
|
+
if (worker.r2Buckets) {
|
|
102
|
+
options.r2Persist = true;
|
|
103
|
+
}
|
|
104
|
+
if (worker.secretsStoreSecrets) {
|
|
105
|
+
options.secretsStorePersist = true;
|
|
106
|
+
}
|
|
107
|
+
if (worker.workflows) {
|
|
108
|
+
options.workflowsPersist = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
94
111
|
return await this.setMiniflareOptions(options);
|
|
95
112
|
});
|
|
96
113
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { RemoteProxyConnectionString } from "miniflare";
|
|
2
2
|
import { HTTPServer } from "../../util/http.ts";
|
|
3
3
|
import { extractCloudflareResult } from "../api-response.ts";
|
|
4
|
-
import {
|
|
4
|
+
import type { CloudflareApi } from "../api.ts";
|
|
5
5
|
import type { WorkerBindingSpec } from "../bindings.ts";
|
|
6
6
|
import { getInternalWorkerBundle } from "../bundle/internal-worker-bundle.ts";
|
|
7
7
|
import { WorkerBundle } from "../worker-bundle.ts";
|
|
8
8
|
import type { WorkerMetadata } from "../worker-metadata.ts";
|
|
9
|
+
import { getAccountSubdomain } from "../worker-subdomain.ts";
|
|
9
10
|
|
|
10
11
|
type WranglerSessionConfig =
|
|
11
12
|
| {
|
|
@@ -30,13 +31,13 @@ export interface RemoteBindingProxy {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export async function createRemoteProxyWorker(input: {
|
|
34
|
+
api: CloudflareApi;
|
|
33
35
|
name: string;
|
|
34
36
|
bindings: WorkerBindingSpec[];
|
|
35
37
|
}): Promise<RemoteBindingProxy> {
|
|
36
|
-
const api = await createCloudflareApi();
|
|
37
38
|
const script = await getInternalWorkerBundle("remote-binding-proxy");
|
|
38
39
|
const [token, subdomain] = await Promise.all([
|
|
39
|
-
createWorkersPreviewToken(api, {
|
|
40
|
+
createWorkersPreviewToken(input.api, {
|
|
40
41
|
name: input.name,
|
|
41
42
|
metadata: {
|
|
42
43
|
main_module: script.bundle.entrypoint,
|
|
@@ -50,7 +51,7 @@ export async function createRemoteProxyWorker(input: {
|
|
|
50
51
|
minimal_mode: true,
|
|
51
52
|
},
|
|
52
53
|
}),
|
|
53
|
-
|
|
54
|
+
getAccountSubdomain(input.api),
|
|
54
55
|
]);
|
|
55
56
|
|
|
56
57
|
const proxyURL = `https://${input.name}.${subdomain}.workers.dev`;
|
|
@@ -218,6 +218,8 @@ export interface PipelineProps extends CloudflareApiOptions {
|
|
|
218
218
|
* @default false
|
|
219
219
|
*/
|
|
220
220
|
adopt?: boolean;
|
|
221
|
+
|
|
222
|
+
dev?: { remote?: boolean };
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
/**
|
|
@@ -337,6 +339,20 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function <
|
|
|
337
339
|
const api = await createCloudflareApi(props);
|
|
338
340
|
const pipelineName = props.name ?? id;
|
|
339
341
|
|
|
342
|
+
if (this.scope.local && !props.dev?.remote) {
|
|
343
|
+
return this({
|
|
344
|
+
type: "pipeline",
|
|
345
|
+
id: this.output?.id ?? "",
|
|
346
|
+
name: this.output?.name ?? pipelineName,
|
|
347
|
+
endpoint: this.output?.endpoint ?? "",
|
|
348
|
+
version: this.output?.version ?? 0,
|
|
349
|
+
source: this.output?.source ?? [],
|
|
350
|
+
destination: props.destination,
|
|
351
|
+
compression: props.compression,
|
|
352
|
+
accountId: this.output?.accountId ?? "",
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
340
356
|
if (this.phase === "delete") {
|
|
341
357
|
if (props.delete !== false) {
|
|
342
358
|
// Delete Pipeline
|
|
@@ -348,7 +364,7 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function <
|
|
|
348
364
|
}
|
|
349
365
|
let pipelineData: CloudflarePipelineResponse;
|
|
350
366
|
|
|
351
|
-
if (this.phase === "create") {
|
|
367
|
+
if (this.phase === "create" || !this.output?.id) {
|
|
352
368
|
// Check if we should adopt an existing pipeline
|
|
353
369
|
try {
|
|
354
370
|
// Try to create pipeline first
|
|
@@ -82,6 +82,15 @@ export interface QueueConsumerProps extends CloudflareApiOptions {
|
|
|
82
82
|
* @default false
|
|
83
83
|
*/
|
|
84
84
|
adopt?: boolean;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* If true, the queue consumer will not be created, but will be retained if it already exists.
|
|
88
|
+
* This is used for local development.
|
|
89
|
+
*
|
|
90
|
+
* @default `false`
|
|
91
|
+
* @internal
|
|
92
|
+
*/
|
|
93
|
+
dev?: boolean;
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
/**
|
|
@@ -148,16 +157,24 @@ export const QueueConsumer = Resource(
|
|
|
148
157
|
_id: string,
|
|
149
158
|
props: QueueConsumerProps,
|
|
150
159
|
): Promise<QueueConsumer> {
|
|
151
|
-
const api = await createCloudflareApi(props);
|
|
152
|
-
|
|
153
160
|
// Get queueId from either props.queue or props.queueId
|
|
154
161
|
const queueId =
|
|
155
162
|
typeof props.queue === "string" ? props.queue : props.queue.id;
|
|
156
163
|
|
|
157
|
-
if (
|
|
158
|
-
|
|
164
|
+
if (this.scope.local && props.dev) {
|
|
165
|
+
return this({
|
|
166
|
+
id: this.output?.id ?? "",
|
|
167
|
+
queueId,
|
|
168
|
+
queue: props.queue,
|
|
169
|
+
type: "worker",
|
|
170
|
+
scriptName: props.scriptName,
|
|
171
|
+
settings: props.settings,
|
|
172
|
+
accountId: this.output?.accountId ?? "",
|
|
173
|
+
});
|
|
159
174
|
}
|
|
160
175
|
|
|
176
|
+
const api = await createCloudflareApi(props);
|
|
177
|
+
|
|
161
178
|
if (this.phase === "delete") {
|
|
162
179
|
logger.log(`Deleting Queue Consumer for queue ${queueId}`);
|
|
163
180
|
if (props.delete !== false && this.output?.id) {
|
|
@@ -168,9 +185,14 @@ export const QueueConsumer = Resource(
|
|
|
168
185
|
// Return void (a deleted consumer has no content)
|
|
169
186
|
return this.destroy();
|
|
170
187
|
}
|
|
188
|
+
|
|
189
|
+
if (!queueId) {
|
|
190
|
+
throw new Error("Either queue or queueId must be provided");
|
|
191
|
+
}
|
|
192
|
+
|
|
171
193
|
let consumerData: CloudflareQueueConsumerResponse;
|
|
172
194
|
|
|
173
|
-
if (this.phase === "create" || this.output?.id
|
|
195
|
+
if (this.phase === "create" || !this.output?.id) {
|
|
174
196
|
try {
|
|
175
197
|
consumerData = await createQueueConsumer(api, queueId, props);
|
|
176
198
|
} catch (err) {
|
package/src/cloudflare/queue.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
import { Resource, ResourceKind } from "../resource.ts";
|
|
3
|
+
import { Scope } from "../scope.ts";
|
|
3
4
|
import { CloudflareApiError, handleApiError } from "./api-error.ts";
|
|
4
5
|
import {
|
|
5
6
|
createCloudflareApi,
|
|
@@ -80,6 +81,12 @@ export interface QueueProps extends CloudflareApiOptions {
|
|
|
80
81
|
* @default false
|
|
81
82
|
*/
|
|
82
83
|
remote?: boolean;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set when `Scope.local` is true to force update to the queue even if it was already deployed live.
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
force?: boolean;
|
|
83
90
|
};
|
|
84
91
|
}
|
|
85
92
|
|
|
@@ -95,7 +102,7 @@ export function isQueue(eventSource: any): eventSource is Queue {
|
|
|
95
102
|
*/
|
|
96
103
|
export interface Queue<Body = unknown>
|
|
97
104
|
extends Resource<"cloudflare::Queue">,
|
|
98
|
-
QueueProps {
|
|
105
|
+
Omit<QueueProps, "dev"> {
|
|
99
106
|
/**
|
|
100
107
|
* Type identifier for Cloudflare Queue
|
|
101
108
|
*/
|
|
@@ -127,6 +134,22 @@ export interface Queue<Body = unknown>
|
|
|
127
134
|
Body: Body;
|
|
128
135
|
|
|
129
136
|
Batch: MessageBatch<Body>;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Development mode properties
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
dev: {
|
|
143
|
+
/**
|
|
144
|
+
* The ID of the queue in development mode
|
|
145
|
+
*/
|
|
146
|
+
id: string;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Whether the queue is running remotely
|
|
150
|
+
*/
|
|
151
|
+
remote: boolean;
|
|
152
|
+
};
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
/**
|
|
@@ -208,26 +231,57 @@ export interface Queue<Body = unknown>
|
|
|
208
231
|
*
|
|
209
232
|
* @see https://developers.cloudflare.com/queues/
|
|
210
233
|
*/
|
|
211
|
-
export
|
|
234
|
+
export async function Queue<T = unknown>(
|
|
235
|
+
id: string,
|
|
236
|
+
props: QueueProps = {},
|
|
237
|
+
): Promise<Queue<T>> {
|
|
238
|
+
return await _Queue(id, {
|
|
239
|
+
...props,
|
|
240
|
+
dev: {
|
|
241
|
+
...(props.dev ?? {}),
|
|
242
|
+
force: Scope.current.local,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const _Queue = Resource("cloudflare::Queue", async function <
|
|
212
248
|
T = unknown,
|
|
213
249
|
>(this: Context<Queue<T>>, id: string, props: QueueProps = {}): Promise<
|
|
214
250
|
Queue<T>
|
|
215
251
|
> {
|
|
216
|
-
const api = await createCloudflareApi(props);
|
|
217
252
|
const queueName = props.name ?? id;
|
|
253
|
+
const dev = {
|
|
254
|
+
id: this.output?.dev?.id ?? this.output?.id ?? id,
|
|
255
|
+
remote: props.dev?.remote ?? false,
|
|
256
|
+
};
|
|
257
|
+
if (this.scope.local && !props.dev?.remote) {
|
|
258
|
+
return this({
|
|
259
|
+
type: "queue",
|
|
260
|
+
id: this.output?.id ?? "",
|
|
261
|
+
name: queueName,
|
|
262
|
+
dev,
|
|
263
|
+
createdOn: this.output?.createdOn ?? new Date().toISOString(),
|
|
264
|
+
modifiedOn: this.output?.modifiedOn ?? new Date().toISOString(),
|
|
265
|
+
Body: undefined as T,
|
|
266
|
+
Batch: undefined! as MessageBatch<T>,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const api = await createCloudflareApi(props);
|
|
218
271
|
|
|
219
272
|
if (this.phase === "delete") {
|
|
220
|
-
if (props.delete !== false) {
|
|
273
|
+
if (props.delete !== false && this.output?.id) {
|
|
221
274
|
// Delete Queue
|
|
222
|
-
await deleteQueue(api, this.output
|
|
275
|
+
await deleteQueue(api, this.output.id);
|
|
223
276
|
}
|
|
224
277
|
|
|
225
278
|
// Return void (a deleted queue has no content)
|
|
226
279
|
return this.destroy();
|
|
227
280
|
}
|
|
281
|
+
|
|
228
282
|
let queueData: CloudflareQueueResponse;
|
|
229
283
|
|
|
230
|
-
if (this.phase === "create") {
|
|
284
|
+
if (this.phase === "create" || !this.output?.id) {
|
|
231
285
|
try {
|
|
232
286
|
queueData = await createQueue(api, queueName, props);
|
|
233
287
|
} catch (error) {
|
|
@@ -282,7 +336,7 @@ export const Queue = Resource("cloudflare::Queue", async function <
|
|
|
282
336
|
createdOn: queueData.result.created_on || new Date().toISOString(),
|
|
283
337
|
modifiedOn: queueData.result.modified_on || new Date().toISOString(),
|
|
284
338
|
accountId: api.accountId,
|
|
285
|
-
dev
|
|
339
|
+
dev,
|
|
286
340
|
// phantom properties
|
|
287
341
|
Body: undefined as T,
|
|
288
342
|
Batch: undefined! as MessageBatch<T>,
|
|
@@ -372,12 +426,8 @@ export async function getQueue(
|
|
|
372
426
|
*/
|
|
373
427
|
export async function deleteQueue(
|
|
374
428
|
api: CloudflareApi,
|
|
375
|
-
queueId
|
|
429
|
+
queueId: string,
|
|
376
430
|
): Promise<void> {
|
|
377
|
-
if (!queueId) {
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
431
|
// Delete Queue
|
|
382
432
|
const deleteResponse = await api.delete(
|
|
383
433
|
`/accounts/${api.accountId}/queues/${queueId}`,
|
package/src/cloudflare/route.ts
CHANGED
|
@@ -48,6 +48,15 @@ export interface RouteProps extends CloudflareApiOptions {
|
|
|
48
48
|
* @default false
|
|
49
49
|
*/
|
|
50
50
|
adopt?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* If true, the route will not be created, but will be retained if it already exists.
|
|
54
|
+
* This is used for local development.
|
|
55
|
+
*
|
|
56
|
+
* @default `false`
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
dev?: boolean;
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
/**
|
|
@@ -150,6 +159,21 @@ export const Route = Resource(
|
|
|
150
159
|
return this.destroy();
|
|
151
160
|
}
|
|
152
161
|
|
|
162
|
+
if (this.scope.local && props.dev) {
|
|
163
|
+
// Get or infer zone ID for noop mode too
|
|
164
|
+
let zoneId = props.zoneId;
|
|
165
|
+
if (!zoneId) {
|
|
166
|
+
zoneId = this.output?.zoneId ?? "noop-zone";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return this({
|
|
170
|
+
id: this.output?.id ?? "noop-route",
|
|
171
|
+
pattern: props.pattern,
|
|
172
|
+
script: scriptName,
|
|
173
|
+
zoneId: zoneId,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
153
177
|
// Get or infer zone ID (only needed for create/update phases)
|
|
154
178
|
let zoneId = props.zoneId;
|
|
155
179
|
if (!zoneId) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
import { Resource } from "../resource.ts";
|
|
3
3
|
import { memoize } from "../util/memoize.ts";
|
|
4
|
+
import { withExponentialBackoff } from "../util/retry.ts";
|
|
5
|
+
import { CloudflareApiError } from "./api-error.ts";
|
|
4
6
|
import { extractCloudflareResult } from "./api-response.ts";
|
|
5
7
|
import {
|
|
6
8
|
createCloudflareApi,
|
|
@@ -25,6 +27,14 @@ interface WorkerSubdomainProps extends CloudflareApiOptions {
|
|
|
25
27
|
* @default false
|
|
26
28
|
*/
|
|
27
29
|
retain?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* If true, the subdomain will not be created, but will be retained if it already exists.
|
|
32
|
+
* This is used for local development.
|
|
33
|
+
*
|
|
34
|
+
* @default `false`
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
dev?: boolean;
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
export interface WorkerSubdomain
|
|
@@ -42,6 +52,12 @@ export const WorkerSubdomain = Resource(
|
|
|
42
52
|
id: string,
|
|
43
53
|
props: WorkerSubdomainProps,
|
|
44
54
|
) {
|
|
55
|
+
if (this.scope.local && props.dev) {
|
|
56
|
+
return this({
|
|
57
|
+
url: this.output?.url ?? "https://unavailable.alchemy.run",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
const api = await createCloudflareApi(props);
|
|
46
62
|
if (this.phase === "delete") {
|
|
47
63
|
if (!props.retain) {
|
|
@@ -86,12 +102,18 @@ export async function enableWorkerSubdomain(
|
|
|
86
102
|
api: CloudflareApi,
|
|
87
103
|
scriptName: string,
|
|
88
104
|
) {
|
|
89
|
-
await
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
105
|
+
await withExponentialBackoff(
|
|
106
|
+
() =>
|
|
107
|
+
extractCloudflareResult<SubdomainResponse>(
|
|
108
|
+
`enable subdomain for "${scriptName}"`,
|
|
109
|
+
api.post(
|
|
110
|
+
`/accounts/${api.accountId}/workers/scripts/${scriptName}/subdomain`,
|
|
111
|
+
{ enabled: true, previews_enabled: true },
|
|
112
|
+
),
|
|
113
|
+
),
|
|
114
|
+
(error) => error instanceof CloudflareApiError && error.status === 404,
|
|
115
|
+
10,
|
|
116
|
+
1000,
|
|
95
117
|
);
|
|
96
118
|
}
|
|
97
119
|
|
|
@@ -105,6 +127,7 @@ export async function getWorkerSubdomain(
|
|
|
105
127
|
`/accounts/${api.accountId}/workers/scripts/${scriptName}/subdomain`,
|
|
106
128
|
),
|
|
107
129
|
).catch((error): SubdomainResponse => {
|
|
130
|
+
console.log("error", error);
|
|
108
131
|
if (error.status === 404) {
|
|
109
132
|
return { enabled: false, previews_enabled: false };
|
|
110
133
|
}
|