fix(watcher): restore ALS context in native watcher callback

The @parcel/watcher callback fires from a native C++ uv_async handle
which does not preserve Node.js AsyncLocalStorage context. Wrap the
callback in Instance.provide to restore the instance ALS context so
Bus.publish can resolve instance-scoped state and directory.
This commit is contained in:
Kit Langton
2026-03-15 09:39:13 -04:00
parent c35dc2245a
commit 2cbd2a5a2d

View File

@@ -1,6 +1,7 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { InstanceContext } from "@/effect/instances"
import { Instance } from "@/project/instance"
import z from "zod"
import { Log } from "../util/log"
import { FileIgnore } from "./ignore"
@@ -88,13 +89,21 @@ export class FileWatcherService extends ServiceMap.Service<FileWatcherService, F
const subs: ParcelWatcher.AsyncSubscription[] = []
yield* Effect.addFinalizer(() => Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))))
const directory = instance.directory
const cb: ParcelWatcher.SubscribeCallback = (err, evts) => {
if (err) return
for (const evt of evts) {
if (evt.type === "create") Bus.publish(event.Updated, { file: evt.path, event: "add" })
if (evt.type === "update") Bus.publish(event.Updated, { file: evt.path, event: "change" })
if (evt.type === "delete") Bus.publish(event.Updated, { file: evt.path, event: "unlink" })
}
// Instance.provide restores ALS context since native watcher callbacks
// fire outside the Instance async context
Instance.provide({
directory,
fn: () => {
for (const evt of evts) {
if (evt.type === "create") Bus.publish(event.Updated, { file: evt.path, event: "add" })
if (evt.type === "update") Bus.publish(event.Updated, { file: evt.path, event: "change" })
if (evt.type === "delete") Bus.publish(event.Updated, { file: evt.path, event: "unlink" })
}
},
})
}
const subscribe = (dir: string, ignore: string[]) =>