Description
The miniflare KV bulk-get handler (@POST("/bulk/get")) wraps each present key as { value, metadata }, but the wrap is gated on the truthiness of the value, so present keys holding a falsy value (JSON 0, false, null, or text "") are returned as the bare value and lose their metadata — diverging from the shape the runtime binding expects.
This contradicts the repo's own tested contract (packages/miniflare/test/plugins/kv/index.spec.ts), which asserts every present key in a bulk getWithMetadata yields { value, metadata } and only missing keys yield null. The existing tests only use truthy values, so the falsy case is uncovered. Empty-string values are the most common real trigger.
Steps to reproduce
await env.KV.put("k", "", { metadata: { a: 1 } });
const r = await env.KV.getWithMetadata(["k"]);
// expected: Map { "k" => { value: "", metadata: { a: 1 } } }
// actual: { "k": "" } — metadata lost, shape wrong
Root cause
packages/miniflare/src/workers/kv/namespace.worker.ts (processKeyValue): if (val && withMetadata) { return [{ value: val, metadata: … }, size]; } return [val, size]; — the val && gates on value-truthiness instead of key-presence.
Suggested fix
Gate on entry presence rather than value truthiness, e.g. compute const exists = obj?.value != null (or obj !== null) and use if (exists && withMetadata). Missing keys continue to return a bare null; present-but-falsy values keep their { value, metadata } wrapper.
Description
The miniflare KV bulk-get handler (
@POST("/bulk/get")) wraps each present key as{ value, metadata }, but the wrap is gated on the truthiness of the value, so present keys holding a falsy value (JSON0,false,null, or text"") are returned as the bare value and lose their metadata — diverging from the shape the runtime binding expects.This contradicts the repo's own tested contract (
packages/miniflare/test/plugins/kv/index.spec.ts), which asserts every present key in a bulkgetWithMetadatayields{ value, metadata }and only missing keys yieldnull. The existing tests only use truthy values, so the falsy case is uncovered. Empty-string values are the most common real trigger.Steps to reproduce
Root cause
packages/miniflare/src/workers/kv/namespace.worker.ts(processKeyValue):if (val && withMetadata) { return [{ value: val, metadata: … }, size]; } return [val, size];— theval &&gates on value-truthiness instead of key-presence.Suggested fix
Gate on entry presence rather than value truthiness, e.g. compute
const exists = obj?.value != null(orobj !== null) and useif (exists && withMetadata). Missing keys continue to return a barenull; present-but-falsy values keep their{ value, metadata }wrapper.