diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 0afc440aadde56..e51a3535f174f0 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -3529,7 +3529,7 @@ const UnsafeObject = struct { callframe: *JSC.CallFrame, ) bun.JSError!JSC.JSValue { const args = callframe.arguments_old(2).slice(); - if (args.len < 1 or !args[0].isCell() or !args[0].jsType().isTypedArray()) { + if (args.len < 1 or !args[0].isCell() or !args[0].jsType().isTypedArrayOrArrayBuffer()) { return globalThis.throwInvalidArguments("Expected an ArrayBuffer", .{}); } diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index 4eaa5441e14413..f8f835fbb341e1 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -189,7 +189,7 @@ pub const HTMLRewriter = struct { const kind: ResponseKind = brk: { if (response_value.isString()) break :brk .string - else if (response_value.jsType().isTypedArray()) + else if (response_value.jsType().isTypedArrayOrArrayBuffer()) break :brk .array_buffer else break :brk .other; diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 4c0ba9e4f6d097..f0a0fa6cc4f1d0 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3805,7 +3805,7 @@ pub const JSValue = enum(i64) { }; } - pub fn isTypedArray(this: JSType) bool { + pub fn isTypedArrayOrArrayBuffer(this: JSType) bool { return switch (this) { .ArrayBuffer, .BigInt64Array, diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index f68fd228f902e4..bd028319ffef53 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -895,6 +895,30 @@ extern "C" napi_status napi_create_arraybuffer(napi_env env, NAPI_RETURN_SUCCESS(env); } +extern "C" napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) +{ + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, value); + NAPI_CHECK_ARG(env, result); + + auto jsValue = toJS(value); + // Despite documentation, Node.js's version of this function returns true for all kinds of + // TypedArray, not just Uint8Array + *result = jsValue.isCell() && isTypedArrayTypeIncludingDataView(jsValue.asCell()->type()); + NAPI_RETURN_SUCCESS(env); +} + +extern "C" napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) +{ + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, value); + NAPI_CHECK_ARG(env, result); + + auto jsValue = toJS(value); + *result = jsValue.isCell() && isTypedArrayType(jsValue.asCell()->type()); + NAPI_RETURN_SUCCESS(env); +} + // This is more efficient than using WTF::String::FromUTF8 // it doesn't copy the string // but it's only safe to use if we are not setting a property diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index 17d95af117663c..3452ec5e662d55 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -578,7 +578,7 @@ pub const Body = struct { }; } - if (js_type.isTypedArray()) { + if (js_type.isTypedArrayOrArrayBuffer()) { if (value.asArrayBuffer(globalThis)) |buffer| { const bytes = buffer.byteSlice(); diff --git a/src/napi/napi.zig b/src/napi/napi.zig index ff738aa8a0f687..0dce7c9bda5aa9 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -846,13 +846,9 @@ pub export fn napi_get_arraybuffer_info(env: napi_env, arraybuffer_: napi_value, len.* = slice.len; return env.ok(); } -pub export fn napi_is_typedarray(env: napi_env, value_: napi_value, result_: ?*bool) napi_status { - log("napi_is_typedarray", .{}); - const value = value_.get(); - const result = result_ orelse return env.invalidArg(); - result.* = value.jsTypeLoose().isTypedArray(); - return env.ok(); -} + +pub extern fn napi_is_typedarray(napi_env, napi_value, *bool) napi_status; + pub export fn napi_create_typedarray(env: napi_env, @"type": napi_typedarray_type, length: usize, arraybuffer_: napi_value, byte_offset: usize, result_: ?*napi_value) napi_status { log("napi_create_typedarray", .{}); const arraybuffer = arraybuffer_.get(); @@ -1240,15 +1236,7 @@ pub export fn napi_create_buffer_copy(env: napi_env, length: usize, data: [*]u8, return env.ok(); } -pub export fn napi_is_buffer(env: napi_env, value_: napi_value, result_: ?*bool) napi_status { - log("napi_is_buffer", .{}); - const result = result_ orelse { - return env.invalidArg(); - }; - const value = value_.get(); - result.* = value.isBuffer(env.toJS()); - return env.ok(); -} +extern fn napi_is_buffer(napi_env, napi_value, *bool) napi_status; pub export fn napi_get_buffer_info(env: napi_env, value_: napi_value, data: ?*[*]u8, length: ?*usize) napi_status { log("napi_get_buffer_info", .{}); const value = value_.get(); diff --git a/src/sql/postgres/postgres_types.zig b/src/sql/postgres/postgres_types.zig index 83a1e8488138cc..9a62d07bf18223 100644 --- a/src/sql/postgres/postgres_types.zig +++ b/src/sql/postgres/postgres_types.zig @@ -345,7 +345,7 @@ pub const Tag = enum(short) { return .timestamptz; } - if (tag.isTypedArray()) { + if (tag.isTypedArrayOrArrayBuffer()) { if (tag == .Int32Array) return .int4_array; diff --git a/test/napi/napi-app/main.cpp b/test/napi/napi-app/main.cpp index 5e99b118d07425..dd5aee63f963c9 100644 --- a/test/napi/napi-app/main.cpp +++ b/test/napi/napi-app/main.cpp @@ -1131,6 +1131,22 @@ static napi_value test_extended_error_messages(const Napi::CallbackInfo &info) { return ok(env); } +static napi_value test_is_buffer(const Napi::CallbackInfo &info) { + bool result; + napi_env env = info.Env(); + NODE_API_CALL(info.Env(), napi_is_buffer(env, info[1], &result)); + printf("napi_is_buffer -> %s\n", result ? "true" : "false"); + return ok(env); +} + +static napi_value test_is_typedarray(const Napi::CallbackInfo &info) { + bool result; + napi_env env = info.Env(); + NODE_API_CALL(info.Env(), napi_is_typedarray(env, info[1], &result)); + printf("napi_is_typedarray -> %s\n", result ? "true" : "false"); + return ok(env); +} + Napi::Value RunCallback(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); // this function is invoked without the GC callback @@ -1204,6 +1220,9 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) { Napi::Function::New(env, create_weird_bigints)); exports.Set("test_extended_error_messages", Napi::Function::New(env, test_extended_error_messages)); + exports.Set("test_is_buffer", Napi::Function::New(env, test_is_buffer)); + exports.Set("test_is_typedarray", + Napi::Function::New(env, test_is_typedarray)); napitests::register_wrap_tests(env, exports); diff --git a/test/napi/napi.test.ts b/test/napi/napi.test.ts index 8d8f1bc62e15c2..d16b7fd9f9e419 100644 --- a/test/napi/napi.test.ts +++ b/test/napi/napi.test.ts @@ -387,6 +387,27 @@ describe("napi", () => { }); }); + describe.each(["buffer", "typedarray"])("napi_is_%s", kind => { + const tests: Array<[string, boolean]> = [ + ["new Uint8Array()", true], + ["new BigUint64Array()", true], + ["new ArrayBuffer()", false], + ["Buffer.alloc(0)", true], + ["new DataView(new ArrayBuffer())", kind == "buffer"], + ["new (class Foo extends Uint8Array {})()", true], + ["false", false], + ["[1, 2, 3]", false], + ["'hello'", false], + ]; + it("returns consistent values with node.js", () => { + for (const [value, expected] of tests) { + // main.js does eval then spread so to pass a single value we need to wrap in an array + const output = checkSameOutput(`test_is_${kind}`, "[" + value + "]"); + expect(output).toBe(`napi_is_${kind} -> ${expected.toString()}`); + } + }); + }); + it.each([ ["nullptr", { number: 123 }], ["null", null],