aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--packages/bun-types/globals.d.ts15
-rw-r--r--src/bun.js/api/bun.zig142
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h3
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h3
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h6
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h7
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp203
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.h50
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp106
-rw-r--r--src/bun.js/bindings/exports.zig14
-rw-r--r--src/bun.js/bindings/generated_classes.zig69
-rw-r--r--src/bun.js/bindings/generated_classes_list.zig1
-rw-r--r--src/bun.js/bindings/headers-cpp.h2
-rw-r--r--src/bun.js/node/node.classes.ts27
-rw-r--r--src/bun.js/node_timers.exports.js105
-rw-r--r--test/bun.js/inspect.test.js9
-rw-r--r--test/bun.js/setInterval.test.js3
-rw-r--r--test/bun.js/setTimeout.test.js58
18 files changed, 616 insertions, 207 deletions
diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts
index 8848f59e6..69c100c05 100644
--- a/packages/bun-types/globals.d.ts
+++ b/packages/bun-types/globals.d.ts
@@ -1368,6 +1368,15 @@ declare function queueMicrotask(callback: (...args: any[]) => void): void;
* @param error Error or string
*/
declare function reportError(error: any): void;
+
+interface Timer {
+ ref(): void;
+ unref(): void;
+ hasRef(): boolean;
+
+ [Symbol.toPrimitive](): number;
+}
+
/**
* Run a function immediately after main event loop is vacant
* @param handler function to call
@@ -1375,7 +1384,7 @@ declare function reportError(error: any): void;
declare function setImmediate(
handler: TimerHandler,
...arguments: any[]
-): number;
+): Timer;
/**
* Run a function every `interval` milliseconds
* @param handler function to call
@@ -1385,7 +1394,7 @@ declare function setInterval(
handler: TimerHandler,
interval?: number,
...arguments: any[]
-): number;
+): Timer;
/**
* Run a function after `timeout` (milliseconds)
* @param handler function to call
@@ -1395,7 +1404,7 @@ declare function setTimeout(
handler: TimerHandler,
timeout?: number,
...arguments: any[]
-): number;
+): Timer;
declare function addEventListener<K extends keyof EventMap>(
type: K,
listener: (this: object, ev: EventMap[K]) => any,
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index 4dcbad374..78927f85a 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -2777,12 +2777,19 @@ pub const Timer = struct {
warned: bool = false,
// We split up the map here to avoid storing an extra "repeat" boolean
-
- /// Used by setTimeout()
- timeout_map: TimeoutMap = TimeoutMap{},
-
- /// Used by setInterval()
- interval_map: TimeoutMap = TimeoutMap{},
+ maps: struct {
+ setTimeout: TimeoutMap = .{},
+ setInterval: TimeoutMap = .{},
+ setImmediate: TimeoutMap = .{},
+
+ pub inline fn get(this: *@This(), kind: Timeout.Kind) *TimeoutMap {
+ return switch (kind) {
+ .setTimeout => &this.setTimeout,
+ .setInterval => &this.setInterval,
+ .setImmediate => &this.setImmediate,
+ };
+ }
+ } = .{},
/// TimeoutMap is map of i32 to nullable Timeout structs
/// i32 is exposed to JavaScript and can be used with clearTimeout, clearInterval, etc.
@@ -2812,7 +2819,7 @@ pub const Timer = struct {
globalThis: *JSC.JSGlobalObject,
callback: JSC.Strong = .{},
arguments: JSC.Strong = .{},
- repeat: bool = false,
+ kind: Timeout.Kind = .setTimeout,
pub const Task = JSC.AnyTask.New(CallbackJob, perform);
@@ -2849,12 +2856,13 @@ pub const Timer = struct {
pub fn perform(this: *CallbackJob) void {
var globalThis = this.globalThis;
var vm = globalThis.bunVM();
- var map: *TimeoutMap = if (this.repeat) &vm.timer.interval_map else &vm.timer.timeout_map;
+ const kind = this.kind;
+ var map: *TimeoutMap = vm.timer.maps.get(kind);
// This doesn't deinit the timer
// Timers are deinit'd separately
// We do need to handle when the timer is cancelled after the job has been enqueued
- if (!this.repeat) {
+ if (kind != .setInterval) {
if (map.fetchSwapRemove(this.id) == null) {
// if the timeout was cancelled, don't run the callback
this.deinit();
@@ -2934,6 +2942,47 @@ pub const Timer = struct {
}
};
+ pub const TimerObject = struct {
+ id: i32 = -1,
+ kind: Timeout.Kind = .setTimeout,
+ ref_count: u16 = 1,
+
+ pub usingnamespace JSC.Codegen.JSTimeout;
+
+ pub fn doRef(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
+ if (this.ref_count > 0)
+ this.ref_count +|= 1;
+
+ return JSValue.jsUndefined();
+ }
+
+ pub fn doUnref(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
+ this.ref_count -|= 1;
+ if (this.ref_count == 0) {
+ switch (this.kind) {
+ .setTimeout, .setImmediate => {
+ _ = clearTimeout(globalObject, JSValue.jsNumber(this.id));
+ },
+ .setInterval => {
+ _ = clearInterval(globalObject, JSValue.jsNumber(this.id));
+ },
+ }
+ }
+
+ return JSValue.jsUndefined();
+ }
+ pub fn hasRef(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
+ return JSValue.jsBoolean(this.ref_count > 0 and globalObject.bunVM().timer.maps.get(this.kind).contains(this.id));
+ }
+ pub fn toPrimitive(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
+ return JSValue.jsNumber(this.id);
+ }
+
+ pub fn finalize(this: *TimerObject) callconv(.C) void {
+ bun.default_allocator.destroy(this);
+ }
+ };
+
pub const Timeout = struct {
callback: JSC.Strong = .{},
globalThis: *JSC.JSGlobalObject,
@@ -2941,11 +2990,21 @@ pub const Timer = struct {
poll_ref: JSC.PollRef = JSC.PollRef.init(),
arguments: JSC.Strong = .{},
+ pub const Kind = enum(u32) {
+ setTimeout,
+ setInterval,
+ setImmediate,
+ };
+
// this is sized to be the same as one pointer
pub const ID = extern struct {
id: i32,
- repeat: u32 = 0,
+ kind: Kind = Kind.setTimeout,
+
+ pub fn repeats(this: ID) bool {
+ return this.kind == .setInterval;
+ }
};
pub fn run(timer: *uws.Timer) callconv(.C) void {
@@ -2955,9 +3014,9 @@ pub const Timer = struct {
// to handle the timeout being cancelled after already enqueued
var vm = JSC.VirtualMachine.get();
- const repeats = timer_id.repeat > 0;
+ const repeats = timer_id.repeats();
- var map = if (repeats) &vm.timer.interval_map else &vm.timer.timeout_map;
+ var map = vm.timer.maps.get(timer_id.kind);
var this_: ?Timeout = map.get(
timer_id.id,
@@ -3000,7 +3059,7 @@ pub const Timer = struct {
this.arguments,
.globalThis = globalThis,
.id = timer_id.id,
- .repeat = timer_id.repeat > 0,
+ .kind = timer_id.kind,
};
// This allows us to:
@@ -3054,19 +3113,18 @@ pub const Timer = struct {
if (repeat) @as(i32, 1) else 0,
);
- var map = if (repeat)
- &vm.timer.interval_map
- else
- &vm.timer.timeout_map;
+ const kind: Timeout.Kind = if (repeat) .setInterval else .setTimeout;
+
+ var map = vm.timer.maps.get(kind);
// setImmediate(foo)
// setTimeout(foo, 0)
- if (interval == 0) {
+ if (kind == .setTimeout and interval == 0) {
var cb: CallbackJob = .{
.callback = JSC.Strong.create(callback, globalThis),
.globalThis = globalThis,
.id = id,
- .repeat = false,
+ .kind = kind,
};
if (arguments_array_or_zero != .zero) {
@@ -3093,7 +3151,7 @@ pub const Timer = struct {
vm.uws_event_loop.?,
Timeout.ID{
.id = id,
- .repeat = @as(u32, @boolToInt(repeat)),
+ .kind = kind,
},
),
};
@@ -3108,11 +3166,11 @@ pub const Timer = struct {
timeout.timer.set(
Timeout.ID{
.id = id,
- .repeat = if (repeat) 1 else 0,
+ .kind = kind,
},
Timeout.run,
interval,
- @as(i32, @boolToInt(repeat)) * interval,
+ @as(i32, @boolToInt(kind == .setInterval)) * interval,
);
}
@@ -3129,7 +3187,13 @@ pub const Timer = struct {
Timer.set(id, globalThis, callback, countdown, arguments, false) catch
return JSValue.jsUndefined();
- return JSValue.jsNumberWithType(i32, id);
+ var timer = globalThis.allocator().create(TimerObject) catch unreachable;
+ timer.* = .{
+ .id = id,
+ .kind = .setTimeout,
+ };
+
+ return timer.toJS(globalThis);
}
pub fn setInterval(
globalThis: *JSGlobalObject,
@@ -3144,21 +3208,37 @@ pub const Timer = struct {
Timer.set(id, globalThis, callback, countdown, arguments, true) catch
return JSValue.jsUndefined();
- return JSValue.jsNumberWithType(i32, id);
+ var timer = globalThis.allocator().create(TimerObject) catch unreachable;
+ timer.* = .{
+ .id = id,
+ .kind = .setInterval,
+ };
+
+ return timer.toJS(globalThis);
}
- pub fn clearTimer(timer_id: JSValue, globalThis: *JSGlobalObject, repeats: bool) void {
+ pub fn clearTimer(timer_id_value: JSValue, globalThis: *JSGlobalObject, repeats: bool) void {
JSC.markBinding(@src());
- var map = if (repeats) &VirtualMachine.get().timer.interval_map else &VirtualMachine.get().timer.timeout_map;
- if (!timer_id.isAnyInt()) {
- return;
- }
+ const kind: Timeout.Kind = if (repeats) .setInterval else .setTimeout;
+
+ var map = globalThis.bunVM().timer.maps.get(kind);
const id: Timeout.ID = .{
- .id = timer_id.coerce(i32, globalThis),
- .repeat = @as(u32, @boolToInt(repeats)),
+ .id = brk: {
+ if (timer_id_value.isAnyInt()) {
+ break :brk timer_id_value.coerce(i32, globalThis);
+ }
+
+ if (TimerObject.fromJS(timer_id_value)) |timer_obj| {
+ break :brk timer_obj.id;
+ }
+
+ return;
+ },
+ .kind = kind,
};
+
var timer = map.fetchSwapRemove(id.id) orelse return;
if (timer.value == null) {
// this timer was scheduled to run but was cancelled before it was run
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
index 8de2f11f8..d642ae40f 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
@@ -22,5 +22,6 @@ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForStatsConstructor;std::
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTCPSocket;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTLSSocket;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoder;
-std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoderConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTranspiler;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoderConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTimeout;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTranspiler;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTranspilerConstructor; \ No newline at end of file
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
index 7084bd710..93cb9bf5e 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
@@ -22,5 +22,6 @@ std::unique_ptr<IsoSubspace> m_subspaceForStatsConstructor;std::unique_ptr<IsoSu
std::unique_ptr<IsoSubspace> m_subspaceForTCPSocket;
std::unique_ptr<IsoSubspace> m_subspaceForTLSSocket;
std::unique_ptr<IsoSubspace> m_subspaceForTextDecoder;
-std::unique_ptr<IsoSubspace> m_subspaceForTextDecoderConstructor;std::unique_ptr<IsoSubspace> m_subspaceForTranspiler;
+std::unique_ptr<IsoSubspace> m_subspaceForTextDecoderConstructor;std::unique_ptr<IsoSubspace> m_subspaceForTimeout;
+std::unique_ptr<IsoSubspace> m_subspaceForTranspiler;
std::unique_ptr<IsoSubspace> m_subspaceForTranspilerConstructor; \ No newline at end of file
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
index f03a3faa3..ab75a9949 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
@@ -142,6 +142,12 @@ JSC::Structure* JSTextDecoderStructure() { return m_JSTextDecoder.getInitialized
JSC::LazyClassStructure m_JSTextDecoder;
bool hasJSTextDecoderSetterValue { false };
mutable JSC::WriteBarrier<JSC::Unknown> m_JSTextDecoderSetterValue;
+JSC::Structure* JSTimeoutStructure() { return m_JSTimeout.getInitializedOnMainThread(this); }
+ JSC::JSObject* JSTimeoutConstructor() { return m_JSTimeout.constructorInitializedOnMainThread(this); }
+ JSC::JSValue JSTimeoutPrototype() { return m_JSTimeout.prototypeInitializedOnMainThread(this); }
+ JSC::LazyClassStructure m_JSTimeout;
+ bool hasJSTimeoutSetterValue { false };
+ mutable JSC::WriteBarrier<JSC::Unknown> m_JSTimeoutSetterValue;
JSC::Structure* JSTranspilerStructure() { return m_JSTranspiler.getInitializedOnMainThread(this); }
JSC::JSObject* JSTranspilerConstructor() { return m_JSTranspiler.constructorInitializedOnMainThread(this); }
JSC::JSValue JSTranspilerPrototype() { return m_JSTranspiler.prototypeInitializedOnMainThread(this); }
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
index 388b1f467..aed941a2e 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
@@ -143,6 +143,12 @@ void GlobalObject::initGeneratedLazyClasses() {
init.setStructure(WebCore::JSTextDecoder::createStructure(init.vm, init.global, init.prototype));
init.setConstructor(WebCore::JSTextDecoder::createConstructor(init.vm, init.global, init.prototype));
});
+ m_JSTimeout.initLater(
+ [](LazyClassStructure::Initializer& init) {
+ init.setPrototype(WebCore::JSTimeout::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
+ init.setStructure(WebCore::JSTimeout::createStructure(init.vm, init.global, init.prototype));
+
+ });
m_JSTranspiler.initLater(
[](LazyClassStructure::Initializer& init) {
init.setPrototype(WebCore::JSTranspiler::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
@@ -177,5 +183,6 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor&
thisObject->m_JSTCPSocket.visit(visitor); visitor.append(thisObject->m_JSTCPSocketSetterValue);
thisObject->m_JSTLSSocket.visit(visitor); visitor.append(thisObject->m_JSTLSSocketSetterValue);
thisObject->m_JSTextDecoder.visit(visitor); visitor.append(thisObject->m_JSTextDecoderSetterValue);
+ thisObject->m_JSTimeout.visit(visitor); visitor.append(thisObject->m_JSTimeoutSetterValue);
thisObject->m_JSTranspiler.visit(visitor); visitor.append(thisObject->m_JSTranspilerSetterValue);
} \ No newline at end of file
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp
index c1e7f674f..805c4c929 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.cpp
+++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp
@@ -12628,6 +12628,209 @@ void JSTextDecoder::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor)
}
DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSTextDecoder);
+class JSTimeoutPrototype final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+
+ static JSTimeoutPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ JSTimeoutPrototype* ptr = new (NotNull, JSC::allocateCell<JSTimeoutPrototype>(vm)) JSTimeoutPrototype(vm, globalObject, structure);
+ ptr->finishCreation(vm, globalObject);
+ return ptr;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ return &vm.plainObjectSpace();
+ }
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+ }
+
+private:
+ JSTimeoutPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
+};
+
+extern "C" void TimeoutClass__finalize(void*);
+
+extern "C" EncodedJSValue TimeoutPrototype__toPrimitive(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__toPrimitiveCallback);
+
+extern "C" EncodedJSValue TimeoutPrototype__hasRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__hasRefCallback);
+
+extern "C" EncodedJSValue TimeoutPrototype__doRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__refCallback);
+
+extern "C" EncodedJSValue TimeoutPrototype__doUnref(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__unrefCallback);
+
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTimeoutPrototype, JSTimeoutPrototype::Base);
+
+static const HashTableValue JSTimeoutPrototypeTableValues[] = {
+ { "hasRef"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__hasRefCallback, 0 } },
+ { "ref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__refCallback, 0 } },
+ { "unref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__unrefCallback, 0 } }
+};
+
+const ClassInfo JSTimeoutPrototype::s_info = { "Timeout"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTimeoutPrototype) };
+
+JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__toPrimitiveCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ return TimeoutPrototype__toPrimitive(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__hasRefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ return TimeoutPrototype__hasRef(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__refCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ return TimeoutPrototype__doRef(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__unrefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ return TimeoutPrototype__doUnref(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+void JSTimeoutPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+ reifyStaticProperties(vm, JSTimeout::info(), JSTimeoutPrototypeTableValues, *this);
+ this->putDirect(vm, vm.propertyNames->toPrimitiveSymbol, JSFunction::create(vm, globalObject, 1, String("toPrimitive"_s), TimeoutPrototype__toPrimitiveCallback, ImplementationVisibility::Public), PropertyAttribute::Function | PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum | 0);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+JSTimeout::~JSTimeout()
+{
+ if (m_ctx) {
+ TimeoutClass__finalize(m_ctx);
+ }
+}
+void JSTimeout::destroy(JSCell* cell)
+{
+ static_cast<JSTimeout*>(cell)->JSTimeout::~JSTimeout();
+}
+
+const ClassInfo JSTimeout::s_info = { "Timeout"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTimeout) };
+
+void JSTimeout::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+}
+
+JSTimeout* JSTimeout::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx)
+{
+ JSTimeout* ptr = new (NotNull, JSC::allocateCell<JSTimeout>(vm)) JSTimeout(vm, structure, ctx);
+ ptr->finishCreation(vm);
+ return ptr;
+}
+
+extern "C" void* Timeout__fromJS(JSC::EncodedJSValue value)
+{
+ JSC::JSValue decodedValue = JSC::JSValue::decode(value);
+ if (decodedValue.isEmpty() || !decodedValue.isCell())
+ return nullptr;
+
+ JSC::JSCell* cell = decodedValue.asCell();
+ JSTimeout* object = JSC::jsDynamicCast<JSTimeout*>(cell);
+
+ if (!object)
+ return nullptr;
+
+ return object->wrapped();
+}
+
+extern "C" bool Timeout__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr)
+{
+ JSTimeout* object = JSC::jsDynamicCast<JSTimeout*>(JSValue::decode(value));
+ if (!object)
+ return false;
+
+ object->m_ctx = ptr;
+ return true;
+}
+
+extern "C" const size_t Timeout__ptrOffset = JSTimeout::offsetOfWrapped();
+
+void JSTimeout::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<JSTimeout*>(cell);
+ if (void* wrapped = thisObject->wrapped()) {
+ // if (thisObject->scriptExecutionContext())
+ // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+ }
+ Base::analyzeHeap(cell, analyzer);
+}
+
+JSObject* JSTimeout::createPrototype(VM& vm, JSDOMGlobalObject* globalObject)
+{
+ return JSTimeoutPrototype::create(vm, globalObject, JSTimeoutPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
+}
+
+extern "C" EncodedJSValue Timeout__create(Zig::GlobalObject* globalObject, void* ptr)
+{
+ auto& vm = globalObject->vm();
+ JSC::Structure* structure = globalObject->JSTimeoutStructure();
+ JSTimeout* instance = JSTimeout::create(vm, globalObject, structure, ptr);
+
+ return JSValue::encode(instance);
+}
class JSTranspilerPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h
index d65a687e0..3cd3e8a1a 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses.h
@@ -1404,6 +1404,56 @@ public:
mutable JSC::WriteBarrier<JSC::Unknown> m_encoding;
};
+class JSTimeout final : public JSC::JSDestructibleObject {
+public:
+ using Base = JSC::JSDestructibleObject;
+ static JSTimeout* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx);
+
+ DECLARE_EXPORT_INFO;
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<JSTimeout, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForTimeout.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForTimeout = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForTimeout.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForTimeout = std::forward<decltype(space)>(space); });
+ }
+
+ static void destroy(JSC::JSCell*);
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info());
+ }
+
+ static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject);
+ ;
+
+ ~JSTimeout();
+
+ void* wrapped() const { return m_ctx; }
+
+ void detach()
+ {
+ m_ctx = nullptr;
+ }
+
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+ static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSTimeout, m_ctx); }
+
+ void* m_ctx { nullptr };
+
+ JSTimeout(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
+ : Base(vm, structure)
+ {
+ m_ctx = sinkPtr;
+ }
+
+ void finishCreation(JSC::VM&);
+};
+
class JSTranspiler final : public JSC::JSDestructibleObject {
public:
using Base = JSC::JSDestructibleObject;
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index feabdd61d..dced9dbdd 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -751,6 +751,45 @@ JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask,
return JSC::JSValue::encode(JSC::jsUndefined());
}
+JSC_DEFINE_HOST_FUNCTION(functionBunSleepThenCallback,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+
+ RELEASE_ASSERT(callFrame->argumentCount() == 1);
+ JSPromise* promise = jsCast<JSC::JSPromise*>(callFrame->argument(0));
+ RELEASE_ASSERT(promise);
+
+ promise->resolve(globalObject, JSC::jsUndefined());
+
+ return JSC::JSValue::encode(promise);
+}
+
+JSC_DEFINE_HOST_FUNCTION(functionBunSleep,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+
+ JSC::JSValue millisecondsValue = callFrame->argument(0);
+
+ if (millisecondsValue.inherits<JSC::DateInstance>()) {
+ auto now = MonotonicTime::now();
+ auto milliseconds = jsCast<JSC::DateInstance*>(millisecondsValue)->internalNumber() - now.approximateWallTime().secondsSinceEpoch().milliseconds();
+ millisecondsValue = JSC::jsNumber(milliseconds > 0 ? milliseconds : 0);
+ }
+
+ if (!millisecondsValue.isNumber()) {
+ auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ JSC::throwTypeError(globalObject, scope, "sleep expects a number (milliseconds)"_s);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ Zig::GlobalObject* global = JSC::jsCast<Zig::GlobalObject*>(globalObject);
+ JSC::JSPromise* promise = JSC::JSPromise::create(vm, globalObject->promiseStructure());
+ Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(global->bunSleepThenCallback()), JSC::JSValue::encode(millisecondsValue), JSValue::encode(promise));
+ return JSC::JSValue::encode(promise);
+}
+
JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
@@ -778,7 +817,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
JSC::JSArray* argumentsArray = JSC::JSArray::tryCreateUninitializedRestricted(
initializationScope, nullptr,
globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
- argumentCount);
+ argumentCount - 2);
if (UNLIKELY(!argumentsArray)) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
@@ -786,8 +825,8 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
return JSC::JSValue::encode(JSC::JSValue {});
}
- for (size_t i = 0; i < argumentCount; i++) {
- argumentsArray->putDirectIndex(globalObject, i, callFrame->uncheckedArgument(i + 2));
+ for (size_t i = 2; i < argumentCount; i++) {
+ argumentsArray->putDirectIndex(globalObject, i - 2, callFrame->uncheckedArgument(i));
}
arguments = JSValue(argumentsArray);
}
@@ -802,46 +841,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments));
}
-JSC_DEFINE_HOST_FUNCTION(functionBunSleepThenCallback,
- (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
-{
- JSC::VM& vm = globalObject->vm();
-
- RELEASE_ASSERT(callFrame->argumentCount() == 1);
- JSPromise* promise = jsCast<JSC::JSPromise*>(callFrame->argument(0));
- RELEASE_ASSERT(promise);
-
- promise->resolve(globalObject, JSC::jsUndefined());
-
- return JSC::JSValue::encode(promise);
-}
-
-JSC_DEFINE_HOST_FUNCTION(functionBunSleep,
- (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
-{
- JSC::VM& vm = globalObject->vm();
-
- JSC::JSValue millisecondsValue = callFrame->argument(0);
-
- if (millisecondsValue.inherits<JSC::DateInstance>()) {
- auto now = MonotonicTime::now();
- auto milliseconds = jsCast<JSC::DateInstance*>(millisecondsValue)->internalNumber() - now.approximateWallTime().secondsSinceEpoch().milliseconds();
- millisecondsValue = JSC::jsNumber(milliseconds > 0 ? milliseconds : 0);
- }
-
- if (!millisecondsValue.isNumber()) {
- auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
- JSC::throwTypeError(globalObject, scope, "sleep expects a number (milliseconds)"_s);
- return JSC::JSValue::encode(JSC::JSValue {});
- }
-
- Zig::GlobalObject* global = JSC::jsCast<Zig::GlobalObject*>(globalObject);
- JSC::JSPromise* promise = JSC::JSPromise::create(vm, globalObject->promiseStructure());
- Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(global->bunSleepThenCallback()), JSC::JSValue::encode(millisecondsValue), JSValue::encode(promise));
- return JSC::JSValue::encode(promise);
-}
-
-static JSC_DEFINE_HOST_FUNCTION(functionSetInterval,
+JSC_DEFINE_HOST_FUNCTION(functionSetInterval,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
@@ -868,7 +868,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetInterval,
JSC::JSArray* argumentsArray = JSC::JSArray::tryCreateUninitializedRestricted(
initializationScope, nullptr,
globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
- argumentCount);
+ argumentCount - 2);
if (UNLIKELY(!argumentsArray)) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
@@ -876,8 +876,8 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetInterval,
return JSC::JSValue::encode(JSC::JSValue {});
}
- for (size_t i = 0; i < argumentCount; i++) {
- argumentsArray->putDirectIndex(globalObject, i, callFrame->uncheckedArgument(i + 2));
+ for (size_t i = 2; i < argumentCount; i++) {
+ argumentsArray->putDirectIndex(globalObject, i - 2, callFrame->uncheckedArgument(i));
}
arguments = JSValue(argumentsArray);
}
@@ -892,7 +892,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetInterval,
return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments));
}
-static JSC_DEFINE_HOST_FUNCTION(functionClearInterval,
+JSC_DEFINE_HOST_FUNCTION(functionClearInterval,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
@@ -908,7 +908,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionClearInterval,
return Bun__Timer__clearInterval(globalObject, JSC::JSValue::encode(num));
}
-static JSC_DEFINE_HOST_FUNCTION(functionClearTimeout,
+JSC_DEFINE_HOST_FUNCTION(functionClearTimeout,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
@@ -924,7 +924,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionClearTimeout,
return Bun__Timer__clearTimeout(globalObject, JSC::JSValue::encode(num));
}
-static JSC_DEFINE_HOST_FUNCTION(functionBTOA,
+JSC_DEFINE_HOST_FUNCTION(functionBTOA,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
@@ -2954,12 +2954,12 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetImmediate,
JSC::JSValue arguments = {};
size_t argumentCount = callFrame->argumentCount() - 1;
- if (argumentCount > 0) {
+ if (argumentCount > 1) {
JSC::ObjectInitializationScope initializationScope(globalObject->vm());
JSC::JSArray* argumentsArray = JSC::JSArray::tryCreateUninitializedRestricted(
initializationScope, nullptr,
globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
- argumentCount);
+ argumentCount - 1);
if (UNLIKELY(!argumentsArray)) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
@@ -2967,8 +2967,8 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetImmediate,
return JSC::JSValue::encode(JSC::JSValue {});
}
- for (size_t i = 0; i < argumentCount; i++) {
- argumentsArray->putDirectIndex(globalObject, i, callFrame->uncheckedArgument(i + 1));
+ for (size_t i = 1; i < argumentCount; i++) {
+ argumentsArray->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i));
}
arguments = JSValue(argumentsArray);
}
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig
index 9e1f03592..15ee53176 100644
--- a/src/bun.js/bindings/exports.zig
+++ b/src/bun.js/bindings/exports.zig
@@ -2113,6 +2113,20 @@ pub const ZigConsoleClient = struct {
.Object,
enable_ansi_colors,
);
+ } else if (value.as(JSC.API.Bun.Timer.TimerObject)) |timer| {
+ this.addForNewLine("Timeout(# ) ".len + bun.fmt.fastDigitCount(@intCast(u64, @max(timer.id, 0))));
+ if (timer.kind == .setInterval) {
+ this.addForNewLine("repeats ".len + bun.fmt.fastDigitCount(@intCast(u64, @max(timer.id, 0))));
+ writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>, repeats)<r>", enable_ansi_colors), .{
+ timer.id,
+ });
+ } else {
+ writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>)<r>", enable_ansi_colors), .{
+ timer.id,
+ });
+ }
+
+ return;
} else if (jsType != .DOMWrapper) {
if (CAPI.JSObjectGetPrivate(value.asRef())) |private_data_ptr| {
const priv_data = JSPrivateDataPtr.from(private_data_ptr);
diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig
index 599d05d7e..dec2d6543 100644
--- a/src/bun.js/bindings/generated_classes.zig
+++ b/src/bun.js/bindings/generated_classes.zig
@@ -3585,6 +3585,74 @@ pub const JSTextDecoder = struct {
}
}
};
+pub const JSTimeout = struct {
+ const Timeout = Classes.Timeout;
+ const GetterType = fn (*Timeout, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const GetterTypeWithThisValue = fn (*Timeout, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const SetterType = fn (*Timeout, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const SetterTypeWithThisValue = fn (*Timeout, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const CallbackType = fn (*Timeout, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue;
+
+ /// Return the pointer to the wrapped object.
+ /// If the object does not match the type, return null.
+ pub fn fromJS(value: JSC.JSValue) ?*Timeout {
+ JSC.markBinding(@src());
+ return Timeout__fromJS(value);
+ }
+
+ /// Create a new instance of Timeout
+ pub fn toJS(this: *Timeout, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding(@src());
+ if (comptime Environment.allow_assert) {
+ const value__ = Timeout__create(globalObject, this);
+ std.debug.assert(value__.as(Timeout).? == this); // If this fails, likely a C ABI issue.
+ return value__;
+ } else {
+ return Timeout__create(globalObject, this);
+ }
+ }
+
+ /// Modify the internal ptr to point to a new instance of Timeout.
+ pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*Timeout) bool {
+ JSC.markBinding(@src());
+ return Timeout__dangerouslySetPtr(value, ptr);
+ }
+
+ /// Detach the ptr from the thisValue
+ pub fn detachPtr(_: *Timeout, value: JSC.JSValue) void {
+ JSC.markBinding(@src());
+ std.debug.assert(Timeout__dangerouslySetPtr(value, null));
+ }
+
+ extern fn Timeout__fromJS(JSC.JSValue) ?*Timeout;
+ extern fn Timeout__getConstructor(*JSC.JSGlobalObject) JSC.JSValue;
+
+ extern fn Timeout__create(globalObject: *JSC.JSGlobalObject, ptr: ?*Timeout) JSC.JSValue;
+
+ extern fn Timeout__dangerouslySetPtr(JSC.JSValue, ?*Timeout) bool;
+
+ comptime {
+ if (@TypeOf(Timeout.finalize) != (fn (*Timeout) callconv(.C) void)) {
+ @compileLog("Timeout.finalize is not a finalizer");
+ }
+
+ if (@TypeOf(Timeout.toPrimitive) != CallbackType)
+ @compileLog("Expected Timeout.toPrimitive to be a callback but received " ++ @typeName(@TypeOf(Timeout.toPrimitive)));
+ if (@TypeOf(Timeout.hasRef) != CallbackType)
+ @compileLog("Expected Timeout.hasRef to be a callback but received " ++ @typeName(@TypeOf(Timeout.hasRef)));
+ if (@TypeOf(Timeout.doRef) != CallbackType)
+ @compileLog("Expected Timeout.doRef to be a callback but received " ++ @typeName(@TypeOf(Timeout.doRef)));
+ if (@TypeOf(Timeout.doUnref) != CallbackType)
+ @compileLog("Expected Timeout.doUnref to be a callback but received " ++ @typeName(@TypeOf(Timeout.doUnref)));
+ if (!JSC.is_bindgen) {
+ @export(Timeout.doRef, .{ .name = "TimeoutPrototype__doRef" });
+ @export(Timeout.doUnref, .{ .name = "TimeoutPrototype__doUnref" });
+ @export(Timeout.finalize, .{ .name = "TimeoutClass__finalize" });
+ @export(Timeout.hasRef, .{ .name = "TimeoutPrototype__hasRef" });
+ @export(Timeout.toPrimitive, .{ .name = "TimeoutPrototype__toPrimitive" });
+ }
+ }
+};
pub const JSTranspiler = struct {
const Transpiler = Classes.Transpiler;
const GetterType = fn (*Transpiler, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
@@ -3691,5 +3759,6 @@ comptime {
_ = JSTCPSocket;
_ = JSTLSSocket;
_ = JSTextDecoder;
+ _ = JSTimeout;
_ = JSTranspiler;
}
diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig
index 446416a3e..d779a4ff1 100644
--- a/src/bun.js/bindings/generated_classes_list.zig
+++ b/src/bun.js/bindings/generated_classes_list.zig
@@ -26,4 +26,5 @@ pub const Classes = struct {
pub const NodeJSFS = JSC.Node.NodeJSFS;
pub const Transpiler = JSC.API.Transpiler;
pub const Stats = JSC.Node.Stats;
+ pub const Timeout = JSC.API.Bun.Timer.TimerObject;
};
diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h
index c57c65e18..45ef8dbb7 100644
--- a/src/bun.js/bindings/headers-cpp.h
+++ b/src/bun.js/bindings/headers-cpp.h
@@ -1,4 +1,4 @@
-//-- AUTOGENERATED FILE -- 1676656020
+//-- AUTOGENERATED FILE -- 1676701449
// clang-format off
#pragma once
diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts
index 14a9ebbc5..5bebabe72 100644
--- a/src/bun.js/node/node.classes.ts
+++ b/src/bun.js/node/node.classes.ts
@@ -2,6 +2,33 @@ import { define } from "../scripts/class-definitions";
export default [
define({
+ name: "Timeout",
+ construct: false,
+ noConstructor: true,
+ finalize: true,
+ configurable: false,
+ klass: {},
+ JSType: "0b11101110",
+ proto: {
+ ref: {
+ fn: "doRef",
+ length: 0,
+ },
+ unref: {
+ fn: "doUnref",
+ length: 0,
+ },
+ hasRef: {
+ fn: "hasRef",
+ length: 0,
+ },
+ ["@@toPrimitive"]: {
+ fn: "toPrimitive",
+ length: 1,
+ },
+ },
+ }),
+ define({
name: "Stats",
construct: true,
finalize: true,
diff --git a/src/bun.js/node_timers.exports.js b/src/bun.js/node_timers.exports.js
index d1f3e81e7..52dec5baa 100644
--- a/src/bun.js/node_timers.exports.js
+++ b/src/bun.js/node_timers.exports.js
@@ -1,110 +1,7 @@
// This implementation isn't 100% correct
// Ref/unref does not impact whether the process is kept alive
-var clear = Symbol("clear");
-class Timeout {
- #id;
- #refCount = 1;
- #clearFunction;
-
- constructor(id, clearFunction) {
- this.#id = id;
- this.#refCount = 1;
- this.#clearFunction = clearFunction;
- }
-
- ref() {
- this.#refCount += 1;
- }
-
- hasRef() {
- return this.#refCount > 0;
- }
-
- [clear]() {
- this.#refCount = 0;
- var clearFunction = this.#clearFunction;
- if (clearFunction) {
- this.#clearFunction = null;
- clearFunction(this.#id);
- }
- }
-
- unref() {
- this.#refCount -= 1;
- var clearFunction = this.#clearFunction;
- if (clearFunction && this.#refCount === 0) {
- this.#clearFunction = null;
- clearFunction(this.#id);
- }
- }
-}
-var {
- setTimeout: setTimeout_,
- setImmediate: setImmediate_,
- clearTimeout: clearTimeout_,
- setInterval: setInterval_,
- clearInterval: clearInterval_,
-} = globalThis;
-
-export function setImmediate(callback, ...args) {
- if (typeof callback !== "function") {
- throw new TypeError("callback must be a function");
- }
- var cleared = false;
- function clearImmediate(id) {
- cleared = true;
- }
-
- const wrapped = function (callback, args) {
- if (cleared) {
- return;
- }
- cleared = true;
- try {
- callback(...args);
- } catch (e) {
- reportError(e);
- } finally {
- }
- };
-
- return new Timeout(setImmediate_(wrapped, callback, args), clearImmediate);
-}
-
-export function setTimeout(callback, delay, ...args) {
- if (typeof callback !== "function") {
- throw new TypeError("callback must be a function");
- }
-
- return new Timeout(setTimeout_.apply(globalThis, arguments), clearTimeout_);
-}
-
-export function setInterval(callback, delay, ...args) {
- if (typeof callback !== "function") {
- throw new TypeError("callback must be a function");
- }
-
- return new Timeout(setInterval_.apply(globalThis, arguments), clearInterval_);
-}
-
-export function clearTimeout(id) {
- if (id && typeof id === "object" && id[clear]) {
- id[clear]();
- return;
- }
-
- clearTimeout_(id);
-}
-
-export function clearInterval(id) {
- if (id && typeof id === "object" && id[clear]) {
- id[clear]();
- return;
- }
-
- clearInterval_(id);
-}
+export var { setTimeout, clearTimeout, setInterval, setImmediate, clearInterval, clearImmediate } = globalThis;
export default {
setInterval,
diff --git a/test/bun.js/inspect.test.js b/test/bun.js/inspect.test.js
index 229f80548..243f23cdc 100644
--- a/test/bun.js/inspect.test.js
+++ b/test/bun.js/inspect.test.js
@@ -1,5 +1,14 @@
import { it, expect, describe } from "bun:test";
+it("Timeout", () => {
+ const id = setTimeout(() => {}, 0);
+ expect(Bun.inspect(id)).toBe(`Timeout (#${+id})`);
+
+ const id2 = setInterval(() => {}, 1);
+ id2.unref();
+ expect(Bun.inspect(id2)).toBe(`Timeout (#${+id2}, repeats)`);
+});
+
it("when prototype defines the same property, don't print the same property twice", () => {
var base = {
foo: "123",
diff --git a/test/bun.js/setInterval.test.js b/test/bun.js/setInterval.test.js
index 0981df8b1..7b03afba5 100644
--- a/test/bun.js/setInterval.test.js
+++ b/test/bun.js/setInterval.test.js
@@ -14,8 +14,7 @@ it("setInterval", async () => {
clearInterval(id);
}
try {
- expect(args.length).toBe(1);
- expect(args[0]).toBe("foo");
+ expect(args).toStrictEqual(["foo"]);
} catch (err) {
reject(err);
clearInterval(id);
diff --git a/test/bun.js/setTimeout.test.js b/test/bun.js/setTimeout.test.js
index 393a32bbe..88472adc7 100644
--- a/test/bun.js/setTimeout.test.js
+++ b/test/bun.js/setTimeout.test.js
@@ -13,8 +13,7 @@ it("setTimeout", async () => {
resolve(numbers);
}
try {
- expect(args.length).toBe(1);
- expect(args[0]).toBe("foo");
+ expect(args).toStrictEqual(["foo"]);
} catch (err) {
reject(err);
}
@@ -22,7 +21,7 @@ it("setTimeout", async () => {
i,
"foo",
);
- expect(id > lastID).toBe(true);
+ expect(+id > lastID).toBe(true);
lastID = id;
}
});
@@ -35,14 +34,30 @@ it("setTimeout", async () => {
it("clearTimeout", async () => {
var called = false;
- const id = setTimeout(() => {
- called = true;
- expect(false).toBe(true);
- }, 1);
- clearTimeout(id);
- // assert it doesn't crash if you call clearTimeout twice
- clearTimeout(id);
+ // as object
+ {
+ const id = setTimeout(() => {
+ called = true;
+ expect(false).toBe(true);
+ }, 0);
+ clearTimeout(id);
+
+ // assert it doesn't crash if you call clearTimeout twice
+ clearTimeout(id);
+ }
+
+ // as number
+ {
+ const id = setTimeout(() => {
+ called = true;
+ expect(false).toBe(true);
+ }, 0);
+ clearTimeout(+id);
+
+ // assert it doesn't crash if you call clearTimeout twice
+ clearTimeout(+id);
+ }
await new Promise((resolve, reject) => {
setTimeout(resolve, 10);
@@ -118,7 +133,7 @@ it("Bun.sleep propagates exceptions", async () => {
it("Bun.sleep works with a Date object", async () => {
var ten_ms = new Date();
- ten_ms.setMilliseconds(ten_ms.getMilliseconds() + 10);
+ ten_ms.setMilliseconds(ten_ms.getMilliseconds() + 12);
const now = performance.now();
await Bun.sleep(ten_ms);
expect(performance.now() - now).toBeGreaterThanOrEqual(10);
@@ -135,3 +150,24 @@ it("node.js timers/promises setTimeout propagates exceptions", async () => {
expect(err.message).toBe("TestPassed");
}
});
+
+it.skip("order of setTimeouts", done => {
+ var nums = [];
+ var maybeDone = cb => {
+ return () => {
+ cb();
+ if (nums.length === 4) {
+ try {
+ expect(nums).toEqual([1, 2, 3, 4]);
+ done();
+ } catch (e) {
+ done(e);
+ }
+ }
+ };
+ };
+ setTimeout(maybeDone(() => nums.push(2)));
+ setTimeout(maybeDone(() => nums.push(3), 0));
+ setTimeout(maybeDone(() => nums.push(4), 1));
+ Promise.resolve().then(maybeDone(() => nums.push(1)));
+});