8000 fix(cloudflare): use dispatch namespace asset upload for WFP by sam-goodwin · Pull Request #412 · sam-goodwin/alchemy · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix(cloudflare): use dispatch namespace asset upload for WFP #412

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions alchemy/src/cloudflare/worker-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,17 @@ interface UploadResponse {
*/
export async function uploadAssets(
api: CloudflareApi,
workerName: string,
assets: Assets,
assetConfig?: WorkerProps["assets"],
{
workerName,
assets,
assetConfig,
namespace,
}: {
workerName: string;
assets: Assets;
assetConfig?: WorkerProps["assets"];
namespace?: string;
},
): Promise<AssetUploadResult> {
// Process the assets configuration once at the beginning
const processedConfig = createAssetConfig(assetConfig);
Expand All @@ -69,7 +77,9 @@ export async function uploadAssets(

// Start the upload session
const uploadSessionResponse = await api.post(
`/accounts/${api.accountId}/workers/scripts/${workerName}/assets-upload-session`,
namespace
? `/accounts/${api.accountId}/workers/dispatch/namespaces/${namespace}/scripts/${workerName}/assets-upload-session`
: `/accounts/${api.accountId}/workers/scripts/${workerName}/assets-upload-session`,
JSON.stringify({ manifest }),
{
headers: { "Content-Type": "application/json" },
Expand Down
24 changes: 12 additions & 12 deletions alchemy/src/cloudflare/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,13 @@ export const _Worker = Resource(
}
}

// Get dispatch namespace if specified
const dispatchNamespace = props.namespace
? typeof props.namespace === "string"
? props.namespace
: props.namespace.namespace
: undefined;

// Upload any assets and get completion tokens
let assetUploadResult: AssetUploadResult | undefined;
if (assetsBindings.length > 0) {
Expand All @@ -934,12 +941,12 @@ export const _Worker = Resource(
const assetBinding = assetsBindings[0];

// Upload the assets and get the completion token
assetUploadResult = await uploadAssets(
api,
assetUploadResult = await uploadAssets(api, {
workerName,
assetBinding.assets,
props.assets,
);
assets: assetBinding.assets,
assetConfig: props.assets,
namespace: dispatchNamespace,
});
}

// Prepare metadata with bindings
Expand All @@ -956,13 +963,6 @@ export const _Worker = Resource(
assetUploadResult,
);

// Get dispatch namespace if specified
const dispatchNamespace = props.namespace
? typeof props.namespace === "string"
? props.namespace
: props.namespace.namespace
: undefined;

// Deploy worker (either as version or live worker)
const versionResult = await putWorker(
api,
Expand Down
104 changes: 104 additions & 0 deletions alchemy/test/cloudflare/dispatch-namespace.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { describe, expect } from "vitest";
import { alchemy } from "../../src/alchemy.ts";
import { createCloudflareApi } from "../../src/cloudflare/api.ts";
import { Assets } from "../../src/cloudflare/assets.ts";
import { DispatchNamespace } from "../../src/cloudflare/dispatch-namespace.ts";
import { Worker } from "../../src/cloudflare/worker.ts";
import { BRANCH_PREFIX } from "../util.ts";
Expand Down Expand Up @@ -140,6 +143,107 @@ describe("Dispatch Namespace Resource", () => {
}
});

test("dispatch namespace with asset bindings", async (scope) => {
const workerName = `${BRANCH_PREFIX}-asset-worker`;
const dispatcherWorkerName = `${BRANCH_PREFIX}-asset-dispatcher`;
const namespaceName = `${BRANCH_PREFIX}-asset-namespace`;
let tempDir: string | undefined;

let assetWorker: Worker | undefined;
let dispatcherWorker: Worker | undefined;
let dispatchNamespace: DispatchNamespace | undefined;

try {
// 1. Create a temporary directory with test assets
tempDir = path.join(".out", "alchemy-dispatch-assets-test");
await fs.rm(tempDir, { recursive: true, force: true });
await fs.mkdir(tempDir, { recursive: true });

// Create test files
const htmlContent = "Hello from dispatch namespace assets!";
const cssContent = "body { color: blue; }";

await Promise.all([
fs.writeFile(path.join(tempDir, "index.html"), htmlContent),
fs.writeFile(path.join(tempDir, "styles.css"), cssContent),
]);

// 2. Create assets resource
const assets = await Assets("dispatch-static-assets", {
path: tempDir,
});

// 3. Create a dispatch namespace
dispatchNamespace = await DispatchNamespace("asset-dispatch-namespace", {
namespace: namespaceName,
adopt: true,
});

// 4. Create a worker in the dispatch namespace with asset bindings
assetWorker = await Worker(workerName, {
name: workerName,
script: `
export default {
async fetch(request, env, ctx) {
return env.ASSETS.fetch(request);
}
}
`,
namespace: dispatchNamespace,
url: false,
bindings: {
ASSETS: assets,
},
});

// 5. Create a dispatcher worker that routes to the asset worker
dispatcherWorker = await Worker(dispatcherWorkerName, {
name: dispatcherWorkerName,
script: `
export default {
async fetch(request, env, ctx) {
return await env.NAMESPACE.get('${workerName}').fetch(request);
}
}
`,
bindings: {
NAMESPACE: dispatchNamespace,
},
url: true,
});

// 6. Test static asset serving through dispatch namespace
const htmlResponse = await fetchAndExpectOK(
`${dispatcherWorker.url}/index.html`,
);
const htmlText = await htmlResponse.text();
expect(htmlText).toEqual(htmlContent);

// Test CSS file
const cssResponse = await fetchAndExpectOK(
`${dispatcherWorker.url}/styles.css`,
);
const cssText = await cssResponse.text();
expect(cssText).toEqual(cssContent);
} finally {
// Clean up temporary directory
if (tempDir) {
await fs.rm(tempDir, { recursive: true, force: true });
}

await alchemy.destroy(scope);
if (assetWorker) {
await assertWorkerDoesNotExist(assetWorker.name);
}
if (dispatcherWorker) {
await assertWorkerDoesNotExist(dispatcherWorker.name);
}
if (dispatchNamespace) {
await assertDispatchNamespaceNotExists(dispatchNamespace.namespace);
}
}
});

async function assertDispatchNamespaceExists(
namespace: string,
): Promise<void> {
Expand Down
0