-
-
Notifications
You must be signed in to change notification settings - Fork 476
Description
Summary
When composing the NodeFileSystem.layer with the ParcelWatcher layer (or any WatchBackend provider), the layer ordering is critical but non-obvious. Providing layers in the wrong order silently falls back to Node.js's built-in fs.watch which is not recursive, instead of using the Parcel watcher.
Reproduction
import { Effect, Layer, Stream } from 'effect'
import { FileSystem } from '@effect/platform'
import { NodeFileSystem } from '@effect/platform-node'
import { layer as ParcelWatcherLayer } from '@effect/platform-node/NodeFileSystem/ParcelWatcher'
const program = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem
const watchStream = fs.watch('/some/directory')
yield* Stream.runForEach(watchStream, (event) => Effect.log(event))
})
// ❌ WRONG: This silently uses fs.watch (non-recursive) instead of Parcel watcher
await program.pipe(
Effect.provide(ParcelWatcherLayer),
Effect.provide(NodeFileSystem.layer),
Effect.runPromise
)
// ✅ CORRECT: Using Layer.provideMerge ensures WatchBackend is available
const layer = NodeFileSystem.layer.pipe(Layer.provideMerge(ParcelWatcherLayer))
await program.pipe(
Effect.provide(layer),
Effect.runPromise
)Root Cause
The NodeFileSystem.layer uses Effect.serviceOption(FileSystem.WatchBackend) at construction time:
const makeFileSystem = Effect.map(
Effect.serviceOption(FileSystem.WatchBackend),
backend => FileSystem.make({
// ...
watch(path, options) {
return watch(backend, path, options) // Falls back to fs.watch if backend is None
},
})
)When using chained Effect.provide calls, the outer layer is constructed first. So:
Effect.provide(ParcelWatcherLayer)(inner) - constructed SECONDEffect.provide(NodeFileSystem.layer)(outer) - constructed FIRST, but WatchBackend isn't available yet!
Impact
This is a subtle bug that's hard to diagnose:
- Silent fallback: No errors or warnings - code appears to work
- Behavioral difference:
fs.watchis not recursive by default, so nested file changes are missed - Platform differences:
fs.watchhas varying behavior across platforms
Suggestions
Option 1: Documentation warning
Add prominent documentation about layer ordering requirements when using WatchBackend.
Option 2: Runtime warning (preferred)
When NodeFileSystem.layer is constructed without a WatchBackend service available, log a warning suggesting the user check their layer composition:
const makeFileSystem = Effect.map(
Effect.serviceOption(FileSystem.WatchBackend),
backend => {
if (Option.isNone(backend)) {
// Could log a warning here about fallback behavior
}
return FileSystem.make({ /* ... */ })
}
)Option 3: Lint rule
The Effect LSP could warn when it detects chained Effect.provide calls with layers that have construction-time dependencies on each other.
Note: The Effect LSP already has a multipleEffectProvide warning, but it doesn't specifically catch this dependency ordering issue.
Environment
- Effect version: 3.19.x
- @effect/platform-node version: 0.98.x
- Platform: macOS (darwin arm64)