mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
208 lines
8.5 KiB
Zig
208 lines
8.5 KiB
Zig
const std = @import("std");
|
|
const bun = @import("root").bun;
|
|
const JSC = bun.JSC;
|
|
const Shimmer = JSC.Shimmer;
|
|
const JSValue = JSC.JSValue;
|
|
const JSGlobalObject = JSC.JSGlobalObject;
|
|
const ZigString = JSC.ZigString;
|
|
|
|
pub const JSObject = extern struct {
|
|
pub const shim = Shimmer("JSC", "JSObject", @This());
|
|
const cppFn = shim.cppFn;
|
|
|
|
pub fn toJS(obj: *JSObject) JSValue {
|
|
return JSValue.fromCell(obj);
|
|
}
|
|
|
|
/// Non-objects will be runtime-coerced to objects.
|
|
///
|
|
/// For cells this is `toObjectSlow`, for other types it's `toObjectSlowCase`.
|
|
pub fn fromJS(value: JSValue, globalThis: JSValue) *JSObject {
|
|
return JSValue.toObject(value, globalThis);
|
|
}
|
|
|
|
/// Returns `null` if the value is not an object.
|
|
pub fn tryFromJS(maybe_obj: JSValue, globalThis: *JSC.JSGlobalObject) ?*JSObject {
|
|
return JSValue.asObject(maybe_obj, globalThis);
|
|
}
|
|
|
|
/// Marshall a struct instance into a JSObject, copying its properties.
|
|
///
|
|
/// Each field will be encoded with `JSC.toJS`. Fields whose types have a
|
|
/// `toJS` method will have it called to encode.
|
|
///
|
|
/// This method is equivalent to `Object.create(...)` + setting properties,
|
|
/// and is only intended for creating POJOs.
|
|
pub fn create(pojo: anytype, global: *JSGlobalObject) *JSObject {
|
|
return createFromStructWithPrototype(@TypeOf(pojo), pojo, global, false);
|
|
}
|
|
/// Marshall a struct into a JSObject, copying its properties. It's
|
|
/// `__proto__` will be `null`.
|
|
///
|
|
/// Each field will be encoded with `JSC.toJS`. Fields whose types have a
|
|
/// `toJS` method will have it called to encode.
|
|
///
|
|
/// This is roughly equivalent to creating an object with
|
|
/// `Object.create(null)` and adding properties to it.
|
|
pub fn createNullProto(pojo: anytype, global: *JSGlobalObject) *JSObject {
|
|
return createFromStructWithPrototype(@TypeOf(pojo), pojo, global, true);
|
|
}
|
|
|
|
/// Marshall a struct instance into a JSObject. `pojo` is borrowed.
|
|
///
|
|
/// Each field will be encoded with `JSC.toJS`. Fields whose types have a
|
|
/// `toJS` method will have it called to encode.
|
|
///
|
|
/// This method is equivalent to `Object.create(...)` + setting properties,
|
|
/// and is only intended for creating POJOs.
|
|
///
|
|
/// The object's prototype with either be `null` or `ObjectPrototype`
|
|
/// depending on whether `null_prototype` is set. Prefer using the object
|
|
/// prototype (`null_prototype = false`) unless you have a good reason not
|
|
/// to.
|
|
fn createFromStructWithPrototype(comptime T: type, pojo: T, global: *JSGlobalObject, comptime null_prototype: bool) *JSObject {
|
|
const info: std.builtin.Type.Struct = @typeInfo(T).@"struct";
|
|
|
|
const obj = obj: {
|
|
const val = if (comptime null_prototype)
|
|
JSValue.createEmptyObjectWithNullPrototype(global)
|
|
else
|
|
JSValue.createEmptyObject(global, comptime info.fields.len);
|
|
if (bun.Environment.isDebug)
|
|
bun.assert(val.isObject());
|
|
break :obj val.uncheckedPtrCast(JSObject);
|
|
};
|
|
|
|
const cell = toJS(obj);
|
|
inline for (info.fields) |field| {
|
|
const property = @field(pojo, field.name);
|
|
cell.put(
|
|
global,
|
|
field.name,
|
|
JSC.toJS(global, @TypeOf(property), property, .temporary),
|
|
);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
pub inline fn put(obj: *JSObject, global: *JSGlobalObject, key: anytype, value: JSValue) !void {
|
|
obj.toJS().put(global, key, value);
|
|
}
|
|
|
|
pub inline fn putAllFromStruct(obj: *JSObject, global: *JSGlobalObject, properties: anytype) !void {
|
|
inline for (comptime std.meta.fieldNames(@TypeOf(properties))) |field| {
|
|
try obj.put(global, field, @field(properties, field));
|
|
}
|
|
}
|
|
|
|
/// Equivalent to `target[property]`. Calls userland getters/proxies. Can
|
|
/// throw. Null indicates the property does not exist. JavaScript undefined
|
|
/// and JavaScript null can exist as a property and is different than zig
|
|
/// `null` (property does not exist).
|
|
///
|
|
/// `property` must be either `[]const u8`. A comptime slice may defer to
|
|
/// calling `fastGet`, which use a more optimal code path. This function is
|
|
/// marked `inline` to allow Zig to determine if `fastGet` should be used
|
|
/// per invocation.
|
|
pub inline fn get(target: *JSObject, global: *JSGlobalObject, property: anytype) bun.JSError!?JSValue {
|
|
const property_slice: []const u8 = property; // must be a slice!
|
|
|
|
// This call requires `get` to be `inline`
|
|
if (bun.isComptimeKnown(property_slice)) {
|
|
if (comptime JSValue.BuiltinName.get(property_slice)) |builtin_name| {
|
|
return target.fastGetWithError(global, builtin_name);
|
|
}
|
|
}
|
|
|
|
return switch (JSC__JSObject__getIfPropertyExistsImpl(target, global, property_slice.ptr, @intCast(property_slice.len))) {
|
|
.zero => error.JSError,
|
|
.property_does_not_exist_on_object => null,
|
|
|
|
// TODO: see bug described in ObjectBindings.cpp
|
|
// since there are false positives, the better path is to make them
|
|
// negatives, as the number of places that desire throwing on
|
|
// existing undefined is extremely small, but non-zero.
|
|
.undefined => null,
|
|
else => |val| val,
|
|
};
|
|
}
|
|
extern fn JSC__JSObject__getIfPropertyExistsImpl(object: *JSObject, global: *JSGlobalObject, ptr: [*]const u8, len: u32) JSValue;
|
|
|
|
|
|
pub fn fastGetWithError(this: *JSObject, global: *JSGlobalObject, builtin_name: JSValue.BuiltinName) bun.JSError!?JSValue {
|
|
return switch (JSC__JSObject__fastGet(this, global, @intFromEnum(builtin_name))) {
|
|
.zero => error.JSError,
|
|
.undefined => null,
|
|
.property_does_not_exist_on_object => null,
|
|
else => |val| val,
|
|
};
|
|
}
|
|
extern fn JSC__JSObject__fastGet(object: *JSObject, global: *JSGlobalObject, builtin_name: u32) JSValue;
|
|
|
|
extern fn JSC__createStructure(*JSC.JSGlobalObject, *JSC.JSCell, u32, names: [*]ExternColumnIdentifier) JSC.JSValue;
|
|
|
|
pub const ExternColumnIdentifier = extern struct {
|
|
tag: u8 = 0,
|
|
value: extern union {
|
|
index: u32,
|
|
name: bun.String,
|
|
},
|
|
|
|
pub fn string(this: *ExternColumnIdentifier) ?*bun.String {
|
|
return switch (this.tag) {
|
|
2 => &this.value.name,
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(this: *ExternColumnIdentifier) void {
|
|
if (this.string()) |str| {
|
|
str.deref();
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn createStructure(global: *JSGlobalObject, owner: JSC.JSValue, length: u32, names: [*]ExternColumnIdentifier) JSValue {
|
|
JSC.markBinding(@src());
|
|
return JSC__createStructure(global, owner.asCell(), length, names);
|
|
}
|
|
|
|
const InitializeCallback = *const fn (ctx: *anyopaque, obj: *JSObject, global: *JSGlobalObject) callconv(.C) void;
|
|
extern fn JSC__JSObject__create(global_object: *JSGlobalObject, length: usize, ctx: *anyopaque, initializer: InitializeCallback) JSValue;
|
|
|
|
pub fn Initializer(comptime Ctx: type, comptime func: fn (*Ctx, obj: *JSObject, global: *JSGlobalObject) void) type {
|
|
return struct {
|
|
pub fn call(this: *anyopaque, obj: *JSObject, global: *JSGlobalObject) callconv(.C) void {
|
|
@call(bun.callmod_inline, func, .{ @as(*Ctx, @ptrCast(@alignCast(this))), obj, global });
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn createWithInitializer(comptime Ctx: type, creator: *Ctx, global: *JSGlobalObject, length: usize) JSValue {
|
|
const Type = Initializer(Ctx, Ctx.create);
|
|
return JSC__JSObject__create(global, length, creator, Type.call);
|
|
}
|
|
|
|
pub fn getIndex(this: JSValue, globalThis: *JSGlobalObject, i: u32) JSValue {
|
|
return cppFn("getIndex", .{
|
|
this,
|
|
globalThis,
|
|
i,
|
|
});
|
|
}
|
|
|
|
pub fn putRecord(this: *JSObject, global: *JSGlobalObject, key: *ZigString, values: []ZigString) void {
|
|
return cppFn("putRecord", .{ this, global, key, values.ptr, values.len });
|
|
}
|
|
|
|
extern fn Bun__JSObject__getCodePropertyVMInquiry(*JSGlobalObject, *JSObject) JSValue;
|
|
|
|
/// This will not call getters or be observable from JavaScript.
|
|
pub fn getCodePropertyVMInquiry(obj: *JSObject, global: *JSGlobalObject) ?JSValue {
|
|
const v = Bun__JSObject__getCodePropertyVMInquiry(global, obj);
|
|
if (v == .zero) return null;
|
|
return v;
|
|
}
|
|
};
|