aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js')
-rw-r--r--src/bun.js/bindings/JSSink.cpp2
-rw-r--r--src/bun.js/bindings/JSSink.h2
-rw-r--r--src/bun.js/bindings/JSSinkLookupTable.h2
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h1
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h1
-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.cpp322
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.h56
-rw-r--r--src/bun.js/bindings/generated_classes.zig94
-rw-r--r--src/bun.js/bindings/generated_classes_list.zig1
-rw-r--r--src/bun.js/event_loop.zig7
-rw-r--r--src/bun.js/javascript.zig7
-rw-r--r--src/bun.js/node/fs_events.zig609
-rw-r--r--src/bun.js/node/node.classes.ts30
-rw-r--r--src/bun.js/node/node_fs.zig13
-rw-r--r--src/bun.js/node/node_fs_binding.zig2
-rw-r--r--src/bun.js/node/node_fs_watcher.zig913
-rw-r--r--src/bun.js/node/types.zig4
-rw-r--r--src/bun.js/webcore/encoding.zig15
20 files changed, 2084 insertions, 10 deletions
diff --git a/src/bun.js/bindings/JSSink.cpp b/src/bun.js/bindings/JSSink.cpp
index 36be334dd..4acf01ff7 100644
--- a/src/bun.js/bindings/JSSink.cpp
+++ b/src/bun.js/bindings/JSSink.cpp
@@ -1,6 +1,6 @@
// AUTO-GENERATED FILE. DO NOT EDIT.
-// Generated by 'make generate-sink' at 2023-05-18T01:04:00.447Z
+// Generated by 'make generate-sink' at 2023-06-14T21:38:04.394Z
// To regenerate this file, run:
//
// make generate-sink
diff --git a/src/bun.js/bindings/JSSink.h b/src/bun.js/bindings/JSSink.h
index 5bbfab777..37c458e9b 100644
--- a/src/bun.js/bindings/JSSink.h
+++ b/src/bun.js/bindings/JSSink.h
@@ -1,6 +1,6 @@
// AUTO-GENERATED FILE. DO NOT EDIT.
-// Generated by 'make generate-sink' at 2023-05-18T01:04:00.446Z
+// Generated by 'make generate-sink' at 2023-06-14T21:38:04.394Z
//
#pragma once
diff --git a/src/bun.js/bindings/JSSinkLookupTable.h b/src/bun.js/bindings/JSSinkLookupTable.h
index a4ace6dc3..e4ed81629 100644
--- a/src/bun.js/bindings/JSSinkLookupTable.h
+++ b/src/bun.js/bindings/JSSinkLookupTable.h
@@ -1,4 +1,4 @@
-// Automatically generated from src/bun.js/bindings/JSSink.cpp using /Users/jarred/Code/bun/src/bun.js/WebKit/Source/JavaScriptCore/create_hash_table. DO NOT EDIT!
+// Automatically generated from src/bun.js/bindings/JSSink.cpp using /home/cirospaciari/Repos/bun/src/bun.js/WebKit/Source/JavaScriptCore/create_hash_table. DO NOT EDIT!
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
index b16febcdb..f0d491c0b 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
@@ -8,6 +8,7 @@ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectConstructor;std:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectAnything;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectStringContaining;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectStringMatching;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFSWatcher;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouter;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouterConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForListener;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD4;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
index 59263e62c..02a9adbca 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
@@ -8,6 +8,7 @@ std::unique_ptr<IsoSubspace> m_subspaceForExpectConstructor;std::unique_ptr<IsoS
std::unique_ptr<IsoSubspace> m_subspaceForExpectAnything;
std::unique_ptr<IsoSubspace> m_subspaceForExpectStringContaining;
std::unique_ptr<IsoSubspace> m_subspaceForExpectStringMatching;
+std::unique_ptr<IsoSubspace> m_subspaceForFSWatcher;
std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouter;
std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouterConstructor;std::unique_ptr<IsoSubspace> m_subspaceForListener;
std::unique_ptr<IsoSubspace> m_subspaceForMD4;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
index 4471fbab3..ac03032e6 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
@@ -58,6 +58,12 @@ JSC::Structure* JSExpectStringMatchingStructure() { return m_JSExpectStringMatch
JSC::LazyClassStructure m_JSExpectStringMatching;
bool hasJSExpectStringMatchingSetterValue { false };
mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectStringMatchingSetterValue;
+JSC::Structure* JSFSWatcherStructure() { return m_JSFSWatcher.getInitializedOnMainThread(this); }
+ JSC::JSObject* JSFSWatcherConstructor() { return m_JSFSWatcher.constructorInitializedOnMainThread(this); }
+ JSC::JSValue JSFSWatcherPrototype() { return m_JSFSWatcher.prototypeInitializedOnMainThread(this); }
+ JSC::LazyClassStructure m_JSFSWatcher;
+ bool hasJSFSWatcherSetterValue { false };
+ mutable JSC::WriteBarrier<JSC::Unknown> m_JSFSWatcherSetterValue;
JSC::Structure* JSFileSystemRouterStructure() { return m_JSFileSystemRouter.getInitializedOnMainThread(this); }
JSC::JSObject* JSFileSystemRouterConstructor() { return m_JSFileSystemRouter.constructorInitializedOnMainThread(this); }
JSC::JSValue JSFileSystemRouterPrototype() { return m_JSFileSystemRouter.prototypeInitializedOnMainThread(this); }
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
index 4e5a2c1fa..b3b5327a4 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
@@ -59,6 +59,12 @@ void GlobalObject::initGeneratedLazyClasses() {
init.setStructure(WebCore::JSExpectStringMatching::createStructure(init.vm, init.global, init.prototype));
});
+ m_JSFSWatcher.initLater(
+ [](LazyClassStructure::Initializer& init) {
+ init.setPrototype(WebCore::JSFSWatcher::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
+ init.setStructure(WebCore::JSFSWatcher::createStructure(init.vm, init.global, init.prototype));
+
+ });
m_JSFileSystemRouter.initLater(
[](LazyClassStructure::Initializer& init) {
init.setPrototype(WebCore::JSFileSystemRouter::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
@@ -211,6 +217,7 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor&
thisObject->m_JSExpectAnything.visit(visitor); visitor.append(thisObject->m_JSExpectAnythingSetterValue);
thisObject->m_JSExpectStringContaining.visit(visitor); visitor.append(thisObject->m_JSExpectStringContainingSetterValue);
thisObject->m_JSExpectStringMatching.visit(visitor); visitor.append(thisObject->m_JSExpectStringMatchingSetterValue);
+ thisObject->m_JSFSWatcher.visit(visitor); visitor.append(thisObject->m_JSFSWatcherSetterValue);
thisObject->m_JSFileSystemRouter.visit(visitor); visitor.append(thisObject->m_JSFileSystemRouterSetterValue);
thisObject->m_JSListener.visit(visitor); visitor.append(thisObject->m_JSListenerSetterValue);
thisObject->m_JSMD4.visit(visitor); visitor.append(thisObject->m_JSMD4SetterValue);
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp
index d51a1959a..e0a3f33d6 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.cpp
+++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp
@@ -5381,6 +5381,297 @@ void JSExpectStringMatching::visitOutputConstraintsImpl(JSCell* cell, Visitor& v
}
DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSExpectStringMatching);
+class JSFSWatcherPrototype final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+
+ static JSFSWatcherPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ JSFSWatcherPrototype* ptr = new (NotNull, JSC::allocateCell<JSFSWatcherPrototype>(vm)) JSFSWatcherPrototype(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:
+ JSFSWatcherPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
+};
+
+extern "C" void FSWatcherClass__finalize(void*);
+
+extern "C" EncodedJSValue FSWatcherPrototype__doClose(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(FSWatcherPrototype__closeCallback);
+
+extern "C" EncodedJSValue FSWatcherPrototype__hasRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(FSWatcherPrototype__hasRefCallback);
+
+extern "C" EncodedJSValue FSWatcherPrototype__doRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(FSWatcherPrototype__refCallback);
+
+extern "C" EncodedJSValue FSWatcherPrototype__doUnref(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(FSWatcherPrototype__unrefCallback);
+
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSFSWatcherPrototype, JSFSWatcherPrototype::Base);
+
+static const HashTableValue JSFSWatcherPrototypeTableValues[] = {
+ { "close"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, FSWatcherPrototype__closeCallback, 0 } },
+ { "hasRef"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, FSWatcherPrototype__hasRefCallback, 0 } },
+ { "ref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, FSWatcherPrototype__refCallback, 0 } },
+ { "unref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, FSWatcherPrototype__unrefCallback, 0 } }
+};
+
+const ClassInfo JSFSWatcherPrototype::s_info = { "FSWatcher"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSFSWatcherPrototype) };
+
+JSC_DEFINE_HOST_FUNCTION(FSWatcherPrototype__closeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSFSWatcher* thisObject = jsDynamicCast<JSFSWatcher*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
+ return FSWatcherPrototype__doClose(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+JSC_DEFINE_HOST_FUNCTION(FSWatcherPrototype__hasRefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSFSWatcher* thisObject = jsDynamicCast<JSFSWatcher*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
+ return FSWatcherPrototype__hasRef(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+JSC_DEFINE_HOST_FUNCTION(FSWatcherPrototype__refCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSFSWatcher* thisObject = jsDynamicCast<JSFSWatcher*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
+ return FSWatcherPrototype__doRef(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+JSC_DEFINE_HOST_FUNCTION(FSWatcherPrototype__unrefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSFSWatcher* thisObject = jsDynamicCast<JSFSWatcher*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
+ return FSWatcherPrototype__doUnref(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+extern "C" void FSWatcherPrototype__listenerSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value)
+{
+ auto& vm = globalObject->vm();
+ auto* thisObject = jsCast<JSFSWatcher*>(JSValue::decode(thisValue));
+ thisObject->m_listener.set(vm, thisObject, JSValue::decode(value));
+}
+
+extern "C" EncodedJSValue FSWatcherPrototype__listenerGetCachedValue(JSC::EncodedJSValue thisValue)
+{
+ auto* thisObject = jsCast<JSFSWatcher*>(JSValue::decode(thisValue));
+ return JSValue::encode(thisObject->m_listener.get());
+}
+
+void JSFSWatcherPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+ reifyStaticProperties(vm, JSFSWatcher::info(), JSFSWatcherPrototypeTableValues, *this);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+JSFSWatcher::~JSFSWatcher()
+{
+ if (m_ctx) {
+ FSWatcherClass__finalize(m_ctx);
+ }
+}
+void JSFSWatcher::destroy(JSCell* cell)
+{
+ static_cast<JSFSWatcher*>(cell)->JSFSWatcher::~JSFSWatcher();
+}
+
+const ClassInfo JSFSWatcher::s_info = { "FSWatcher"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSFSWatcher) };
+
+void JSFSWatcher::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+}
+
+JSFSWatcher* JSFSWatcher::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx)
+{
+ JSFSWatcher* ptr = new (NotNull, JSC::allocateCell<JSFSWatcher>(vm)) JSFSWatcher(vm, structure, ctx);
+ ptr->finishCreation(vm);
+ return ptr;
+}
+
+extern "C" void* FSWatcher__fromJS(JSC::EncodedJSValue value)
+{
+ JSC::JSValue decodedValue = JSC::JSValue::decode(value);
+ if (decodedValue.isEmpty() || !decodedValue.isCell())
+ return nullptr;
+
+ JSC::JSCell* cell = decodedValue.asCell();
+ JSFSWatcher* object = JSC::jsDynamicCast<JSFSWatcher*>(cell);
+
+ if (!object)
+ return nullptr;
+
+ return object->wrapped();
+}
+
+extern "C" bool FSWatcher__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr)
+{
+ JSFSWatcher* object = JSC::jsDynamicCast<JSFSWatcher*>(JSValue::decode(value));
+ if (!object)
+ return false;
+
+ object->m_ctx = ptr;
+ return true;
+}
+
+extern "C" const size_t FSWatcher__ptrOffset = JSFSWatcher::offsetOfWrapped();
+
+void JSFSWatcher::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<JSFSWatcher*>(cell);
+ if (void* wrapped = thisObject->wrapped()) {
+ // if (thisObject->scriptExecutionContext())
+ // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+ }
+ Base::analyzeHeap(cell, analyzer);
+}
+
+JSObject* JSFSWatcher::createPrototype(VM& vm, JSDOMGlobalObject* globalObject)
+{
+ return JSFSWatcherPrototype::create(vm, globalObject, JSFSWatcherPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
+}
+
+extern "C" EncodedJSValue FSWatcher__create(Zig::GlobalObject* globalObject, void* ptr)
+{
+ auto& vm = globalObject->vm();
+ JSC::Structure* structure = globalObject->JSFSWatcherStructure();
+ JSFSWatcher* instance = JSFSWatcher::create(vm, globalObject, structure, ptr);
+
+ return JSValue::encode(instance);
+}
+
+template<typename Visitor>
+void JSFSWatcher::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ JSFSWatcher* thisObject = jsCast<JSFSWatcher*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ visitor.append(thisObject->m_listener);
+}
+
+DEFINE_VISIT_CHILDREN(JSFSWatcher);
+
+template<typename Visitor>
+void JSFSWatcher::visitAdditionalChildren(Visitor& visitor)
+{
+ JSFSWatcher* thisObject = this;
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ visitor.append(thisObject->m_listener);
+}
+
+DEFINE_VISIT_ADDITIONAL_CHILDREN(JSFSWatcher);
+
+template<typename Visitor>
+void JSFSWatcher::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor)
+{
+ JSFSWatcher* thisObject = jsCast<JSFSWatcher*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ thisObject->visitAdditionalChildren<Visitor>(visitor);
+}
+
+DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSFSWatcher);
class JSFileSystemRouterPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
@@ -7654,6 +7945,9 @@ JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__utimesCallback);
extern "C" EncodedJSValue NodeJSFSPrototype__utimesSync(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__utimesSyncCallback);
+extern "C" EncodedJSValue NodeJSFSPrototype__watch(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__watchCallback);
+
extern "C" EncodedJSValue NodeJSFSPrototype__write(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__writeCallback);
@@ -7751,6 +8045,7 @@ static const HashTableValue JSNodeJSFSPrototypeTableValues[] = {
{ "unlinkSync"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__unlinkSyncCallback, 1 } },
{ "utimes"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__utimesCallback, 4 } },
{ "utimesSync"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__utimesSyncCallback, 3 } },
+ { "watch"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__watchCallback, 3 } },
{ "write"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__writeCallback, 6 } },
{ "writeFile"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__writeFileCallback, 4 } },
{ "writeFileSync"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__writeFileSyncCallback, 3 } },
@@ -9795,6 +10090,33 @@ JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__utimesSyncCallback, (JSGlobalObject
return NodeJSFSPrototype__utimesSync(thisObject->wrapped(), lexicalGlobalObject, callFrame);
}
+JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__watchCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSNodeJSFS* thisObject = jsDynamicCast<JSNodeJSFS*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
+ return NodeJSFSPrototype__watch(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__writeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = lexicalGlobalObject->vm();
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h
index 668cd3f6b..3fa0e26d2 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses.h
@@ -578,6 +578,62 @@ public:
mutable JSC::WriteBarrier<JSC::Unknown> m_testValue;
};
+class JSFSWatcher final : public JSC::JSDestructibleObject {
+public:
+ using Base = JSC::JSDestructibleObject;
+ static JSFSWatcher* 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<JSFSWatcher, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForFSWatcher.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForFSWatcher = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForFSWatcher.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForFSWatcher = 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);
+ ;
+
+ ~JSFSWatcher();
+
+ void* wrapped() const { return m_ctx; }
+
+ void detach()
+ {
+ m_ctx = nullptr;
+ }
+
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+ static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSFSWatcher, m_ctx); }
+
+ void* m_ctx { nullptr };
+
+ JSFSWatcher(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
+ : Base(vm, structure)
+ {
+ m_ctx = sinkPtr;
+ }
+
+ void finishCreation(JSC::VM&);
+
+ DECLARE_VISIT_CHILDREN;
+ template<typename Visitor> void visitAdditionalChildren(Visitor&);
+ DECLARE_VISIT_OUTPUT_CONSTRAINTS;
+
+ mutable JSC::WriteBarrier<JSC::Unknown> m_listener;
+};
+
class JSFileSystemRouter final : public JSC::JSDestructibleObject {
public:
using Base = JSC::JSDestructibleObject;
diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig
index 0ec65a469..74e30cd83 100644
--- a/src/bun.js/bindings/generated_classes.zig
+++ b/src/bun.js/bindings/generated_classes.zig
@@ -1406,6 +1406,96 @@ pub const JSExpectStringMatching = struct {
}
}
};
+pub const JSFSWatcher = struct {
+ const FSWatcher = Classes.FSWatcher;
+ const GetterType = fn (*FSWatcher, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const GetterTypeWithThisValue = fn (*FSWatcher, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const SetterType = fn (*FSWatcher, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const SetterTypeWithThisValue = fn (*FSWatcher, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const CallbackType = fn (*FSWatcher, *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) ?*FSWatcher {
+ JSC.markBinding(@src());
+ return FSWatcher__fromJS(value);
+ }
+
+ extern fn FSWatcherPrototype__listenerSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void;
+
+ extern fn FSWatcherPrototype__listenerGetCachedValue(JSC.JSValue) JSC.JSValue;
+
+ /// `FSWatcher.listener` setter
+ /// This value will be visited by the garbage collector.
+ pub fn listenerSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void {
+ JSC.markBinding(@src());
+ FSWatcherPrototype__listenerSetCachedValue(thisValue, globalObject, value);
+ }
+
+ /// `FSWatcher.listener` getter
+ /// This value will be visited by the garbage collector.
+ pub fn listenerGetCached(thisValue: JSC.JSValue) ?JSC.JSValue {
+ JSC.markBinding(@src());
+ const result = FSWatcherPrototype__listenerGetCachedValue(thisValue);
+ if (result == .zero)
+ return null;
+
+ return result;
+ }
+
+ /// Create a new instance of FSWatcher
+ pub fn toJS(this: *FSWatcher, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding(@src());
+ if (comptime Environment.allow_assert) {
+ const value__ = FSWatcher__create(globalObject, this);
+ std.debug.assert(value__.as(FSWatcher).? == this); // If this fails, likely a C ABI issue.
+ return value__;
+ } else {
+ return FSWatcher__create(globalObject, this);
+ }
+ }
+
+ /// Modify the internal ptr to point to a new instance of FSWatcher.
+ pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*FSWatcher) bool {
+ JSC.markBinding(@src());
+ return FSWatcher__dangerouslySetPtr(value, ptr);
+ }
+
+ /// Detach the ptr from the thisValue
+ pub fn detachPtr(_: *FSWatcher, value: JSC.JSValue) void {
+ JSC.markBinding(@src());
+ std.debug.assert(FSWatcher__dangerouslySetPtr(value, null));
+ }
+
+ extern fn FSWatcher__fromJS(JSC.JSValue) ?*FSWatcher;
+ extern fn FSWatcher__getConstructor(*JSC.JSGlobalObject) JSC.JSValue;
+
+ extern fn FSWatcher__create(globalObject: *JSC.JSGlobalObject, ptr: ?*FSWatcher) JSC.JSValue;
+
+ extern fn FSWatcher__dangerouslySetPtr(JSC.JSValue, ?*FSWatcher) bool;
+
+ comptime {
+ if (@TypeOf(FSWatcher.finalize) != (fn (*FSWatcher) callconv(.C) void)) {
+ @compileLog("FSWatcher.finalize is not a finalizer");
+ }
+
+ if (@TypeOf(FSWatcher.doClose) != CallbackType)
+ @compileLog("Expected FSWatcher.doClose to be a callback but received " ++ @typeName(@TypeOf(FSWatcher.doClose)));
+ if (@TypeOf(FSWatcher.hasRef) != CallbackType)
+ @compileLog("Expected FSWatcher.hasRef to be a callback but received " ++ @typeName(@TypeOf(FSWatcher.hasRef)));
+ if (@TypeOf(FSWatcher.doRef) != CallbackType)
+ @compileLog("Expected FSWatcher.doRef to be a callback but received " ++ @typeName(@TypeOf(FSWatcher.doRef)));
+ if (@TypeOf(FSWatcher.doUnref) != CallbackType)
+ @compileLog("Expected FSWatcher.doUnref to be a callback but received " ++ @typeName(@TypeOf(FSWatcher.doUnref)));
+ if (!JSC.is_bindgen) {
+ @export(FSWatcher.doClose, .{ .name = "FSWatcherPrototype__doClose" });
+ @export(FSWatcher.doRef, .{ .name = "FSWatcherPrototype__doRef" });
+ @export(FSWatcher.doUnref, .{ .name = "FSWatcherPrototype__doUnref" });
+ @export(FSWatcher.finalize, .{ .name = "FSWatcherClass__finalize" });
+ @export(FSWatcher.hasRef, .{ .name = "FSWatcherPrototype__hasRef" });
+ }
+ }
+};
pub const JSFileSystemRouter = struct {
const FileSystemRouter = Classes.FileSystemRouter;
const GetterType = fn (*FileSystemRouter, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
@@ -2312,6 +2402,8 @@ pub const JSNodeJSFS = struct {
@compileLog("Expected NodeJSFS.utimes to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.utimes)));
if (@TypeOf(NodeJSFS.utimesSync) != CallbackType)
@compileLog("Expected NodeJSFS.utimesSync to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.utimesSync)));
+ if (@TypeOf(NodeJSFS.watch) != CallbackType)
+ @compileLog("Expected NodeJSFS.watch to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.watch)));
if (@TypeOf(NodeJSFS.write) != CallbackType)
@compileLog("Expected NodeJSFS.write to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.write)));
if (@TypeOf(NodeJSFS.writeFile) != CallbackType)
@@ -2402,6 +2494,7 @@ pub const JSNodeJSFS = struct {
@export(NodeJSFS.unlinkSync, .{ .name = "NodeJSFSPrototype__unlinkSync" });
@export(NodeJSFS.utimes, .{ .name = "NodeJSFSPrototype__utimes" });
@export(NodeJSFS.utimesSync, .{ .name = "NodeJSFSPrototype__utimesSync" });
+ @export(NodeJSFS.watch, .{ .name = "NodeJSFSPrototype__watch" });
@export(NodeJSFS.write, .{ .name = "NodeJSFSPrototype__write" });
@export(NodeJSFS.writeFile, .{ .name = "NodeJSFSPrototype__writeFile" });
@export(NodeJSFS.writeFileSync, .{ .name = "NodeJSFSPrototype__writeFileSync" });
@@ -4855,6 +4948,7 @@ comptime {
_ = JSExpectAnything;
_ = JSExpectStringContaining;
_ = JSExpectStringMatching;
+ _ = JSFSWatcher;
_ = JSFileSystemRouter;
_ = JSListener;
_ = JSMD4;
diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig
index c54965093..d90267337 100644
--- a/src/bun.js/bindings/generated_classes_list.zig
+++ b/src/bun.js/bindings/generated_classes_list.zig
@@ -37,4 +37,5 @@ pub const Classes = struct {
pub const BuildArtifact = JSC.API.BuildArtifact;
pub const BuildMessage = JSC.BuildMessage;
pub const ResolveMessage = JSC.ResolveMessage;
+ pub const FSWatcher = JSC.Node.FSWatcher.JSObject;
};
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index 0a3459d64..a3ccd16ad 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -224,6 +224,7 @@ pub const CppTask = opaque {
const ThreadSafeFunction = JSC.napi.ThreadSafeFunction;
const MicrotaskForDefaultGlobalObject = JSC.MicrotaskForDefaultGlobalObject;
const HotReloadTask = JSC.HotReloader.HotReloadTask;
+const FSWatchTask = JSC.Node.FSWatcher.FSWatchTask;
const PollPendingModulesTask = JSC.ModuleLoader.AsyncModule.Queue;
// const PromiseTask = JSInternalPromise.Completion.PromiseTask;
const GetAddrInfoRequestTask = JSC.DNS.GetAddrInfoRequest.Task;
@@ -242,6 +243,7 @@ pub const Task = TaggedPointerUnion(.{
HotReloadTask,
PollPendingModulesTask,
GetAddrInfoRequestTask,
+ FSWatchTask,
// PromiseTask,
// TimeoutTasklet,
});
@@ -467,6 +469,11 @@ pub const EventLoop = struct {
// special case: we return
return 0;
},
+ .FSWatchTask => {
+ var transform_task: *FSWatchTask = task.get(FSWatchTask).?;
+ transform_task.*.run();
+ transform_task.deinit();
+ },
@field(Task.Tag, typeBaseName(@typeName(AnyTask))) => {
var any: *AnyTask = task.get(AnyTask).?;
any.run();
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index bebfbeb18..3baa25e22 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -2609,6 +2609,13 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
return this.tombstones.get(key);
}
+ pub fn onError(
+ _: *@This(),
+ err: anyerror,
+ ) void {
+ Output.prettyErrorln("<r>Watcher crashed: <red><b>{s}<r>", .{@errorName(err)});
+ }
+
pub fn onFileUpdate(
this: *@This(),
events: []watcher.WatchEvent,
diff --git a/src/bun.js/node/fs_events.zig b/src/bun.js/node/fs_events.zig
new file mode 100644
index 000000000..a3fba5441
--- /dev/null
+++ b/src/bun.js/node/fs_events.zig
@@ -0,0 +1,609 @@
+const std = @import("std");
+const bun = @import("root").bun;
+const Environment = bun.Environment;
+const Mutex = @import("../../lock.zig").Lock;
+const sync = @import("../../sync.zig");
+const Semaphore = sync.Semaphore;
+const UnboundedQueue = @import("../unbounded_queue.zig").UnboundedQueue;
+const TaggedPointerUnion = @import("../../tagged_pointer.zig").TaggedPointerUnion;
+const string = bun.string;
+
+pub const CFAbsoluteTime = f64;
+pub const CFTimeInterval = f64;
+pub const CFArrayCallBacks = anyopaque;
+
+pub const FSEventStreamEventFlags = c_int;
+pub const OSStatus = c_int;
+pub const CFIndex = c_long;
+
+pub const FSEventStreamCreateFlags = u32;
+pub const FSEventStreamEventId = u64;
+
+pub const CFStringEncoding = c_uint;
+
+pub const CFArrayRef = ?*anyopaque;
+pub const CFAllocatorRef = ?*anyopaque;
+pub const CFBundleRef = ?*anyopaque;
+pub const CFDictionaryRef = ?*anyopaque;
+pub const CFRunLoopRef = ?*anyopaque;
+pub const CFRunLoopSourceRef = ?*anyopaque;
+pub const CFStringRef = ?*anyopaque;
+pub const CFTypeRef = ?*anyopaque;
+pub const FSEventStreamRef = ?*anyopaque;
+pub const FSEventStreamCallback = *const fn (FSEventStreamRef, ?*anyopaque, usize, ?*anyopaque, *FSEventStreamEventFlags, *FSEventStreamEventId) callconv(.C) void;
+
+// we only care about info and perform
+pub const CFRunLoopSourceContext = extern struct {
+ version: CFIndex = 0,
+ info: *anyopaque,
+ retain: ?*anyopaque = null,
+ release: ?*anyopaque = null,
+ copyDescription: ?*anyopaque = null,
+ equal: ?*anyopaque = null,
+ hash: ?*anyopaque = null,
+ schedule: ?*anyopaque = null,
+ cancel: ?*anyopaque = null,
+ perform: *const fn (?*anyopaque) callconv(.C) void,
+};
+
+pub const FSEventStreamContext = extern struct {
+ version: CFIndex = 0,
+ info: ?*anyopaque = null,
+ pad: [3]?*anyopaque = .{ null, null, null },
+};
+
+pub const kCFStringEncodingUTF8: CFStringEncoding = 0x8000100;
+pub const noErr: OSStatus = 0;
+
+pub const kFSEventStreamCreateFlagNoDefer: c_int = 2;
+pub const kFSEventStreamCreateFlagFileEvents: c_int = 16;
+
+pub const kFSEventStreamEventFlagEventIdsWrapped: c_int = 8;
+pub const kFSEventStreamEventFlagHistoryDone: c_int = 16;
+pub const kFSEventStreamEventFlagItemChangeOwner: c_int = 0x4000;
+pub const kFSEventStreamEventFlagItemCreated: c_int = 0x100;
+pub const kFSEventStreamEventFlagItemFinderInfoMod: c_int = 0x2000;
+pub const kFSEventStreamEventFlagItemInodeMetaMod: c_int = 0x400;
+pub const kFSEventStreamEventFlagItemIsDir: c_int = 0x20000;
+pub const kFSEventStreamEventFlagItemModified: c_int = 0x1000;
+pub const kFSEventStreamEventFlagItemRemoved: c_int = 0x200;
+pub const kFSEventStreamEventFlagItemRenamed: c_int = 0x800;
+pub const kFSEventStreamEventFlagItemXattrMod: c_int = 0x8000;
+pub const kFSEventStreamEventFlagKernelDropped: c_int = 4;
+pub const kFSEventStreamEventFlagMount: c_int = 64;
+pub const kFSEventStreamEventFlagRootChanged: c_int = 32;
+pub const kFSEventStreamEventFlagUnmount: c_int = 128;
+pub const kFSEventStreamEventFlagUserDropped: c_int = 2;
+
+// Lazy function call binding.
+const RTLD_LAZY = 0x1;
+// Symbols exported from this image (dynamic library or bundle)
+// are generally hidden and only availble to dlsym() when
+// directly using the handle returned by this call to dlopen().
+const RTLD_LOCAL = 0x4;
+
+pub const kFSEventsModified: c_int =
+ kFSEventStreamEventFlagItemChangeOwner |
+ kFSEventStreamEventFlagItemFinderInfoMod |
+ kFSEventStreamEventFlagItemInodeMetaMod |
+ kFSEventStreamEventFlagItemModified |
+ kFSEventStreamEventFlagItemXattrMod;
+
+pub const kFSEventsRenamed: c_int =
+ kFSEventStreamEventFlagItemCreated |
+ kFSEventStreamEventFlagItemRemoved |
+ kFSEventStreamEventFlagItemRenamed;
+
+pub const kFSEventsSystem: c_int =
+ kFSEventStreamEventFlagUserDropped |
+ kFSEventStreamEventFlagKernelDropped |
+ kFSEventStreamEventFlagEventIdsWrapped |
+ kFSEventStreamEventFlagHistoryDone |
+ kFSEventStreamEventFlagMount |
+ kFSEventStreamEventFlagUnmount |
+ kFSEventStreamEventFlagRootChanged;
+
+var fsevents_mutex: Mutex = Mutex.init();
+var fsevents_default_loop_mutex: Mutex = Mutex.init();
+var fsevents_default_loop: ?*FSEventsLoop = null;
+
+fn dlsym(handle: ?*anyopaque, comptime Type: type, comptime symbol: [:0]const u8) ?Type {
+ if (std.c.dlsym(handle, symbol)) |ptr| {
+ return bun.cast(Type, ptr);
+ }
+ return null;
+}
+
+pub const CoreFoundation = struct {
+ handle: ?*anyopaque,
+ ArrayCreate: *fn (CFAllocatorRef, [*]?*anyopaque, CFIndex, ?*CFArrayCallBacks) callconv(.C) CFArrayRef,
+ Release: *fn (CFTypeRef) callconv(.C) void,
+
+ RunLoopAddSource: *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void,
+ RunLoopGetCurrent: *fn () callconv(.C) CFRunLoopRef,
+ RunLoopRemoveSource: *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void,
+ RunLoopRun: *fn () callconv(.C) void,
+ RunLoopSourceCreate: *fn (CFAllocatorRef, CFIndex, *CFRunLoopSourceContext) callconv(.C) CFRunLoopSourceRef,
+ RunLoopSourceSignal: *fn (CFRunLoopSourceRef) callconv(.C) void,
+ RunLoopStop: *fn (CFRunLoopRef) callconv(.C) void,
+ RunLoopWakeUp: *fn (CFRunLoopRef) callconv(.C) void,
+ StringCreateWithFileSystemRepresentation: *fn (CFAllocatorRef, [*]const u8) callconv(.C) CFStringRef,
+ RunLoopDefaultMode: *CFStringRef,
+
+ pub fn get() CoreFoundation {
+ if (fsevents_cf) |cf| return cf;
+ fsevents_mutex.lock();
+ defer fsevents_mutex.unlock();
+ if (fsevents_cf) |cf| return cf;
+
+ InitLibrary();
+
+ return fsevents_cf.?;
+ }
+
+ // We Actually never deinit it
+ // pub fn deinit(this: *CoreFoundation) void {
+ // if(this.handle) | ptr| {
+ // this.handle = null;
+ // _ = std.c.dlclose(this.handle);
+ // }
+ // }
+
+};
+
+pub const CoreServices = struct {
+ handle: ?*anyopaque,
+ FSEventStreamCreate: *fn (CFAllocatorRef, FSEventStreamCallback, *FSEventStreamContext, CFArrayRef, FSEventStreamEventId, CFTimeInterval, FSEventStreamCreateFlags) callconv(.C) FSEventStreamRef,
+ FSEventStreamInvalidate: *fn (FSEventStreamRef) callconv(.C) void,
+ FSEventStreamRelease: *fn (FSEventStreamRef) callconv(.C) void,
+ FSEventStreamScheduleWithRunLoop: *fn (FSEventStreamRef, CFRunLoopRef, CFStringRef) callconv(.C) void,
+ FSEventStreamStart: *fn (FSEventStreamRef) callconv(.C) c_int,
+ FSEventStreamStop: *fn (FSEventStreamRef) callconv(.C) void,
+ // libuv set it to -1 so the actual value is this
+ kFSEventStreamEventIdSinceNow: FSEventStreamEventId = 18446744073709551615,
+
+ pub fn get() CoreServices {
+ if (fsevents_cs) |cs| return cs;
+ fsevents_mutex.lock();
+ defer fsevents_mutex.unlock();
+ if (fsevents_cs) |cs| return cs;
+
+ InitLibrary();
+
+ return fsevents_cs.?;
+ }
+
+ // We Actually never deinit it
+ // pub fn deinit(this: *CoreServices) void {
+ // if(this.handle) | ptr| {
+ // this.handle = null;
+ // _ = std.c.dlclose(this.handle);
+ // }
+ // }
+
+};
+
+var fsevents_cf: ?CoreFoundation = null;
+var fsevents_cs: ?CoreServices = null;
+
+fn InitLibrary() void {
+ const fsevents_cf_handle = std.c.dlopen("/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", RTLD_LAZY | RTLD_LOCAL);
+ if (fsevents_cf_handle == null) @panic("Cannot Load CoreFoundation");
+
+ fsevents_cf = CoreFoundation{
+ .handle = fsevents_cf_handle,
+ .ArrayCreate = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, [*]?*anyopaque, CFIndex, ?*CFArrayCallBacks) callconv(.C) CFArrayRef, "CFArrayCreate") orelse @panic("Cannot Load CoreFoundation"),
+ .Release = dlsym(fsevents_cf_handle, *fn (CFTypeRef) callconv(.C) void, "CFRelease") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopAddSource = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void, "CFRunLoopAddSource") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopGetCurrent = dlsym(fsevents_cf_handle, *fn () callconv(.C) CFRunLoopRef, "CFRunLoopGetCurrent") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopRemoveSource = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void, "CFRunLoopRemoveSource") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopRun = dlsym(fsevents_cf_handle, *fn () callconv(.C) void, "CFRunLoopRun") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopSourceCreate = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, CFIndex, *CFRunLoopSourceContext) callconv(.C) CFRunLoopSourceRef, "CFRunLoopSourceCreate") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopSourceSignal = dlsym(fsevents_cf_handle, *fn (CFRunLoopSourceRef) callconv(.C) void, "CFRunLoopSourceSignal") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopStop = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef) callconv(.C) void, "CFRunLoopStop") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopWakeUp = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef) callconv(.C) void, "CFRunLoopWakeUp") orelse @panic("Cannot Load CoreFoundation"),
+ .StringCreateWithFileSystemRepresentation = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, [*]const u8) callconv(.C) CFStringRef, "CFStringCreateWithFileSystemRepresentation") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopDefaultMode = dlsym(fsevents_cf_handle, *CFStringRef, "kCFRunLoopDefaultMode") orelse @panic("Cannot Load CoreFoundation"),
+ };
+
+ const fsevents_cs_handle = std.c.dlopen("/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices", RTLD_LAZY | RTLD_LOCAL);
+ if (fsevents_cs_handle == null) @panic("Cannot Load CoreServices");
+
+ fsevents_cs = CoreServices{
+ .handle = fsevents_cs_handle,
+ .FSEventStreamCreate = dlsym(fsevents_cs_handle, *fn (CFAllocatorRef, FSEventStreamCallback, *FSEventStreamContext, CFArrayRef, FSEventStreamEventId, CFTimeInterval, FSEventStreamCreateFlags) callconv(.C) FSEventStreamRef, "FSEventStreamCreate") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamInvalidate = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamInvalidate") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamRelease = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamRelease") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamScheduleWithRunLoop = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef, CFRunLoopRef, CFStringRef) callconv(.C) void, "FSEventStreamScheduleWithRunLoop") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamStart = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) c_int, "FSEventStreamStart") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamStop = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamStop") orelse @panic("Cannot Load CoreServices"),
+ };
+}
+
+pub const FSEventsLoop = struct {
+ signal_source: CFRunLoopSourceRef,
+ mutex: Mutex,
+ loop: CFRunLoopRef = null,
+ sem: Semaphore,
+ thread: std.Thread = undefined,
+ tasks: ConcurrentTask.Queue = ConcurrentTask.Queue{},
+ watchers: bun.BabyList(?*FSEventsWatcher) = .{},
+ watcher_count: u32 = 0,
+ fsevent_stream: FSEventStreamRef = null,
+ paths: ?[]?*anyopaque = null,
+ cf_paths: CFArrayRef = null,
+ has_scheduled_watchers: bool = false,
+
+ pub const Task = struct {
+ ctx: ?*anyopaque,
+ callback: *const (fn (*anyopaque) void),
+
+ pub fn run(this: *Task) void {
+ var callback = this.callback;
+ var ctx = this.ctx;
+ callback(ctx.?);
+ }
+
+ pub fn New(comptime Type: type, comptime Callback: anytype) type {
+ return struct {
+ pub fn init(ctx: *Type) Task {
+ return Task{
+ .callback = wrap,
+ .ctx = ctx,
+ };
+ }
+
+ pub fn wrap(this: ?*anyopaque) void {
+ @call(.always_inline, Callback, .{@ptrCast(*Type, @alignCast(@alignOf(Type), this.?))});
+ }
+ };
+ }
+ };
+
+ pub const ConcurrentTask = struct {
+ task: Task = undefined,
+ next: ?*ConcurrentTask = null,
+ auto_delete: bool = false,
+
+ pub const Queue = UnboundedQueue(ConcurrentTask, .next);
+
+ pub fn from(this: *ConcurrentTask, task: Task) *ConcurrentTask {
+ this.* = .{
+ .task = task,
+ .next = null,
+ };
+ return this;
+ }
+ };
+
+ pub fn CFThreadLoop(this: *FSEventsLoop) void {
+ bun.Output.Source.configureNamedThread("CFThreadLoop");
+
+ const CF = CoreFoundation.get();
+
+ this.loop = CF.RunLoopGetCurrent();
+
+ CF.RunLoopAddSource(this.loop, this.signal_source, CF.RunLoopDefaultMode.*);
+
+ this.sem.post();
+
+ CF.RunLoopRun();
+ CF.RunLoopRemoveSource(this.loop, this.signal_source, CF.RunLoopDefaultMode.*);
+
+ this.loop = null;
+ }
+
+ // Runs in CF thread, executed after `enqueueTaskConcurrent()`
+ fn CFLoopCallback(arg: ?*anyopaque) callconv(.C) void {
+ if (arg) |self| {
+ const this = bun.cast(*FSEventsLoop, self);
+
+ var concurrent = this.tasks.popBatch();
+ const count = concurrent.count;
+ if (count == 0)
+ return;
+
+ var iter = concurrent.iterator();
+ while (iter.next()) |task| {
+ task.task.run();
+ if (task.auto_delete) bun.default_allocator.destroy(task);
+ }
+ }
+ }
+
+ pub fn init() !*FSEventsLoop {
+ const this = bun.default_allocator.create(FSEventsLoop) catch unreachable;
+
+ const CF = CoreFoundation.get();
+
+ var ctx = CFRunLoopSourceContext{
+ .info = this,
+ .perform = CFLoopCallback,
+ };
+
+ const signal_source = CF.RunLoopSourceCreate(null, 0, &ctx);
+ if (signal_source == null) {
+ return error.FailedToCreateCoreFoudationSourceLoop;
+ }
+
+ var fs_loop = FSEventsLoop{ .sem = Semaphore.init(0), .mutex = Mutex.init(), .signal_source = signal_source };
+
+ this.* = fs_loop;
+ this.thread = try std.Thread.spawn(.{}, FSEventsLoop.CFThreadLoop, .{this});
+
+ // sync threads
+ this.sem.wait();
+ return this;
+ }
+
+ fn enqueueTaskConcurrent(this: *FSEventsLoop, task: Task) void {
+ const CF = CoreFoundation.get();
+ var concurrent = bun.default_allocator.create(ConcurrentTask) catch unreachable;
+ concurrent.auto_delete = true;
+ this.tasks.push(concurrent.from(task));
+ CF.RunLoopSourceSignal(this.signal_source);
+ CF.RunLoopWakeUp(this.loop);
+ }
+
+ // Runs in CF thread, when there're events in FSEventStream
+ fn _events_cb(_: FSEventStreamRef, info: ?*anyopaque, numEvents: usize, eventPaths: ?*anyopaque, eventFlags: *FSEventStreamEventFlags, _: *FSEventStreamEventId) callconv(.C) void {
+ const paths_ptr = bun.cast([*][*:0]const u8, eventPaths);
+ const paths = paths_ptr[0..numEvents];
+ var loop = bun.cast(*FSEventsLoop, info);
+ const event_flags = bun.cast([*]FSEventStreamEventFlags, eventFlags);
+
+ for (loop.watchers.slice()) |watcher| {
+ if (watcher) |handle| {
+ for (paths, 0..) |path_ptr, i| {
+ var flags = event_flags[i];
+ var path = path_ptr[0..bun.len(path_ptr)];
+ // Filter out paths that are outside handle's request
+ if (path.len < handle.path.len or !bun.strings.startsWith(path, handle.path)) {
+ continue;
+ }
+ const is_file = (flags & kFSEventStreamEventFlagItemIsDir) == 0;
+
+ // Remove common prefix, unless the watched folder is "/"
+ if (!(handle.path.len == 1 and handle.path[0] == '/')) {
+ path = path[handle.path.len..];
+
+ // Ignore events with path equal to directory itself
+ if (path.len <= 1 and is_file) {
+ continue;
+ }
+ if (path.len == 0) {
+ // Since we're using fsevents to watch the file itself, path == handle.path, and we now need to get the basename of the file back
+ while (path.len > 0) {
+ if (bun.strings.startsWithChar(path, '/')) {
+ path = path[1..];
+ break;
+ } else {
+ path = path[1..];
+ }
+ }
+
+ // Created and Removed seem to be always set, but don't make sense
+ flags &= ~kFSEventsRenamed;
+ } else {
+ // Skip forward slash
+ path = path[1..];
+ }
+ }
+
+ // Do not emit events from subdirectories (without option set)
+ if (path.len == 0 or (bun.strings.containsChar(path, '/') and !handle.recursive)) {
+ continue;
+ }
+
+ var is_rename = true;
+
+ if ((flags & kFSEventsRenamed) == 0) {
+ if ((flags & kFSEventsModified) != 0 or is_file) {
+ is_rename = false;
+ }
+ }
+
+ handle.callback(handle.ctx, path, is_file, is_rename);
+ }
+ }
+ }
+ }
+
+ // Runs on CF Thread
+ pub fn _schedule(this: *FSEventsLoop) void {
+ this.mutex.lock();
+ defer this.mutex.unlock();
+ this.has_scheduled_watchers = false;
+
+ var watchers = this.watchers.slice();
+
+ const CF = CoreFoundation.get();
+ const CS = CoreServices.get();
+
+ if (this.fsevent_stream) |stream| {
+ // Stop emitting events
+ CS.FSEventStreamStop(stream);
+
+ // Release stream
+ CS.FSEventStreamInvalidate(stream);
+ CS.FSEventStreamRelease(stream);
+ this.fsevent_stream = null;
+ }
+ // clean old paths
+ if (this.paths) |p| {
+ this.paths = null;
+ bun.default_allocator.destroy(p);
+ }
+ if (this.cf_paths) |cf| {
+ this.cf_paths = null;
+ CF.Release(cf);
+ }
+
+ const paths = bun.default_allocator.alloc(?*anyopaque, this.watcher_count) catch unreachable;
+ var count: u32 = 0;
+ for (watchers) |w| {
+ if (w) |watcher| {
+ const path = CF.StringCreateWithFileSystemRepresentation(null, watcher.path.ptr);
+ paths[count] = path;
+ count += 1;
+ }
+ }
+
+ const cf_paths = CF.ArrayCreate(null, paths.ptr, count, null);
+ var ctx: FSEventStreamContext = .{
+ .info = this,
+ };
+
+ const latency: CFAbsoluteTime = 0.05;
+ // Explanation of selected flags:
+ // 1. NoDefer - without this flag, events that are happening continuously
+ // (i.e. each event is happening after time interval less than `latency`,
+ // counted from previous event), will be deferred and passed to callback
+ // once they'll either fill whole OS buffer, or when this continuous stream
+ // will stop (i.e. there'll be delay between events, bigger than
+ // `latency`).
+ // Specifying this flag will invoke callback after `latency` time passed
+ // since event.
+ // 2. FileEvents - fire callback for file changes too (by default it is firing
+ // it only for directory changes).
+ //
+ const flags: FSEventStreamCreateFlags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents;
+
+ //
+ // NOTE: It might sound like a good idea to remember last seen StreamEventId,
+ // but in reality one dir might have last StreamEventId less than, the other,
+ // that is being watched now. Which will cause FSEventStream API to report
+ // changes to files from the past.
+ //
+ const ref = CS.FSEventStreamCreate(null, _events_cb, &ctx, cf_paths, CS.kFSEventStreamEventIdSinceNow, latency, flags);
+
+ CS.FSEventStreamScheduleWithRunLoop(ref, this.loop, CF.RunLoopDefaultMode.*);
+ if (CS.FSEventStreamStart(ref) == 0) {
+ //clean in case of failure
+ bun.default_allocator.destroy(paths);
+ CF.Release(cf_paths);
+ CS.FSEventStreamInvalidate(ref);
+ CS.FSEventStreamRelease(ref);
+ return;
+ }
+ this.fsevent_stream = ref;
+ this.paths = paths;
+ this.cf_paths = cf_paths;
+ }
+
+ fn registerWatcher(this: *FSEventsLoop, watcher: *FSEventsWatcher) void {
+ this.mutex.lock();
+ defer this.mutex.unlock();
+ if (this.watcher_count == this.watchers.len) {
+ this.watcher_count += 1;
+ this.watchers.push(bun.default_allocator, watcher) catch unreachable;
+ } else {
+ var watchers = this.watchers.slice();
+ for (watchers, 0..) |w, i| {
+ if (w == null) {
+ watchers[i] = watcher;
+ this.watcher_count += 1;
+ break;
+ }
+ }
+ }
+
+ if (this.has_scheduled_watchers == false) {
+ this.has_scheduled_watchers = true;
+ this.enqueueTaskConcurrent(Task.New(FSEventsLoop, _schedule).init(this));
+ }
+ }
+
+ fn unregisterWatcher(this: *FSEventsLoop, watcher: *FSEventsWatcher) void {
+ this.mutex.lock();
+ defer this.mutex.unlock();
+ var watchers = this.watchers.slice();
+ for (watchers, 0..) |w, i| {
+ if (w) |item| {
+ if (item == watcher) {
+ watchers[i] = null;
+ // if is the last one just pop
+ if (i == watchers.len - 1) {
+ this.watchers.len -= 1;
+ }
+ this.watcher_count -= 1;
+ break;
+ }
+ }
+ }
+ }
+
+ // Runs on CF loop to close the loop
+ fn _stop(this: *FSEventsLoop) void {
+ const CF = CoreFoundation.get();
+ CF.RunLoopStop(this.loop);
+ }
+ fn deinit(this: *FSEventsLoop) void {
+ // signal close and wait
+ this.enqueueTaskConcurrent(Task.New(FSEventsLoop, FSEventsLoop._stop).init(this));
+ this.thread.join();
+ const CF = CoreFoundation.get();
+
+ CF.Release(this.signal_source);
+ this.signal_source = null;
+
+ this.sem.deinit();
+ this.mutex.deinit();
+ if (this.watcher_count > 0) {
+ while (this.watchers.popOrNull()) |watcher| {
+ if (watcher) |w| {
+ // unlink watcher
+ w.loop = null;
+ }
+ }
+ }
+
+ this.watchers.deinitWithAllocator(bun.default_allocator);
+
+ bun.default_allocator.destroy(this);
+ }
+};
+
+pub const FSEventsWatcher = struct {
+ path: string,
+ callback: Callback,
+ loop: ?*FSEventsLoop,
+ recursive: bool,
+ ctx: ?*anyopaque,
+
+ const Callback = *const fn (ctx: ?*anyopaque, path: string, is_file: bool, is_rename: bool) void;
+
+ pub fn init(loop: *FSEventsLoop, path: string, recursive: bool, callback: Callback, ctx: ?*anyopaque) *FSEventsWatcher {
+ var this = bun.default_allocator.create(FSEventsWatcher) catch unreachable;
+ this.* = FSEventsWatcher{
+ .path = path,
+ .callback = callback,
+ .loop = loop,
+ .recursive = recursive,
+ .ctx = ctx,
+ };
+
+ loop.registerWatcher(this);
+ return this;
+ }
+
+ pub fn deinit(this: *FSEventsWatcher) void {
+ if (this.loop) |loop| {
+ loop.unregisterWatcher(this);
+ }
+ bun.default_allocator.destroy(this);
+ }
+};
+
+pub fn watch(path: string, recursive: bool, callback: FSEventsWatcher.Callback, ctx: ?*anyopaque) !*FSEventsWatcher {
+ if (fsevents_default_loop) |loop| {
+ return FSEventsWatcher.init(loop, path, recursive, callback, ctx);
+ } else {
+ fsevents_default_loop_mutex.lock();
+ defer fsevents_default_loop_mutex.unlock();
+ if (fsevents_default_loop == null) {
+ fsevents_default_loop = try FSEventsLoop.init();
+ }
+ return FSEventsWatcher.init(fsevents_default_loop.?, path, recursive, callback, ctx);
+ }
+}
diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts
index f984077e4..ce35c940a 100644
--- a/src/bun.js/node/node.classes.ts
+++ b/src/bun.js/node/node.classes.ts
@@ -2,6 +2,34 @@ import { define } from "../scripts/class-definitions";
export default [
define({
+ name: "FSWatcher",
+ 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,
+ },
+ close: {
+ fn: "doClose",
+ length: 0,
+ },
+ },
+ values: ["listener"],
+ }),
+ define({
name: "Timeout",
construct: false,
noConstructor: true,
@@ -300,7 +328,7 @@ export default [
utimes: { fn: "utimes", length: 4 },
utimesSync: { fn: "utimesSync", length: 3 },
// TODO:
- // watch: { fn: "watch", length: 3 },
+ watch: { fn: "watch", length: 3 },
// watchFile: { fn: "watchFile", length: 3 },
writeFile: { fn: "writeFile", length: 4 },
writeFileSync: { fn: "writeFileSync", length: 3 },
diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig
index 3ea0822e6..21a65251a 100644
--- a/src/bun.js/node/node_fs.zig
+++ b/src/bun.js/node/node_fs.zig
@@ -34,7 +34,6 @@ const Mode = JSC.Node.Mode;
const uid_t = std.os.uid_t;
const gid_t = std.os.gid_t;
-
/// u63 to allow one null bit
const ReadPosition = u63;
@@ -2313,7 +2312,7 @@ pub const Arguments = struct {
};
pub const UnwatchFile = void;
- pub const Watch = void;
+ pub const Watch = JSC.Node.FSWatcher.Arguments;
pub const WatchFile = void;
pub const Fsync = struct {
fd: FileDescriptor,
@@ -2475,7 +2474,7 @@ const Return = struct {
pub const Truncate = void;
pub const Unlink = void;
pub const UnwatchFile = void;
- pub const Watch = void;
+ pub const Watch = JSC.JSValue;
pub const WatchFile = void;
pub const Utimes = void;
@@ -4181,8 +4180,12 @@ pub const NodeFS = struct {
return Maybe(Return.Lutimes).todo;
}
- pub fn watch(_: *NodeFS, _: Arguments.Watch, comptime _: Flavor) Maybe(Return.Watch) {
- return Maybe(Return.Watch).todo;
+ pub fn watch(_: *NodeFS, args: Arguments.Watch, comptime _: Flavor) Maybe(Return.Watch) {
+ const watcher = args.createFSWatcher() catch |err| {
+ args.global_this.throwError(err, "Failed to watch filename");
+ return Maybe(Return.Watch){ .result = JSC.JSValue.jsUndefined() };
+ };
+ return Maybe(Return.Watch){ .result = watcher };
}
pub fn createReadStream(_: *NodeFS, _: Arguments.CreateReadStream, comptime _: Flavor) Maybe(Return.CreateReadStream) {
return Maybe(Return.CreateReadStream).todo;
diff --git a/src/bun.js/node/node_fs_binding.zig b/src/bun.js/node/node_fs_binding.zig
index 74b769bf6..f178f0355 100644
--- a/src/bun.js/node/node_fs_binding.zig
+++ b/src/bun.js/node/node_fs_binding.zig
@@ -241,6 +241,8 @@ pub const NodeJSFS = struct {
return JSC.Node.Stats.getConstructor(globalThis);
}
+ pub const watch = callSync(.watch);
+
// Not implemented yet:
const notimpl = fdatasync;
pub const opendir = notimpl;
diff --git a/src/bun.js/node/node_fs_watcher.zig b/src/bun.js/node/node_fs_watcher.zig
new file mode 100644
index 000000000..397d51916
--- /dev/null
+++ b/src/bun.js/node/node_fs_watcher.zig
@@ -0,0 +1,913 @@
+const std = @import("std");
+const JSC = @import("root").bun.JSC;
+const bun = @import("root").bun;
+const Fs = @import("../../fs.zig");
+const Path = @import("../../resolver/resolve_path.zig");
+const Encoder = JSC.WebCore.Encoder;
+
+const FSEvents = @import("./fs_events.zig");
+
+const VirtualMachine = JSC.VirtualMachine;
+const EventLoop = JSC.EventLoop;
+const PathLike = JSC.Node.PathLike;
+const ArgumentsSlice = JSC.Node.ArgumentsSlice;
+const Output = bun.Output;
+const string = bun.string;
+const StoredFileDescriptorType = bun.StoredFileDescriptorType;
+const Environment = bun.Environment;
+
+pub const FSWatcher = struct {
+ const watcher = @import("../../watcher.zig");
+ const options = @import("../../options.zig");
+ pub const Watcher = watcher.NewWatcher(*FSWatcher);
+ const log = Output.scoped(.FSWatcher, false);
+
+ pub const ChangeEvent = struct {
+ hash: Watcher.HashType = 0,
+ event_type: FSWatchTask.EventType = .change,
+ time_stamp: i64 = 0,
+ };
+
+ onAccept: std.ArrayHashMapUnmanaged(FSWatcher.Watcher.HashType, bun.BabyList(OnAcceptCallback), bun.ArrayIdentityContext, false) = .{},
+ ctx: *VirtualMachine,
+ js_watcher: ?*JSObject = null,
+ watcher_instance: ?*FSWatcher.Watcher = null,
+ verbose: bool = false,
+ file_paths: bun.BabyList(string) = .{},
+ entry_path: ?string = null,
+ entry_dir: string = "",
+ last_change_event: ChangeEvent = .{},
+
+ pub fn toJS(this: *FSWatcher) JSC.JSValue {
+ return if (this.js_watcher) |js| js.js_this else JSC.JSValue.jsUndefined();
+ }
+
+ pub fn eventLoop(this: FSWatcher) *EventLoop {
+ return this.ctx.eventLoop();
+ }
+
+ pub fn enqueueTaskConcurrent(this: FSWatcher, task: *JSC.ConcurrentTask) void {
+ this.eventLoop().enqueueTaskConcurrent(task);
+ }
+
+ pub fn deinit(this: *FSWatcher) void {
+ while (this.file_paths.popOrNull()) |file_path| {
+ bun.default_allocator.destroy(file_path);
+ }
+ this.file_paths.deinitWithAllocator(bun.default_allocator);
+ if (this.entry_path) |path| {
+ this.entry_path = null;
+ bun.default_allocator.destroy(path);
+ }
+ bun.default_allocator.destroy(this);
+ }
+
+ pub const FSWatchTask = struct {
+ ctx: *FSWatcher,
+ count: u8 = 0,
+
+ entries: [8]Entry = undefined,
+ concurrent_task: JSC.ConcurrentTask = undefined,
+
+ pub const EventType = enum {
+ rename,
+ change,
+ @"error",
+ abort,
+ };
+
+ pub const EventFreeType = enum {
+ destroy,
+ free,
+ none,
+ };
+
+ pub const Entry = struct {
+ file_path: string,
+ event_type: EventType,
+ free_type: EventFreeType,
+ };
+
+ pub fn append(this: *FSWatchTask, file_path: string, event_type: EventType, free_type: EventFreeType) void {
+ if (this.count == 8) {
+ this.enqueue();
+ var ctx = this.ctx;
+ this.* = .{
+ .ctx = ctx,
+ .count = 0,
+ };
+ }
+
+ this.entries[this.count] = .{
+ .file_path = file_path,
+ .event_type = event_type,
+ .free_type = free_type,
+ };
+ this.count += 1;
+ }
+
+ pub fn run(this: *FSWatchTask) void {
+ // this runs on JS Context
+ if (this.ctx.js_watcher) |js_watcher| {
+ for (this.entries[0..this.count]) |entry| {
+ switch (entry.event_type) {
+ .rename => {
+ js_watcher.emit(entry.file_path, "rename");
+ },
+ .change => {
+ js_watcher.emit(entry.file_path, "change");
+ },
+ .@"error" => {
+ // file_path is the error message in this case
+ js_watcher.emitError(entry.file_path);
+ },
+ .abort => {
+ js_watcher.emitIfAborted();
+ },
+ }
+ }
+ }
+ }
+
+ pub fn enqueue(this: *FSWatchTask) void {
+ if (this.count == 0)
+ return;
+
+ var that = bun.default_allocator.create(FSWatchTask) catch unreachable;
+
+ that.* = this.*;
+ this.count = 0;
+ that.concurrent_task.task = JSC.Task.init(that);
+ this.ctx.enqueueTaskConcurrent(&that.concurrent_task);
+ }
+
+ pub fn deinit(this: *FSWatchTask) void {
+ while (this.count > 0) {
+ this.count -= 1;
+ switch (this.entries[this.count].free_type) {
+ .destroy => bun.default_allocator.destroy(this.entries[this.count].file_path),
+ .free => bun.default_allocator.free(this.entries[this.count].file_path),
+ else => {},
+ }
+ }
+ bun.default_allocator.destroy(this);
+ }
+ };
+
+ fn NewCallback(comptime FunctionSignature: type) type {
+ return union(enum) {
+ javascript_callback: JSC.Strong,
+ zig_callback: struct {
+ ptr: *anyopaque,
+ function: *const FunctionSignature,
+ },
+ };
+ }
+
+ pub const OnAcceptCallback = NewCallback(fn (
+ vm: *JSC.VirtualMachine,
+ specifier: []const u8,
+ ) void);
+
+ fn addDirectory(ctx: *FSWatcher, fs_watcher: *FSWatcher.Watcher, fd: StoredFileDescriptorType, file_path: string, recursive: bool, buf: *[bun.MAX_PATH_BYTES + 1]u8, is_entry_path: bool) !void {
+ var dir_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable;
+
+ if (is_entry_path) {
+ ctx.entry_path = dir_path_clone;
+ ctx.entry_dir = dir_path_clone;
+ } else {
+ ctx.file_paths.push(bun.default_allocator, dir_path_clone) catch unreachable;
+ }
+ fs_watcher.addDirectory(fd, dir_path_clone, FSWatcher.Watcher.getHash(file_path), false) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+
+ var iter = (std.fs.IterableDir{ .dir = std.fs.Dir{
+ .fd = fd,
+ } }).iterate();
+
+ while (iter.next() catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ }) |entry| {
+ var parts = [2]string{ dir_path_clone, entry.name };
+ var entry_path = Path.joinAbsStringBuf(
+ Fs.FileSystem.instance.topLevelDirWithoutTrailingSlash(),
+ buf,
+ &parts,
+ .auto,
+ );
+
+ buf[entry_path.len] = 0;
+ var entry_path_z = buf[0..entry_path.len :0];
+
+ var fs_info = fdFromAbsolutePathZ(entry_path_z) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+
+ if (fs_info.is_file) {
+ const file_path_clone = bun.default_allocator.dupeZ(u8, entry_path) catch unreachable;
+
+ ctx.file_paths.push(bun.default_allocator, file_path_clone) catch unreachable;
+
+ fs_watcher.addFile(fs_info.fd, file_path_clone, FSWatcher.Watcher.getHash(entry_path), options.Loader.file, 0, null, false) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+ } else {
+ if (recursive) {
+ addDirectory(ctx, fs_watcher, fs_info.fd, entry_path, recursive, buf, false) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+ }
+ }
+ }
+ }
+
+ pub fn onError(
+ this: *FSWatcher,
+ err: anyerror,
+ ) void {
+ var current_task: FSWatchTask = .{
+ .ctx = this,
+ };
+ current_task.append(@errorName(err), .@"error", .none);
+ current_task.enqueue();
+ }
+
+ pub fn onFSEventUpdate(
+ ctx: ?*anyopaque,
+ path: string,
+ _: bool,
+ is_rename: bool,
+ ) void {
+ const this = bun.cast(*FSWatcher, ctx.?);
+
+ var current_task: FSWatchTask = .{
+ .ctx = this,
+ };
+ defer current_task.enqueue();
+
+ const relative_path = bun.default_allocator.dupe(u8, path) catch unreachable;
+ const event_type: FSWatchTask.EventType = if (is_rename) .rename else .change;
+
+ current_task.append(relative_path, event_type, .destroy);
+ }
+
+ pub fn onFileUpdate(
+ this: *FSWatcher,
+ events: []watcher.WatchEvent,
+ changed_files: []?[:0]u8,
+ watchlist: watcher.Watchlist,
+ ) void {
+ var slice = watchlist.slice();
+ const file_paths = slice.items(.file_path);
+
+ var counts = slice.items(.count);
+ const kinds = slice.items(.kind);
+ var _on_file_update_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+
+ var ctx = this.watcher_instance.?;
+ defer ctx.flushEvictions();
+ defer Output.flush();
+
+ var bundler = if (@TypeOf(this.ctx.bundler) == *bun.Bundler)
+ this.ctx.bundler
+ else
+ &this.ctx.bundler;
+
+ var fs: *Fs.FileSystem = bundler.fs;
+
+ var current_task: FSWatchTask = .{
+ .ctx = this,
+ };
+ defer current_task.enqueue();
+
+ const time_stamp = std.time.milliTimestamp();
+ const time_diff = time_stamp - this.last_change_event.time_stamp;
+
+ for (events) |event| {
+ const file_path = file_paths[event.index];
+ const update_count = counts[event.index] + 1;
+ counts[event.index] = update_count;
+ const kind = kinds[event.index];
+
+ if (comptime Environment.isDebug) {
+ if (this.verbose) {
+ Output.prettyErrorln("[watch] {s} ({s}, {})", .{ file_path, @tagName(kind), event.op });
+ }
+ }
+
+ switch (kind) {
+ .file => {
+ if (event.op.delete) {
+ ctx.removeAtIndex(
+ event.index,
+ 0,
+ &.{},
+ .file,
+ );
+ }
+
+ var file_hash: FSWatcher.Watcher.HashType = FSWatcher.Watcher.getHash(file_path);
+
+ if (event.op.write or event.op.delete or event.op.rename) {
+ const event_type: FSWatchTask.EventType = if (event.op.delete or event.op.rename or event.op.move_to) .rename else .change;
+ // skip consecutive duplicates
+ if ((this.last_change_event.time_stamp == 0 or time_diff > 1) or this.last_change_event.event_type != event_type and this.last_change_event.hash != file_hash) {
+ this.last_change_event.time_stamp = time_stamp;
+ this.last_change_event.event_type = event_type;
+ this.last_change_event.hash = file_hash;
+
+ const relative_slice = fs.relative(this.entry_dir, file_path);
+
+ if (this.verbose)
+ Output.prettyErrorln("<r><d>File changed: {s}<r>", .{relative_slice});
+
+ const relative_path = bun.default_allocator.dupe(u8, relative_slice) catch unreachable;
+
+ current_task.append(relative_path, event_type, .destroy);
+ }
+ }
+ },
+ .directory => {
+ // macOS should use FSEvents for directories
+ if (comptime Environment.isMac) {
+ @panic("Unexpected directory watch");
+ }
+
+ const affected = event.names(changed_files);
+
+ for (affected) |changed_name_| {
+ const changed_name: []const u8 = bun.asByteSlice(changed_name_.?);
+ if (changed_name.len == 0 or changed_name[0] == '~' or changed_name[0] == '.') continue;
+
+ var file_hash: FSWatcher.Watcher.HashType = 0;
+ const relative_slice: string = brk: {
+ var file_path_without_trailing_slash = std.mem.trimRight(u8, file_path, std.fs.path.sep_str);
+
+ @memcpy(_on_file_update_path_buf[0..file_path_without_trailing_slash.len], file_path_without_trailing_slash);
+
+ _on_file_update_path_buf[file_path_without_trailing_slash.len] = std.fs.path.sep;
+
+ @memcpy(_on_file_update_path_buf[file_path_without_trailing_slash.len + 1 ..][0..changed_name.len], changed_name);
+ const path_slice = _on_file_update_path_buf[0 .. file_path_without_trailing_slash.len + changed_name.len + 1];
+ file_hash = FSWatcher.Watcher.getHash(path_slice);
+
+ const relative = fs.relative(this.entry_dir, path_slice);
+
+ break :brk relative;
+ };
+
+ // skip consecutive duplicates
+ const event_type: FSWatchTask.EventType = .rename; // renaming folders, creating folder or files will be always be rename
+ if ((this.last_change_event.time_stamp == 0 or time_diff > 1) or this.last_change_event.event_type != event_type and this.last_change_event.hash != file_hash) {
+ const relative_path = bun.default_allocator.dupe(u8, relative_slice) catch unreachable;
+
+ this.last_change_event.time_stamp = time_stamp;
+ this.last_change_event.event_type = event_type;
+ this.last_change_event.hash = file_hash;
+
+ current_task.append(relative_path, event_type, .destroy);
+
+ if (this.verbose)
+ Output.prettyErrorln("<r> <d>Dir change: {s}<r>", .{relative_path});
+ }
+ }
+
+ if (this.verbose and affected.len == 0) {
+ Output.prettyErrorln("<r> <d>Dir change: {s}<r>", .{fs.relative(this.entry_dir, file_path)});
+ }
+ },
+ }
+ }
+ }
+
+ pub const Arguments = struct {
+ path: PathLike,
+ listener: JSC.JSValue,
+ global_this: JSC.C.JSContextRef,
+ signal: ?*JSC.AbortSignal,
+ persistent: bool,
+ recursive: bool,
+ encoding: JSC.Node.Encoding,
+ verbose: bool,
+ pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Arguments {
+ const vm = ctx.vm();
+ const path = PathLike.fromJS(ctx, arguments, exception) orelse {
+ if (exception.* == null) {
+ JSC.throwInvalidArguments(
+ "filename must be a string or TypedArray",
+ .{},
+ ctx,
+ exception,
+ );
+ }
+ return null;
+ };
+
+ if (exception.* != null) return null;
+ var listener: JSC.JSValue = .zero;
+ var signal: ?*JSC.AbortSignal = null;
+ var persistent: bool = true;
+ var recursive: bool = false;
+ var encoding: JSC.Node.Encoding = .utf8;
+ var verbose = false;
+ if (arguments.nextEat()) |options_or_callable| {
+
+ // options
+ if (options_or_callable.isObject()) {
+ if (options_or_callable.get(ctx, "persistent")) |persistent_| {
+ if (!persistent_.isBoolean()) {
+ JSC.throwInvalidArguments(
+ "persistent must be a boolean.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ persistent = persistent_.toBoolean();
+ }
+
+ if (options_or_callable.get(ctx, "verbose")) |verbose_| {
+ if (!verbose_.isBoolean()) {
+ JSC.throwInvalidArguments(
+ "verbose must be a boolean.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ verbose = verbose_.toBoolean();
+ }
+
+ if (options_or_callable.get(ctx, "encoding")) |encoding_| {
+ if (!encoding_.isString()) {
+ JSC.throwInvalidArguments(
+ "encoding must be a string.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ if (JSC.Node.Encoding.fromJS(encoding_, ctx.ptr())) |node_encoding| {
+ encoding = node_encoding;
+ } else {
+ JSC.throwInvalidArguments(
+ "invalid encoding.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ }
+
+ if (options_or_callable.get(ctx, "recursive")) |recursive_| {
+ if (!recursive_.isBoolean()) {
+ JSC.throwInvalidArguments(
+ "recursive must be a boolean.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ recursive = recursive_.toBoolean();
+ }
+
+ // abort signal
+ if (options_or_callable.get(ctx, "signal")) |signal_| {
+ if (JSC.AbortSignal.fromJS(signal_)) |signal_obj| {
+ //Keep it alive
+ signal_.ensureStillAlive();
+ signal = signal_obj;
+ } else {
+ JSC.throwInvalidArguments(
+ "signal is not of type AbortSignal.",
+ .{},
+ ctx,
+ exception,
+ );
+
+ return null;
+ }
+ }
+
+ // listener
+ if (arguments.nextEat()) |callable| {
+ if (!callable.isCell() or !callable.isCallable(vm)) {
+ exception.* = JSC.toInvalidArguments("Expected \"listener\" callback to be a function", .{}, ctx).asObjectRef();
+ return null;
+ }
+ listener = callable;
+ }
+ } else {
+ if (!options_or_callable.isCell() or !options_or_callable.isCallable(vm)) {
+ exception.* = JSC.toInvalidArguments("Expected \"listener\" callback to be a function", .{}, ctx).asObjectRef();
+ return null;
+ }
+ listener = options_or_callable;
+ }
+ }
+ if (listener == .zero) {
+ exception.* = JSC.toInvalidArguments("Expected \"listener\" callback", .{}, ctx).asObjectRef();
+ return null;
+ }
+
+ return Arguments{
+ .path = path,
+ .listener = listener,
+ .global_this = ctx,
+ .signal = signal,
+ .persistent = persistent,
+ .recursive = recursive,
+ .encoding = encoding,
+ .verbose = verbose,
+ };
+ }
+
+ pub fn createFSWatcher(this: Arguments) !JSC.JSValue {
+ const obj = try FSWatcher.init(this);
+ return obj.toJS();
+ }
+ };
+
+ pub const JSObject = struct {
+ signal: ?*JSC.AbortSignal,
+ persistent: bool,
+ manager: ?*FSWatcher.Watcher,
+ fsevents_watcher: ?*FSEvents.FSEventsWatcher,
+ poll_ref: JSC.PollRef = .{},
+ globalThis: ?*JSC.JSGlobalObject,
+ js_this: JSC.JSValue,
+ encoding: JSC.Node.Encoding,
+ closed: bool,
+
+ pub usingnamespace JSC.Codegen.JSFSWatcher;
+
+ pub fn getFSWatcher(this: *JSObject) *FSWatcher {
+ if (this.manager) |manager| return manager.ctx;
+ if (this.fsevents_watcher) |manager| return bun.cast(*FSWatcher, manager.ctx.?);
+
+ @panic("No context attached to JSFSWatcher");
+ }
+
+ pub fn init(globalThis: *JSC.JSGlobalObject, manager: ?*FSWatcher.Watcher, fsevents_watcher: ?*FSEvents.FSEventsWatcher, signal: ?*JSC.AbortSignal, listener: JSC.JSValue, persistent: bool, encoding: JSC.Node.Encoding) !*JSObject {
+ var obj = try globalThis.allocator().create(JSObject);
+ obj.* = .{
+ .signal = null,
+ .persistent = persistent,
+ .manager = manager,
+ .fsevents_watcher = fsevents_watcher,
+ .globalThis = globalThis,
+ .js_this = .zero,
+ .encoding = encoding,
+ .closed = false,
+ };
+ const instance = obj.getFSWatcher();
+
+ if (persistent) {
+ obj.poll_ref.ref(instance.ctx);
+ }
+
+ var js_this = JSObject.toJS(obj, globalThis);
+ JSObject.listenerSetCached(js_this, globalThis, listener);
+ obj.js_this = js_this;
+ obj.js_this.protect();
+
+ if (signal) |s| {
+
+ // already aborted?
+ if (s.aborted()) {
+ obj.signal = s.ref();
+ // abort next tick
+ var current_task: FSWatchTask = .{
+ .ctx = instance,
+ };
+ current_task.append("", .abort, .none);
+ current_task.enqueue();
+ } else {
+ // watch for abortion
+ obj.signal = s.ref().listen(JSObject, obj, JSObject.emitAbort);
+ }
+ }
+ return obj;
+ }
+
+ pub fn emitIfAborted(this: *JSObject) void {
+ if (this.signal) |s| {
+ if (s.aborted()) {
+ const err = s.abortReason();
+ this.emitAbort(err);
+ }
+ }
+ }
+
+ pub fn emitAbort(this: *JSObject, err: JSC.JSValue) void {
+ if (this.closed) return;
+ defer this.close(true);
+
+ err.ensureStillAlive();
+
+ if (this.globalThis) |globalThis| {
+ if (this.js_this != .zero) {
+ if (JSObject.listenerGetCached(this.js_this)) |listener| {
+ var args = [_]JSC.JSValue{
+ JSC.ZigString.static("error").toValue(globalThis),
+ if (err.isEmptyOrUndefinedOrNull()) JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, globalThis) else err,
+ };
+ _ = listener.callWithGlobalThis(
+ globalThis,
+ &args,
+ );
+ }
+ }
+ }
+ }
+ pub fn emitError(this: *JSObject, err: string) void {
+ if (this.closed) return;
+ defer this.close(true);
+
+ if (this.globalThis) |globalThis| {
+ if (this.js_this != .zero) {
+ if (JSObject.listenerGetCached(this.js_this)) |listener| {
+ var args = [_]JSC.JSValue{
+ JSC.ZigString.static("error").toValue(globalThis),
+ JSC.ZigString.fromUTF8(err).toErrorInstance(globalThis),
+ };
+ _ = listener.callWithGlobalThis(
+ globalThis,
+ &args,
+ );
+ }
+ }
+ }
+ }
+
+ pub fn emit(this: *JSObject, file_name: string, comptime eventType: string) void {
+ if (this.globalThis) |globalThis| {
+ if (this.js_this != .zero) {
+ if (JSObject.listenerGetCached(this.js_this)) |listener| {
+ var filename: JSC.JSValue = JSC.JSValue.jsUndefined();
+ if (file_name.len > 0) {
+ if (this.encoding == .buffer)
+ filename = JSC.ArrayBuffer.createBuffer(globalThis, file_name)
+ else if (this.encoding == .utf8) {
+ filename = JSC.ZigString.fromUTF8(file_name).toValueGC(globalThis);
+ } else {
+ // convert to desired encoding
+ filename = Encoder.toStringAtRuntime(file_name.ptr, file_name.len, globalThis, this.encoding);
+ }
+ }
+ var args = [_]JSC.JSValue{
+ JSC.ZigString.static(eventType).toValue(globalThis),
+ filename,
+ };
+ _ = listener.callWithGlobalThis(
+ globalThis,
+ &args,
+ );
+ }
+ }
+ }
+ }
+
+ pub fn ref(this: *JSObject) void {
+ if (this.closed) return;
+
+ if (!this.persistent) {
+ this.persistent = true;
+ this.poll_ref.ref(this.getFSWatcher().ctx);
+ }
+ }
+
+ pub fn doRef(this: *JSObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ this.ref();
+ return JSC.JSValue.jsUndefined();
+ }
+
+ pub fn unref(this: *JSObject) void {
+ if (this.persistent) {
+ this.persistent = false;
+ this.poll_ref.unref(this.getFSWatcher().ctx);
+ }
+ }
+
+ pub fn doUnref(this: *JSObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ this.unref();
+ return JSC.JSValue.jsUndefined();
+ }
+
+ pub fn hasRef(this: *JSObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ return JSC.JSValue.jsBoolean(this.persistent);
+ }
+
+ pub fn close(
+ this: *JSObject,
+ emitEvent: bool,
+ ) void {
+ if (!this.closed) {
+ if (this.signal) |signal| {
+ this.signal = null;
+ signal.detach(this);
+ }
+ this.closed = true;
+ if (emitEvent) {
+ this.emit("", "close");
+ }
+
+ this.detach();
+ }
+ }
+
+ pub fn detach(this: *JSObject) void {
+ this.unref();
+
+ if (this.js_this != .zero) {
+ this.js_this.unprotect();
+ this.js_this = .zero;
+ }
+
+ this.globalThis = null;
+
+ if (this.signal) |signal| {
+ this.signal = null;
+ signal.detach(this);
+ }
+ if (this.manager) |manager| {
+ var ctx = manager.ctx;
+ this.manager = null;
+ ctx.js_watcher = null;
+ ctx.deinit();
+ manager.deinit(true);
+ }
+
+ if (this.fsevents_watcher) |manager| {
+ var ctx = bun.cast(*FSWatcher, manager.ctx.?);
+ ctx.js_watcher = null;
+ ctx.deinit();
+ manager.deinit();
+ }
+ }
+
+ pub fn doClose(this: *JSObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ this.close(true);
+ return JSC.JSValue.jsUndefined();
+ }
+
+ pub fn finalize(this: *JSObject) callconv(.C) void {
+ if (!this.closed) {
+ this.detach();
+ }
+
+ bun.default_allocator.destroy(this);
+ }
+ };
+
+ const PathResult = struct {
+ fd: StoredFileDescriptorType = 0,
+ is_file: bool = true,
+ };
+
+ fn fdFromAbsolutePathZ(
+ absolute_path_z: [:0]const u8,
+ ) !PathResult {
+ var stat = try bun.C.lstat_absolute(absolute_path_z);
+ var result = PathResult{};
+
+ switch (stat.kind) {
+ .sym_link => {
+ var file = try std.fs.openFileAbsoluteZ(absolute_path_z, .{ .mode = .read_only });
+ result.fd = file.handle;
+ const _stat = try file.stat();
+
+ result.is_file = _stat.kind == .directory;
+ },
+ .directory => {
+ const dir = (try std.fs.openIterableDirAbsoluteZ(absolute_path_z, .{
+ .access_sub_paths = true,
+ })).dir;
+ result.fd = dir.fd;
+ result.is_file = false;
+ },
+ else => {
+ const file = try std.fs.openFileAbsoluteZ(absolute_path_z, .{ .mode = .read_only });
+ result.fd = file.handle;
+ result.is_file = true;
+ },
+ }
+ return result;
+ }
+
+ pub fn init(args: Arguments) !*FSWatcher {
+ var buf: [bun.MAX_PATH_BYTES + 1]u8 = undefined;
+ var slice = args.path.slice();
+ if (bun.strings.startsWith(slice, "file://")) {
+ slice = slice[6..];
+ }
+ var parts = [_]string{
+ slice,
+ };
+
+ var file_path = Path.joinAbsStringBuf(
+ Fs.FileSystem.instance.top_level_dir,
+ &buf,
+ &parts,
+ .auto,
+ );
+
+ buf[file_path.len] = 0;
+ var file_path_z = buf[0..file_path.len :0];
+
+ var fs_type = try fdFromAbsolutePathZ(file_path_z);
+
+ var ctx = try bun.default_allocator.create(FSWatcher);
+ const vm = args.global_this.bunVM();
+ ctx.* = .{
+ .ctx = vm,
+ .verbose = args.verbose,
+ .file_paths = bun.BabyList(string).initCapacity(bun.default_allocator, 1) catch |err| {
+ ctx.deinit();
+ return err;
+ },
+ };
+
+ if (comptime Environment.isMac) {
+ if (!fs_type.is_file) {
+ var dir_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable;
+ ctx.entry_path = dir_path_clone;
+ ctx.entry_dir = dir_path_clone;
+
+ var fsevents_watcher = FSEvents.watch(dir_path_clone, args.recursive, onFSEventUpdate, bun.cast(*anyopaque, ctx)) catch |err| {
+ ctx.deinit();
+ return err;
+ };
+
+ ctx.js_watcher = JSObject.init(args.global_this, null, fsevents_watcher, args.signal, args.listener, args.persistent, args.encoding) catch |err| {
+ ctx.deinit();
+ fsevents_watcher.deinit();
+ return err;
+ };
+
+ return ctx;
+ }
+ }
+
+ var fs_watcher = FSWatcher.Watcher.init(
+ ctx,
+ vm.bundler.fs,
+ bun.default_allocator,
+ ) catch |err| {
+ ctx.deinit();
+ return err;
+ };
+
+ ctx.watcher_instance = fs_watcher;
+
+ if (fs_type.is_file) {
+ var file_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable;
+
+ ctx.entry_path = file_path_clone;
+ ctx.entry_dir = std.fs.path.dirname(file_path_clone) orelse file_path_clone;
+
+ fs_watcher.addFile(fs_type.fd, file_path_clone, FSWatcher.Watcher.getHash(file_path), options.Loader.file, 0, null, false) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+ } else {
+ addDirectory(ctx, fs_watcher, fs_type.fd, file_path, args.recursive, &buf, true) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+ }
+
+ fs_watcher.start() catch |err| {
+ ctx.deinit();
+
+ fs_watcher.deinit(true);
+ return err;
+ };
+
+ ctx.js_watcher = JSObject.init(args.global_this, fs_watcher, null, args.signal, args.listener, args.persistent, args.encoding) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+
+ return ctx;
+ }
+};
diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig
index e2de35706..659ac31bb 100644
--- a/src/bun.js/node/types.zig
+++ b/src/bun.js/node/types.zig
@@ -93,6 +93,10 @@ pub fn Maybe(comptime ResultType: type) type {
return JSC.JSValue.jsUndefined();
}
+ if (comptime ReturnType == JSC.JSValue) {
+ return r;
+ }
+
if (comptime ReturnType == JSC.ArrayBuffer) {
return r.toJS(globalThis, null);
}
diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig
index 5c8221128..061a25eed 100644
--- a/src/bun.js/webcore/encoding.zig
+++ b/src/bun.js/webcore/encoding.zig
@@ -802,7 +802,20 @@ pub const Encoder = struct {
// pub fn writeUTF16AsUTF8(utf16: [*]const u16, len: usize, to: [*]u8, to_len: usize) callconv(.C) i32 {
// return @intCast(i32, strings.copyUTF16IntoUTF8(to[0..to_len], []const u16, utf16[0..len], true).written);
// }
-
+ pub fn toStringAtRuntime(input: [*]const u8, len: usize, globalObject: *JSGlobalObject, encoding: JSC.Node.Encoding) JSValue {
+ return switch (encoding) {
+ .ucs2 => toString(input, len, globalObject, .utf16le),
+ .utf16le => toString(input, len, globalObject, .utf16le),
+ .utf8 => toString(input, len, globalObject, .utf8),
+ .ascii => toString(input, len, globalObject, .ascii),
+ .hex => toString(input, len, globalObject, .hex),
+ .base64 => toString(input, len, globalObject, .base64),
+ .base64url => toString(input, len, globalObject, .base64url),
+ .latin1 => toString(input, len, globalObject, .latin1),
+ // treat everything else as utf8
+ else => toString(input, len, globalObject, .utf8),
+ };
+ }
pub fn toString(input_ptr: [*]const u8, len: usize, global: *JSGlobalObject, comptime encoding: JSC.Node.Encoding) JSValue {
if (len == 0)
return ZigString.Empty.toValue(global);