Skip to content
Merged
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
33 changes: 33 additions & 0 deletions examples/basic/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ export declare const enum StringColor {
Blue = "Blue",
}

declare const __napiExternalBrand: unique symbol;
export interface ExternalObject<T> {
readonly [__napiExternalBrand]: T;
}

export interface ExternalPoint {
x: number;
y: number;
}

export declare function test_i32(left: number, right: number): number;
export declare function test_f32(left: number, right: number): number;
export declare function test_u32(left: number, right: number): number;
Expand Down Expand Up @@ -251,3 +261,26 @@ export declare function favorite_color(): Color;
export declare function is_primary(color: Color): boolean;
export declare function string_enum_identity(color: StringColor): StringColor;
export declare function favorite_string_color(): StringColor;
export declare function create_external(value: number): ExternalObject<number>;
export declare function create_external_with_size_hint(value: number): ExternalObject<number>;
export declare function create_external_pair(value: number): Array<ExternalObject<number>>;
export declare function create_misaligned_external(): unknown | undefined | null;
export declare function get_external(external: ExternalObject<number>): number;
export declare function get_external_size_hint(external: ExternalObject<number>): number;
export declare function mutate_external(external: ExternalObject<number>, value: number): void;
export declare function create_external_point(x: number, y: number): ExternalObject<ExternalPoint>;
export declare function get_external_point(external: ExternalObject<ExternalPoint>): ExternalPoint;
export declare function mutate_external_point(
external: ExternalObject<ExternalPoint>,
x: number,
y: number,
): void;
export declare function external_either_kind(
value: ExternalObject<number> | ExternalObject<ExternalPoint>,
): number;
export declare function external_either_value(
value: ExternalObject<number> | ExternalObject<ExternalPoint>,
): number;
export declare function reset_detached_external_deinit_count(): void;
export declare function detached_external_deinit_count(): number;
export declare function deinit_detached_external(): number;
141 changes: 141 additions & 0 deletions examples/basic/src/external.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
const std = @import("std");
const napi = @import("napi");
const c = napi.napi_sys.napi_sys;

pub const ExternalPoint = struct {
x: i32,
y: i32,
};

const ExternalEither = union(enum) {
number: napi.External(u32),
point: napi.External(ExternalPoint),
};

var detached_external_deinits = std.atomic.Value(usize).init(0);

const DetachedExternalPayload = struct {
value: u32,

pub fn deinit(self: *DetachedExternalPayload) void {
_ = self;
_ = detached_external_deinits.fetchAdd(1, .monotonic);
}
};

pub fn create_external(value: u32) !napi.External(u32) {
return try napi.External(u32).New(value);
}

pub fn create_external_with_size_hint(value: u32) !napi.External(u32) {
return try napi.External(u32).NewWithSizeHint(value, 128);
}

pub fn create_external_pair(value: u32) ![2]napi.External(u32) {
const external = try napi.External(u32).New(value);
return .{ external, external };
}

const ForeignExternalHint = struct {
allocator: std.mem.Allocator,
ptr: [*]u64,
len: usize,

fn destroy(self: *ForeignExternalHint) void {
const allocator_value = self.allocator;
allocator_value.free(self.ptr[0..self.len]);
allocator_value.destroy(self);
}
};

fn foreignExternalFinalizer(
_: c.napi_env,
_: ?*anyopaque,
hint: ?*anyopaque,
) callconv(.c) void {
if (hint) |raw_hint| {
const external_hint: *ForeignExternalHint = @ptrCast(@alignCast(raw_hint));
external_hint.destroy();
}
}

pub fn create_misaligned_external(env: napi.Env) !c.napi_value {
const allocator_value = napi.globalAllocator();
const storage = try allocator_value.alloc(u64, 2);
errdefer allocator_value.free(storage);

const hint = try allocator_value.create(ForeignExternalHint);
errdefer allocator_value.destroy(hint);
hint.* = .{
.allocator = allocator_value,
.ptr = storage.ptr,
.len = storage.len,
};

const byte_ptr: [*]u8 = @ptrCast(storage.ptr);
var raw: c.napi_value = undefined;
const status = c.napi_create_external(
env.raw,
@ptrCast(byte_ptr + 1),
foreignExternalFinalizer,
hint,
&raw,
);
if (status != c.napi_ok) {
return napi.Error.fromStatus(napi.Status.New(status));
}
return raw;
}

pub fn get_external(external: napi.External(u32)) u32 {
return external.value().*;
}

pub fn get_external_size_hint(external: napi.External(u32)) usize {
return external.sizeHint();
}

pub fn mutate_external(external: napi.External(u32), value: u32) void {
external.valueMut().* = value;
}

pub fn create_external_point(x: i32, y: i32) !napi.External(ExternalPoint) {
return try napi.External(ExternalPoint).New(.{ .x = x, .y = y });
}

pub fn get_external_point(external: napi.External(ExternalPoint)) ExternalPoint {
return external.value().*;
}

pub fn mutate_external_point(external: napi.External(ExternalPoint), x: i32, y: i32) void {
external.valueMut().* = .{ .x = x, .y = y };
}

pub fn external_either_kind(value: ExternalEither) u32 {
return switch (value) {
.number => 1,
.point => 2,
};
}

pub fn external_either_value(value: ExternalEither) i32 {
return switch (value) {
.number => |external| @intCast(external.value().*),
.point => |external| external.value().x + external.value().y,
};
}

pub fn reset_detached_external_deinit_count() void {
detached_external_deinits.store(0, .monotonic);
}

pub fn detached_external_deinit_count() usize {
return detached_external_deinits.load(.monotonic);
}

pub fn deinit_detached_external() !usize {
reset_detached_external_deinit_count();
var external = try napi.External(DetachedExternalPayload).New(.{ .value = 1 });
external.deinit();
return detached_external_deinit_count();
}
17 changes: 17 additions & 0 deletions examples/basic/src/hello.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const dataview = @import("dataview.zig");
const reference = @import("reference.zig");
const union_value = @import("union.zig");
const enum_value = @import("enum.zig");
const external = @import("external.zig");

pub const test_i32 = number.test_i32;
pub const test_f32 = number.test_f32;
Expand Down Expand Up @@ -132,6 +133,22 @@ pub const is_primary = enum_value.is_primary;
pub const string_enum_identity = enum_value.string_enum_identity;
pub const favorite_string_color = enum_value.favorite_string_color;

pub const create_external = external.create_external;
pub const create_external_with_size_hint = external.create_external_with_size_hint;
pub const create_external_pair = external.create_external_pair;
pub const create_misaligned_external = external.create_misaligned_external;
pub const get_external = external.get_external;
pub const get_external_size_hint = external.get_external_size_hint;
pub const mutate_external = external.mutate_external;
pub const create_external_point = external.create_external_point;
pub const get_external_point = external.get_external_point;
pub const mutate_external_point = external.mutate_external_point;
pub const external_either_kind = external.external_either_kind;
pub const external_either_value = external.external_either_value;
pub const reset_detached_external_deinit_count = external.reset_detached_external_deinit_count;
pub const detached_external_deinit_count = external.detached_external_deinit_count;
pub const deinit_detached_external = external.deinit_detached_external;

comptime {
napi.NODE_API_MODULE("hello", @This());
}
43 changes: 43 additions & 0 deletions node-test/napi/__tests__/values.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,46 @@ test("either", (t) => {
t.is(bindings.eitherFromOption("zig"), "zig");
t.is(bindings.eitherFromOption(null), 0);
});

test("External", (t) => {
const external = bindings.createExternal(10);
t.is(bindings.getExternal(external), 10);

bindings.mutateExternal(external, 42);
t.is(bindings.getExternal(external), 42);

const sizedExternal = bindings.createExternalWithSizeHint(7);
t.is(bindings.getExternal(sizedExternal), 7);
t.is(bindings.getExternalSizeHint(sizedExternal), 128);

const pair = bindings.createExternalPair(11);
t.is(pair.length, 2);
t.is(bindings.getExternal(pair[0]), 11);
t.is(bindings.getExternal(pair[1]), 11);
bindings.mutateExternal(pair[0], 12);
t.is(bindings.getExternal(pair[1]), 12);

const point = bindings.createExternalPoint(3, 4);
t.deepEqual(bindings.getExternalPoint(point), { x: 3, y: 4 });

bindings.mutateExternalPoint(point, 5, 6);
t.deepEqual(bindings.getExternalPoint(point), { x: 5, y: 6 });
t.is(bindings.externalEitherKind(external), 1);
t.is(bindings.externalEitherValue(external), 42);
t.is(bindings.externalEitherKind(point), 2);
t.is(bindings.externalEitherValue(point), 11);

bindings.resetDetachedExternalDeinitCount();
t.is(bindings.deinitDetachedExternal(), 1);
t.is(bindings.detachedExternalDeinitCount(), 1);

t.throws(() => bindings.getExternalPoint(external), {
message: /External value type does not match expected type/,
});
t.throws(() => bindings.getExternal({}), {
message: /Expected external value/,
});
t.throws(() => bindings.getExternal(bindings.createMisalignedExternal()), {
message: /External value was not created by zig-napi/,
});
});
15 changes: 15 additions & 0 deletions node-test/napi/src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,21 @@ pub const bigintFromI128 = values.bigintFromI128;
pub const eitherStringOrNumber = values.eitherStringOrNumber;
pub const returnEither = values.returnEither;
pub const eitherFromOption = values.eitherFromOption;
pub const createExternal = values.createExternal;
pub const createExternalWithSizeHint = values.createExternalWithSizeHint;
pub const createExternalPair = values.createExternalPair;
pub const createMisalignedExternal = values.createMisalignedExternal;
pub const getExternal = values.getExternal;
pub const getExternalSizeHint = values.getExternalSizeHint;
pub const mutateExternal = values.mutateExternal;
pub const createExternalPoint = values.createExternalPoint;
pub const getExternalPoint = values.getExternalPoint;
pub const mutateExternalPoint = values.mutateExternalPoint;
pub const externalEitherKind = values.externalEitherKind;
pub const externalEitherValue = values.externalEitherValue;
pub const resetDetachedExternalDeinitCount = values.resetDetachedExternalDeinitCount;
pub const detachedExternalDeinitCount = values.detachedExternalDeinitCount;
pub const deinitDetachedExternal = values.deinitDetachedExternal;
pub const callThreadsafeFunction = threadsafe_function.callThreadsafeFunction;

pub const validateArray = strict.validateArray;
Expand Down
Loading
Loading