Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: cloudflare/workers-sdk
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: wrangler@4.5.0
Choose a base ref
...
head repository: cloudflare/workers-sdk
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: wrangler@4.5.1
Choose a head ref
  • 8 commits
  • 36 files changed
  • 10 contributors

Commits on Mar 27, 2025

  1. Fix Workers KV ownership (#8693)

    Signed-off-by: ferhat elmas <elmas.ferhat@gmail.com>
    ferhatelmas authored Mar 27, 2025
    Copy the full SHA
    a24cf19 View commit details
  2. vite-plugin: show warning if the user has forgotten to turn on nodejs…

    …_compat (#8207)
    
    * Add warnings to `vite dev` for when code (or a library) uses Node.js APIs without setting the `nodejs_compat` flag
    
    * fixups
    petebacondarwin authored Mar 27, 2025
    Copy the full SHA
    910007b View commit details
  3. Remove NodeJSCompatModule (#8666)

    penalosa authored Mar 27, 2025
    Copy the full SHA
    f29f018 View commit details
  4. feat: add bulk gets for kv in miniflare (#8623)

    * feat: add bulk gets for kv in miniflare
    
    * update workerd version
    
    * fix: lint
    
    * test: add tests for metadata validation
    
    * update changeset
    
    * fix: address pr comments
    
    * refactor: small refactor when processing object
    
    ---------
    
    Co-authored-by: talves <talves@cloudflare.com>
    teresalves and talves authored Mar 27, 2025
    Copy the full SHA
    cad99dc View commit details
  5. vite-plugin: ensure that we don't crash when logging Node.js warnings…

    … when running in react-router builds (#8702)
    petebacondarwin authored Mar 27, 2025
    Copy the full SHA
    fcd71f8 View commit details
  6. [wrangler] Wrangler pages deployments list - added ID to output (#8641)

    * [wrangler] Wrangler pages deployments list - added ID to output
    
    * Update changeset messaging
    
    ---------
    
    Co-authored-by: Enigo <>
    Co-authored-by: Daniel Walsh <walshydev@gmail.com>
    Enigo and WalshyDev authored Mar 27, 2025
    Copy the full SHA
    f378b4d View commit details
  7. Revert "[wrangler] Wrangler pages deployments list - added ID to outp…

    …ut (#8641)" (#8704)
    
    This reverts commit f378b4d.
    CarmenPopoviciu authored Mar 27, 2025
    Copy the full SHA
    d62cc68 View commit details
  8. Version Packages (#8695)

    Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
    workers-devprod and github-actions[bot] authored Mar 27, 2025
    Copy the full SHA
    42f4912 View commit details
Showing with 523 additions and 75 deletions.
  1. +5 −3 CODEOWNERS
  2. +10 −0 packages/miniflare/CHANGELOG.md
  3. +1 −1 packages/miniflare/package.json
  4. +3 −11 packages/miniflare/src/plugins/core/modules.ts
  5. +0 −1 packages/miniflare/src/runtime/config/workerd.ts
  6. +1 −0 packages/miniflare/src/workers/kv/constants.ts
  7. +73 −3 packages/miniflare/src/workers/kv/namespace.worker.ts
  8. +1 −1 packages/miniflare/test/fixtures/modules/index.node.cjs
  9. +11 −11 packages/miniflare/test/plugins/core/modules.spec.ts
  10. +102 −0 packages/miniflare/test/plugins/kv/index.spec.ts
  11. +20 −2 packages/miniflare/test/test-shared/miniflare.ts
  12. +7 −0 packages/pages-shared/CHANGELOG.md
  13. +1 −1 packages/pages-shared/package.json
  14. +13 −0 packages/vite-plugin-cloudflare/CHANGELOG.md
  15. +1 −1 packages/vite-plugin-cloudflare/package.json
  16. +18 −0 packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-warnings/warnings.spec.ts
  17. +5 −1 packages/vite-plugin-cloudflare/playground/node-compat/package.json
  18. +2 −1 packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.worker.json
  19. +15 −0 packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-warnings.ts
  20. +10 −0 packages/vite-plugin-cloudflare/playground/node-compat/worker-warnings/index.ts
  21. +5 −0 packages/vite-plugin-cloudflare/playground/node-compat/worker-warnings/wrangler.toml
  22. +1 −0 packages/vite-plugin-cloudflare/playground/package.json
  23. +122 −7 packages/vite-plugin-cloudflare/src/index.ts
  24. +55 −3 packages/vite-plugin-cloudflare/src/node-js-compat.ts
  25. +10 −0 packages/vitest-pool-workers/CHANGELOG.md
  26. +1 −1 packages/vitest-pool-workers/package.json
  27. +2 −5 packages/vitest-pool-workers/src/pool/index.ts
  28. +4 −6 packages/vitest-pool-workers/src/pool/module-fallback.ts
  29. +9 −0 packages/wrangler/CHANGELOG.md
  30. +1 −1 packages/wrangler/package.json
  31. +9 −9 packages/wrangler/src/__tests__/api/startDevWorker/LocalRuntimeController.test.ts
  32. +1 −2 packages/wrangler/src/config/environment.ts
  33. +0 −1 packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts
  34. +0 −1 packages/wrangler/src/deployment-bundle/module-collection.ts
  35. +1 −2 packages/wrangler/src/deployment-bundle/worker.ts
  36. +3 −0 pnpm-lock.yaml
8 changes: 5 additions & 3 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -22,9 +22,11 @@
/packages/create-cloudflare/ @cloudflare/c3 @cloudflare/wrangler

# Workers KV ownership
/packages/wrangler/src/kv @cloudflare/kv @cloudflare/wrangler
/packages/wrangler/src/__tests__/kv.local.test.ts @cloudflare/kv @cloudflare/wrangler
/packages/wrangler/src/__tests__/kv.test.ts @cloudflare/kv @cloudflare/wrangler
/packages/wrangler/src/kv @cloudflare/workers-kv @cloudflare/wrangler
/packages/wrangler/src/__tests__/kv.local.test.ts @cloudflare/workers-kv @cloudflare/wrangler
/packages/wrangler/src/__tests__/kv.test.ts @cloudflare/workers-kv @cloudflare/wrangler
/packages/miniflare/src/workers/kv @cloudflare/workers-kv @cloudflare/wrangler
/packages/miniflare/test/plugins/kv @cloudflare/workers-kv @cloudflare/wrangler

# kv-asset-handler ownership
/packages/kv-asset-handler/ @cloudflare/wrangler
10 changes: 10 additions & 0 deletions packages/miniflare/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# miniflare

## 4.20250321.1

### Minor Changes

- [#8623](https://github.com/cloudflare/workers-sdk/pull/8623) [`cad99dc`](https://github.com/cloudflare/workers-sdk/commit/cad99dc78d76e35f846e85ac328effff8ba9477d) Thanks [@teresalves](https://github.com/teresalves)! - Add Miniflare Workers KV bulk get support

### Patch Changes

- [#8666](https://github.com/cloudflare/workers-sdk/pull/8666) [`f29f018`](https://github.com/cloudflare/workers-sdk/commit/f29f01813683ab3e42c53738be3d49a0f8cba512) Thanks [@penalosa](https://github.com/penalosa)! - Remove `NodeJSCompatModule`. This was never fully supported, and never worked for deploying Workers from Wrangler.

## 4.20250321.0

### Patch Changes
2 changes: 1 addition & 1 deletion packages/miniflare/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "miniflare",
"version": "4.20250321.0",
"version": "4.20250321.1",
"description": "Fun, full-featured, fully-local simulator for Cloudflare Workers",
"keywords": [
"cloudflare",
14 changes: 3 additions & 11 deletions packages/miniflare/src/plugins/core/modules.ts
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ const SUGGEST_NODE =
"that uses Node.js built-ins, you'll either need to:" +
"\n- Bundle your Worker, configuring your bundler to polyfill Node.js built-ins" +
"\n- Configure your bundler to load Workers-compatible builds by changing the main fields/conditions" +
"\n- Enable the `nodejs_compat` compatibility flag and use the `NodeJsCompatModule` module type" +
"\n- Enable the `nodejs_compat` compatibility flag" +
"\n- Find an alternative package that doesn't require Node.js built-ins";

const builtinModulesWithPrefix = builtinModules.concat(
@@ -43,7 +43,6 @@ export function maybeGetStringScriptPathIndex(
export const ModuleRuleTypeSchema = z.enum([
"ESModule",
"CommonJS",
"NodeJsCompatModule",
"Text",
"Data",
"CompiledWasm",
@@ -52,7 +51,7 @@ export const ModuleRuleTypeSchema = z.enum([
]);
export type ModuleRuleType = z.infer<typeof ModuleRuleTypeSchema>;

type JavaScriptModuleRuleType = "ESModule" | "CommonJS" | "NodeJsCompatModule";
type JavaScriptModuleRuleType = "ESModule" | "CommonJS";

export const ModuleRuleSchema = z.object({
type: ModuleRuleTypeSchema,
@@ -291,15 +290,14 @@ ${dim(modulesConfig)}`;
}
const spec = specExpression.value;

const isNodeJsCompatModule = referencingType === "NodeJsCompatModule";
if (
// `cloudflare:` and `workerd:` imports don't need to be included explicitly
spec.startsWith("cloudflare:") ||
spec.startsWith("workerd:") ||
// Node.js compat v1 requires imports to be prefixed with `node:`
(this.#nodejsCompatMode === "v1" && spec.startsWith("node:")) ||
// Node.js compat modules and v2 can also handle non-prefixed imports
((this.#nodejsCompatMode === "v2" || isNodeJsCompatModule) &&
(this.#nodejsCompatMode === "v2" &&
builtinModulesWithPrefix.includes(spec)) ||
// Async Local Storage mode (node_als) only deals with `node:async_hooks` imports
(this.#nodejsCompatMode === "als" && spec === "node:async_hooks") ||
@@ -345,7 +343,6 @@ ${dim(modulesConfig)}`;
switch (rule.type) {
case "ESModule":
case "CommonJS":
case "NodeJsCompatModule":
const code = data.toString("utf8");
this.#visitJavaScriptModule(code, identifier, rule.type);
break;
@@ -383,8 +380,6 @@ function createJavaScriptModule(
return { name, esModule: code };
} else if (type === "CommonJS") {
return { name, commonJsModule: code };
} else if (type === "NodeJsCompatModule") {
return { name, nodeJsCompatModule: code };
}
// noinspection UnnecessaryLocalVariableJS
const exhaustive: never = type;
@@ -409,7 +404,6 @@ export function convertModuleDefinition(
switch (def.type) {
case "ESModule":
case "CommonJS":
case "NodeJsCompatModule":
return createJavaScriptModule(
contentsToString(contents),
name,
@@ -441,8 +435,6 @@ function convertWorkerModule(mod: Worker_Module): ModuleDefinition {

if ("esModule" in m) return { path, type: "ESModule" };
else if ("commonJsModule" in m) return { path, type: "CommonJS" };
else if ("nodeJsCompatModule" in m)
return { path, type: "NodeJsCompatModule" };
else if ("text" in m) return { path, type: "Text" };
else if ("data" in m) return { path, type: "Data" };
else if ("wasm" in m) return { path, type: "CompiledWasm" };
1 change: 0 additions & 1 deletion packages/miniflare/src/runtime/config/workerd.ts
Original file line number Diff line number Diff line change
@@ -78,7 +78,6 @@ export type Worker_Module = {
| { data?: Uint8Array }
| { wasm?: Uint8Array }
| { json?: string }
| { nodeJsCompatModule?: string }
| { pythonModule?: string }
| { pythonRequirement?: string }
);
1 change: 1 addition & 0 deletions packages/miniflare/src/workers/kv/constants.ts
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ export const SiteBindings = {
// This ensures edge caching of Workers Sites files is disabled, and the latest
// local version is always served.
export const SITES_NO_CACHE_PREFIX = "$__MINIFLARE_SITES__$/";
export const MAX_BULK_GET_KEYS = 100;

export function encodeSitesKey(key: string): string {
// `encodeURIComponent()` ensures `ETag`s used by `@cloudflare/kv-asset-handler`
76 changes: 73 additions & 3 deletions packages/miniflare/src/workers/kv/namespace.worker.ts
Original file line number Diff line number Diff line change
@@ -4,13 +4,15 @@ import {
DELETE,
GET,
HttpError,
KeyValueEntry,
KeyValueStorage,
maybeApply,
MiniflareDurableObject,
POST,
PUT,
RouteHandler,
} from "miniflare:shared";
import { KVHeaders, KVLimits, KVParams } from "./constants";
import { KVHeaders, KVLimits, KVParams, MAX_BULK_GET_KEYS } from "./constants";
import {
decodeKey,
decodeListOptions,
@@ -73,6 +75,42 @@ function secondsToMillis(seconds: number): number {
return seconds * 1000;
}

async function processKeyValue(
obj: KeyValueEntry<unknown> | null,
type: "text" | "json" = "text",
withMetadata = false
) {
const decoder = new TextDecoder();
let decodedValue = "";
if (obj?.value) {
for await (const chunk of obj?.value) {
decodedValue += decoder.decode(chunk, { stream: true });
}
decodedValue += decoder.decode();
}

let val = null;
try {
val = !obj?.value
? null
: type === "json"
? JSON.parse(decodedValue)
: decodedValue;
} catch (err: any) {
throw new HttpError(
400,
"At least one of the requested keys corresponds to a non-JSON value"
);
}
if (val && withMetadata) {
return {
value: val,
metadata: obj?.metadata ?? null,
};
}
return val;
}

export class KVNamespaceObject extends MiniflareDurableObject {
#storage?: KeyValueStorage;
get storage() {
@@ -81,13 +119,46 @@ export class KVNamespaceObject extends MiniflareDurableObject {
}

@GET("/:key")
@POST("/bulk/get")
get: RouteHandler<KVParams> = async (req, params, url) => {
if (req.method === "POST" && req.body != null) {
let decodedBody = "";
const decoder = new TextDecoder();
for await (const chunk of req.body) {
decodedBody += decoder.decode(chunk, { stream: true });
}
decodedBody += decoder.decode();
const parsedBody = JSON.parse(decodedBody);
const keys: string[] = parsedBody.keys;
const type = parsedBody?.type;
if (type && type !== "text" && type !== "json") {
return new Response(`Type ${type} is invalid`, { status: 400 });
}
const obj: { [key: string]: any } = {};
if (keys.length > MAX_BULK_GET_KEYS) {
return new Response(`Accepting a max of 100 keys, got ${keys.length}`, {
status: 400,
});
}
for (const key of keys) {
validateGetOptions(key, { cacheTtl: parsedBody?.cacheTtl });
const entry = await this.storage.get(key);
const value = await processKeyValue(
entry,
parsedBody?.type,
parsedBody?.withMetadata
);
obj[key] = value;
}

return new Response(JSON.stringify(obj));
}

// Decode URL parameters
const key = decodeKey(params, url.searchParams);
const cacheTtlParam = url.searchParams.get(KVParams.CACHE_TTL);
const cacheTtl =
cacheTtlParam === null ? undefined : parseInt(cacheTtlParam);

// Get value from storage
validateGetOptions(key, { cacheTtl });
const entry = await this.storage.get(key);
@@ -114,7 +185,6 @@ export class KVNamespaceObject extends MiniflareDurableObject {
const rawExpiration = url.searchParams.get(KVParams.EXPIRATION);
const rawExpirationTtl = url.searchParams.get(KVParams.EXPIRATION_TTL);
const rawMetadata = req.headers.get(KVHeaders.METADATA);

// Validate key, expiration and metadata
const now = millisToSeconds(this.timers.now());
const { expiration, metadata } = validatePutOptions(key, {
2 changes: 1 addition & 1 deletion packages/miniflare/test/fixtures/modules/index.node.cjs
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ const assert = require("assert");
// Test `require()` of Node built-in module with prefix
const assert2 = require("node:assert");

// `Buffer` should be global in `NodeJsCompatModule`s
// `Buffer` should be global in `CommonJS`s with node_compat_v2 turned on
assert.strictEqual(typeof Buffer, "function");
assert2(true);

22 changes: 11 additions & 11 deletions packages/miniflare/test/plugins/core/modules.spec.ts
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ test("Miniflare: accepts manually defined modules", async (t) => {
// Check with just `path`
const mf = new Miniflare({
compatibilityDate: "2023-08-01",
compatibilityFlags: ["nodejs_compat"],
compatibilityFlags: ["nodejs_compat_v2"],
// TODO(soon): remove `modulesRoot` once https://github.com/cloudflare/workerd/issues/1101 fixed
// and add separate test for that
modulesRoot: ROOT,
@@ -29,7 +29,7 @@ test("Miniflare: accepts manually defined modules", async (t) => {
{ type: "ESModule", path: path.join(ROOT, "blobs.mjs") },
{ type: "ESModule", path: path.join(ROOT, "blobs-indirect.mjs") },
{ type: "CommonJS", path: path.join(ROOT, "index.cjs") },
{ type: "NodeJsCompatModule", path: path.join(ROOT, "index.node.cjs") },
{ type: "CommonJS", path: path.join(ROOT, "index.node.cjs") },
// Testing modules in subdirectories
{ type: "Text", path: path.join(ROOT, "blobs", "text.txt") },
{ type: "Data", path: path.join(ROOT, "blobs", "data.bin") },
@@ -51,7 +51,7 @@ test("Miniflare: accepts manually defined modules", async (t) => {
"AGFzbQEAAAABBwFgAn9/AX8DAgEABwcBA2FkZAAACgkBBwAgACABawsACgRuYW1lAgMBAAA=";
await mf.setOptions({
compatibilityDate: "2023-08-01",
compatibilityFlags: ["nodejs_compat"],
compatibilityFlags: ["nodejs_compat_v2"],
modules: [
{ type: "ESModule", path: path.join(ROOT, "index.mjs") },
{
@@ -79,7 +79,7 @@ test("Miniflare: accepts manually defined modules", async (t) => {
`,
},
{
type: "NodeJsCompatModule",
type: "CommonJS",
path: path.join(ROOT, "index.node.cjs"),
contents: `module.exports = "node:";`,
},
@@ -112,14 +112,14 @@ test("Miniflare: automatically collects modules", async (t) => {
modules: true,
modulesRoot: ROOT,
modulesRules: [
// Implicitly testing default module rules for `ESModule` and `CommonJS`
{ type: "NodeJsCompatModule", include: ["**/*.node.cjs"] },
// Implicitly testing default module rules for `ESModule`
{ type: "CommonJS", include: ["**/*.node.cjs", "**/*.cjs"] },
{ type: "Text", include: ["**/*.txt"] },
{ type: "Data", include: ["**/*.bin"] },
{ type: "CompiledWasm", include: ["**/*.wasm"] },
],
compatibilityDate: "2023-08-01",
compatibilityFlags: ["nodejs_compat"],
compatibilityFlags: ["nodejs_compat_v2"],
scriptPath: path.join(ROOT, "index.mjs"),
});
t.teardown(() => mf.dispose());
@@ -206,14 +206,14 @@ test("Miniflare: cannot automatically collect modules from dynamic import expres
modules: true,
modulesRoot: ROOT,
modulesRules: [
// Implicitly testing default module rules for `ESModule` and `CommonJS`
{ type: "NodeJsCompatModule", include: ["**/*.node.cjs"] },
// Implicitly testing default module rules for `ESModule`
{ type: "CommonJS", include: ["**/*.node.cjs", "**/*.cjs"] },
{ type: "Text", include: ["**/*.txt"] },
{ type: "Data", include: ["**/*.bin"] },
{ type: "CompiledWasm", include: ["**/*.wasm"] },
],
compatibilityDate: "2023-08-01",
compatibilityFlags: ["nodejs_compat"],
compatibilityFlags: ["nodejs_compat_v2"],
scriptPath,
});

@@ -233,7 +233,7 @@ You must manually define your modules when constructing Miniflare:
modules: [
{ type: "ESModule", path: "index-dynamic.mjs" },
{ type: "CommonJS", path: "index.cjs" },
{ type: "NodeJsCompatModule", path: "index.node.cjs" },
{ type: "CommonJS", path: "index.node.cjs" },
{ type: "ESModule", path: "blobs-indirect.mjs" },
{ type: "ESModule", path: "blobs.mjs" },
{ type: "Text", path: "blobs/text.txt" },
Loading