Skip to content
Open
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
30 changes: 30 additions & 0 deletions examples/hello_world/mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,34 @@ describe("example mod", () => {
it("addon without parameters", () => {
expect(example.surprise()).toEqual("Surprise!");
});

it("copies immutable input into independently writable buffers", () => {
const arrayBuffer = example.copyArrayBuffer(false);
const arrayBufferBytes = new Uint8Array(arrayBuffer);
expect(new TextDecoder().decode(arrayBufferBytes)).toEqual("copy me");
arrayBufferBytes[0] = "C".charCodeAt(0);
expect(new TextDecoder().decode(arrayBufferBytes)).toEqual("Copy me");
expect(new TextDecoder().decode(new Uint8Array(example.copyArrayBuffer(false)))).toEqual(
"copy me"
);

const buffer = example.copyBuffer();
expect(Buffer.isBuffer(buffer)).toBe(true);
buffer[0] = "C".charCodeAt(0);
expect(buffer.toString()).toEqual("Copy me");
expect(example.copyBuffer().toString()).toEqual("copy me");
});

it("copies empty input into an empty ArrayBuffer", () => {
const arrayBuffer = example.copyArrayBuffer(true);
expect(arrayBuffer).toBeInstanceOf(ArrayBuffer);
expect(arrayBuffer.byteLength).toEqual(0);
});

it("shares mutable storage with an external Buffer", () => {
const buffer = example.externalBuffer();
expect(Buffer.isBuffer(buffer)).toBe(true);
buffer[0] = 9;
expect(example.externalBufferFirstByte()).toEqual(9);
});
});
49 changes: 49 additions & 0 deletions examples/hello_world/mod.zig
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,36 @@ fn exampleMod(env: zapi.Env, module: zapi.Value) anyerror!void {
null,
));

try module.setNamedProperty("copyArrayBuffer", try env.createFunction(
"copyArrayBuffer",
1,
copy_arraybuffer,
null,
));

try module.setNamedProperty("copyBuffer", try env.createFunction(
"copyBuffer",
0,
copy_buffer,
null,
));

try module.setNamedProperty("externalBuffer", try env.createFunction(
"externalBuffer",
0,
zapi.createCallback(0, external_buffer, .{
.returns = .external_buffer,
}),
null,
));

try module.setNamedProperty("externalBufferFirstByte", try env.createFunction(
"externalBufferFirstByte",
0,
zapi.createCallback(0, external_buffer_first_byte, .{}),
null,
));

try module.setNamedProperty("update", try env.createFunction(
"update",
1,
Expand Down Expand Up @@ -139,6 +169,25 @@ fn surprise() []const u8 {
return "Surprise!";
}

fn copy_arraybuffer(env: zapi.Env, cb: zapi.CallbackInfo(1)) !zapi.Value {
if (try cb.arg(0).getValueBool()) return try env.createArrayBufferCopy("");
return try env.createArrayBufferCopy("copy me");
}

fn copy_buffer(env: zapi.Env, _: zapi.CallbackInfo(0)) !zapi.Value {
return try env.createBufferCopy("copy me", null);
}

var external_buffer_data = [_]u8{ 1, 2, 3 };

fn external_buffer() []u8 {
return &external_buffer_data;
}

fn external_buffer_first_byte() u8 {
return external_buffer_data[0];
}

const S = struct {
a: i32,
b: i32,
Expand Down
44 changes: 40 additions & 4 deletions src/Env.zig
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ pub fn createArrayBuffer(self: Env, size: usize, out: ?*[*]u8) NapiError!Value {
};
}

/// Creates an ArrayBuffer containing a copy of `data`.
/// The returned ArrayBuffer owns its backing memory and may be mutated independently of `data`.
pub fn createArrayBufferCopy(self: Env, data: []const u8) NapiError!Value {
if (data.len == 0) return self.createArrayBuffer(0, null);

var output: [*]u8 = undefined;
const value = try self.createArrayBuffer(data.len, &output);
@memcpy(output[0..data.len], data);
return value;
}

/// https://nodejs.org/api/n-api.html#napi_create_buffer
pub fn createBuffer(self: Env, size: usize, out: ?*[*]u8) NapiError!Value {
var value: c.napi_value = undefined;
Expand All @@ -265,6 +276,8 @@ pub fn createBuffer(self: Env, size: usize, out: ?*[*]u8) NapiError!Value {
};
}

/// Creates a Buffer containing a copy of `data`.
/// The returned Buffer owns its backing memory and may be mutated independently of `data`.
/// https://nodejs.org/api/n-api.html#napi_create_buffer_copy
pub fn createBufferCopy(self: Env, data: []const u8, out: ?*[*]u8) NapiError!Value {
var value: c.napi_value = undefined;
Expand Down Expand Up @@ -301,13 +314,21 @@ pub fn createExternal(self: Env, data: [*]const u8, finalize_cb: c.napi_finalize
};
}

/// Creates an ArrayBuffer backed by caller-owned mutable memory.
/// JavaScript may mutate `data`. It must remain valid until `finalize_cb` runs, or until the
/// end of the ArrayBuffer's lifetime when no finalizer is provided.
/// https://nodejs.org/api/n-api.html#napi_create_external_arraybuffer
pub fn createExternalArrayBuffer(self: Env, data: []const u8, finalize_cb: c.napi_finalize, finalize_hint: ?*anyopaque) NapiError!Value {
pub fn createExternalArrayBuffer(
self: Env,
data: []u8,
finalize_cb: c.napi_finalize,
finalize_hint: ?*anyopaque,
) NapiError!Value {
var value: c.napi_value = undefined;
try status.check(
c.napi_create_external_arraybuffer(
self.env,
@ptrCast(@constCast(data.ptr)),
@ptrCast(data.ptr),
data.len,
finalize_cb,
finalize_hint,
Expand All @@ -320,11 +341,26 @@ pub fn createExternalArrayBuffer(self: Env, data: []const u8, finalize_cb: c.nap
};
}

/// Creates a Buffer backed by caller-owned mutable memory.
/// JavaScript may mutate `data`. It must remain valid until `finalize_cb` runs, or until the
/// end of the Buffer's lifetime when no finalizer is provided.
/// https://nodejs.org/api/n-api.html#napi_create_external_buffer
pub fn createExternalBuffer(self: Env, data: []const u8, finalize_cb: c.napi_finalize, finalize_hint: ?*anyopaque) NapiError!Value {
pub fn createExternalBuffer(
self: Env,
data: []u8,
finalize_cb: c.napi_finalize,
finalize_hint: ?*anyopaque,
) NapiError!Value {
var value: c.napi_value = undefined;
try status.check(
c.napi_create_external_buffer(self.env, data.len, @ptrCast(@constCast(data.ptr)), finalize_cb, finalize_hint, &value),
c.napi_create_external_buffer(
self.env,
data.len,
@ptrCast(data.ptr),
finalize_cb,
finalize_hint,
&value,
),
);
return Value{
.env = self.env,
Expand Down
10 changes: 8 additions & 2 deletions src/to_from_value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,17 @@ pub fn toValue(
.pointer => |p| {
const h = hint;
if (p.child == u8 and p.size == .slice) {
if (h == .external_buffer) {
if (p.is_const) {
@compileError("external buffers require a mutable byte slice");
}
const bytes: []u8 = @ptrCast(v);
return try env.createExternalBuffer(bytes, null, null);
}

const bytes: []const u8 = @ptrCast(v);
if (h == .string) {
return try env.createStringUtf8(bytes);
} else if (h == .external_buffer) {
return try env.createExternalBuffer(bytes, null, null);
} else if (h == .buffer or h == .auto) {
return try env.createBufferCopy(bytes, null);
}
Expand Down
Loading