Summary
Node's Blob object URL surface rejects non-Blob inputs to URL.createObjectURL() and returns blob:nodedata:<uuid> identifiers for valid Blob values. Perry's object URL helper currently maps invalid handles to an empty string and generates monotonic 32-hex identifiers without UUID separators.
Node.js behavior
Observed with Node v25.9.0:
const { Blob, resolveObjectURL } = require("node:buffer");
URL.createObjectURL("x");
// TypeError ERR_INVALID_ARG_TYPE: The "obj" argument must be an instance of Blob
URL.createObjectURL({});
// TypeError ERR_INVALID_ARG_TYPE: The "obj" argument must be an instance of Blob
const b = new Blob(["hi"]);
const id = URL.createObjectURL(b);
console.log(id);
// blob:nodedata:<uuid>, e.g. blob:nodedata:50e4632e-6844-422a-b2d5-68227d49b179
console.log(resolveObjectURL(id)?.size); // 2
URL.revokeObjectURL(id);
console.log(resolveObjectURL(id)); // undefined
URL.revokeObjectURL(nonString) and resolveObjectURL(nonStringOrUnknown) are lenient and return undefined, but createObjectURL validates that the object is a Blob.
Current Perry behavior
crates/perry-stdlib/src/fetch_blob.rs implements the object URL registry:
js_url_create_object_url(blob_handle) calls handle_id(blob_handle) and returns an empty string when the id is 0; it does not throw for strings, objects, or other non-Blob values.
- Valid ids are formatted with
format!("blob:nodedata:{:032x}", n), so generated ids are monotonic fixed-width hex strings rather than Node's UUID-shaped blob:nodedata:<uuid> strings.
resolveObjectURL/revokeObjectURL leniency broadly matches Node for bad or unknown URLs, so the validation gap is specific to creation.
Suggested test surface
URL.createObjectURL("x") and URL.createObjectURL({}) throw TypeError with code ERR_INVALID_ARG_TYPE.
URL.createObjectURL(new Blob(["hi"])) returns a string matching ^blob:nodedata:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$.
resolveObjectURL(id) returns a Blob with the original size/type, and returns undefined after revoke.
revokeObjectURL(123) and resolveObjectURL(123) remain lenient.
Scope / non-goals
This is scoped to Blob object URL validation and identifier shape. It does not require changing Blob/File constructor coercion or object URL lifetime semantics beyond existing revoke behavior.
Summary
Node's Blob object URL surface rejects non-Blob inputs to
URL.createObjectURL()and returnsblob:nodedata:<uuid>identifiers for valid Blob values. Perry's object URL helper currently maps invalid handles to an empty string and generates monotonic 32-hex identifiers without UUID separators.Node.js behavior
Observed with Node v25.9.0:
URL.revokeObjectURL(nonString)andresolveObjectURL(nonStringOrUnknown)are lenient and returnundefined, butcreateObjectURLvalidates that the object is a Blob.Current Perry behavior
crates/perry-stdlib/src/fetch_blob.rsimplements the object URL registry:js_url_create_object_url(blob_handle)callshandle_id(blob_handle)and returns an empty string when the id is0; it does not throw for strings, objects, or other non-Blob values.format!("blob:nodedata:{:032x}", n), so generated ids are monotonic fixed-width hex strings rather than Node's UUID-shapedblob:nodedata:<uuid>strings.resolveObjectURL/revokeObjectURLleniency broadly matches Node for bad or unknown URLs, so the validation gap is specific to creation.Suggested test surface
URL.createObjectURL("x")andURL.createObjectURL({})throwTypeErrorwith codeERR_INVALID_ARG_TYPE.URL.createObjectURL(new Blob(["hi"]))returns a string matching^blob:nodedata:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$.resolveObjectURL(id)returns a Blob with the original size/type, and returnsundefinedafter revoke.revokeObjectURL(123)andresolveObjectURL(123)remain lenient.Scope / non-goals
This is scoped to Blob object URL validation and identifier shape. It does not require changing Blob/File constructor coercion or object URL lifetime semantics beyond existing revoke behavior.