import * as T from '@effect-ts/core/Effect' ; import * as Ex from '@effect-ts/core/Effect/Exit' ; import * as S from '@effect-ts/core/Effect/Experimental/Stream' ; import * as H from '@effect-ts/core/Effect/Hub' ; import * as M from '@effect-ts/core/Effect/Managed' ; import * as Q from '@effect-ts/core/Effect/Queue' ; import * as Ref from '@effect-ts/core/Effect/Ref' ; import * as E from '@effect-ts/core/Either' ; import { pipe } from '@effect-ts/core/Function' ; import * as O from '@effect-ts/core/Option' ; import * as Chokidar from 'chokidar' ; import type fs from 'fs' ; import { Tagged } from '../effect/index.js' ; import type { UnknownFilePath } from '../file-paths.js' ; import { unknownFilePath } from '../file-paths.js' ; export class FileAdded { readonly _tag = 'FileAdded' ; constructor ( public path , public stats ) { } } export class FileRemoved { readonly _tag = 'FileRemoved' ; constructor ( public path , public stats ) { } } export class FileChanged { readonly _tag = 'FileChanged' ; constructor ( public path , public stats ) { } } export class DirectoryAdded { readonly _tag = 'DirectoryAdded' ; constructor ( public path , public stats ) { } } export class DirectoryRemoved { readonly _tag = 'DirectoryRemoved' ; constructor ( public path , public stats ) { } } export type FileSystemEvent = FileAdded | FileRemoved | FileChanged | DirectoryAdded | DirectoryRemoved ; export const FileWatcherTypeId = Symbol ( ) ; export type FileWatcherTypeId = typeof FileWatcherTypeId ; export abstract class FileWatcher { readonly [ FileWatcherTypeId ] : FileWatcherTypeId = FileWatcherTypeId ; } abstract class FileWatcherInternal extends FileWatcher { readonly [ FileWatcherTypeId ] : FileWatcherTypeId = FileWatcherTypeId ; abstract shutdown ( ) ; abstract subscribe ( ) ; abstract add ( paths ) ; abstract remove ( paths ) ; } export const WatchErrorTypeId = Symbol ( ) ; export type WatchErrorTypeId = typeof WatchErrorTypeId ; export class FileWatcherError extends Tagged ( 'FileWatcherError' ) < { readonly origin : O . Option < unknown > ; } > { readonly [ WatchErrorTypeId ] : WatchErrorTypeId = WatchErrorTypeId ; } class ConcreteFileWatcher extends FileWatcherInternal { readonly [ FileWatcherTypeId ] : FileWatcherTypeId = FileWatcherTypeId ; constructor ( public instance , private fsEventsHub , public readonly paths , public readonly options ? ) { super ( ) ; } shutdown ( ) { return pipe ( this . instance , Ref . get , T . chain ( ( _ ) => T . tryPromise ( ( ) => _ . close ( ) ) ) , T . catchAll ( ( _ ) => T . unit ) ) ; } add ( paths ) { return pipe ( this . instance , Ref . get , T . chain ( ( _ ) => T . succeedWith ( ( ) => { _ . add ( paths ) ; } ) ) ) ; } remove ( paths ) { return pipe ( this . instance , Ref . get , T . chain ( ( _ ) => T . succeedWith ( ( ) => { _ . unwatch ( paths ) ; } ) ) ) ; } subscribeToEvents ( ) { return pipe ( this . instance , Ref . get , T . chain ( ( _ ) => T . succeedWith ( ( ) => { _ . on ( 'error' , ( error ) => { T . run ( H . publish_ ( this . fsEventsHub , Ex . succeed ( E . left ( new FileWatcherError ( { origin : O . some ( error ) } ) ) ) ) ) ; } ) ; _ . on ( 'all' , ( eventName , path , stats ) => { switch ( eventName ) { case 'add' : T . run ( H . publish_ ( this . fsEventsHub , Ex . succeed ( E . right ( new FileAdded ( unknownFilePath ( path ) , O . fromNullable ( stats ) ) ) ) ) ) ; break ; case 'unlink' : T . run ( H . publish_ ( this . fsEventsHub , Ex . succeed ( E . right ( new FileRemoved ( unknownFilePath ( path ) , O . fromNullable ( stats ) ) ) ) ) ) ; break ; case 'change' : T . run ( H . publish_ ( this . fsEventsHub , Ex . succeed ( E . right ( new FileChanged ( unknownFilePath ( path ) , O . fromNullable ( stats ) ) ) ) ) ) ; break ; case 'addDir' : T . run ( H . publish_ ( this . fsEventsHub , Ex . succeed ( E . right ( new DirectoryAdded ( unknownFilePath ( path ) , O . fromNullable ( stats ) ) ) ) ) ) ; break ; case 'unlinkDir' : T . run ( H . publish_ ( this . fsEventsHub , Ex . succeed ( E . right ( new DirectoryRemoved ( unknownFilePath ( path ) , O . fromNullable ( stats ) ) ) ) ) ) ; break ; } } ) ; } ) ) ) ; } subscribe ( ) { return pipe ( H . subscribe ( this . fsEventsHub ) , M . chain ( ( _ ) => M . ensuringFirst_ ( M . succeed ( S . fromQueue ( ) ( _ ) ) , Q . shutdown ( _ ) ) ) , M . map ( S . flattenExit ) ) ; } } function concrete ( fileWatcher ) { } export function makeUnsafe ( paths , options ? ) { const instance = Ref . unsafeMakeRef < Chokidar . FSWatcher > ( Chokidar . watch ( paths , options ) ) ; const hub = H . unsafeMakeUnbounded < Ex . Exit < never , E . Either < FileWatcherError , FileSystemEvent > > > ( ) ; return new ConcreteFileWatcher ( instance , hub , paths , options ) ; } export function make ( paths , options ? ) { return pipe ( T . succeedWith ( ( ) => Chokidar . watch ( paths , options ) ) , T . chain ( ( _ ) => Ref . makeRef < Chokidar . FSWatcher > ( _ ) ) , T . zip ( H . makeUnbounded < Ex . Exit < never , E . Either < FileWatcherError , FileSystemEvent > > > ( ) ) , T . chain ( ( { tuple : [ instance , hub ] } ) => T . succeedWith ( ( ) => new ConcreteFileWatcher ( instance , hub , paths , options ) ) ) , T . tap ( ( _ ) => _ . subscribeToEvents ( ) ) ) ; } export const makeAndSubscribe = ( paths , options ? ) : S . Stream < unknown , never , E . Either < FileWatcherError , FileSystemEvent > > => pipe ( M . make_ ( make ( paths , options ) , shutdown ) , M . chain ( subscribe ) , S . unwrapManaged ) ; export function subscribe ( self ) { concrete ( self ) ; return self . subscribe ( ) ; } export function add_ ( self , paths ) { concrete ( self ) ; return self . add ( paths ) ; } export function add ( paths ) { return ( self ) => add_ ( self , paths ) ; } export function remove_ ( self , paths ) { concrete ( self ) ; return self . remove ( paths ) ; } export function remove ( paths ) { return ( self ) => remove_ ( self , paths ) ; } export function shutdown ( self ) { concrete ( self ) ; return self . shutdown ( ) ; }