diff --git a/.changes/fs-watch-event-format.md b/.changes/fs-watch-event-format.md new file mode 100644 index 000000000..c1d498df0 --- /dev/null +++ b/.changes/fs-watch-event-format.md @@ -0,0 +1,6 @@ +--- +"fs": major +"fs-js": major +--- + +**Breaking:** The `notify` crate's `serialization-compat-6` feature is no longer enabled, so `WatchEvent` now follows notify v8's format: the event kind is flattened into the event, with `type` holding the top-level kind (`any`, `access`, `create`, `modify`, `remove` or `other`) and `kind`/`mode` refining it. For instance `{ type: { modify: { kind: 'data', mode: 'content' } } }` is now `{ type: 'modify', kind: 'data', mode: 'content' }`, and `attrs.flag` is `rescan` instead of `Rescan`. `WatchEvent.attrs` is now typed as `WatchEventAttributes`. diff --git a/plugins/fs/Cargo.toml b/plugins/fs/Cargo.toml index 79b92330b..ae78d4016 100644 --- a/plugins/fs/Cargo.toml +++ b/plugins/fs/Cargo.toml @@ -38,11 +38,7 @@ url = { workspace = true } log = { workspace = true } anyhow = "1" glob = { workspace = true } -# TODO: Remove `serialization-compat-6` in v3 -notify = { version = "8", optional = true, features = [ - "serde", - "serialization-compat-6", -] } +notify = { version = "8", optional = true, features = ["serde"] } notify-debouncer-full = { version = "0.6", optional = true } dunce = { workspace = true } percent-encoding = "2" diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts index 9d8aceff8..cf0a8182e 100644 --- a/plugins/fs/guest-js/index.ts +++ b/plugins/fs/guest-js/index.ts @@ -1184,24 +1184,57 @@ interface DebouncedWatchOptions extends WatchOptions { } /** + * Additional attributes of a {@linkcode WatchEvent}. + * + * @since 3.0.0 + */ +interface WatchEventAttributes { + /** Tracker ID that groups related events, e.g. both sides of a rename. */ + tracker?: number + /** + * `rescan` means some events may have been missed, so any file or folder might have been modified. + */ + flag?: 'rescan' + /** Short string identifying the details of an `other` event. */ + info?: string + /** Short string identifying the backend that generated the event. */ + source?: string +} + +/** + * A file system event. + * + * The event kind is flattened into the event: `type` is the top-level kind and, + * for `access`, `create`, `modify` and `remove` events, `kind` (and `mode` when available) + * refines it. + * + * @example + * ```typescript + * import { watch } from '@tauri-apps/plugin-fs'; + * await watch('/path/to/file', (event) => { + * if (event.type === 'modify' && event.kind === 'data') { + * console.log('data changed', event.paths, event.mode); + * } + * }); + * ``` + * * @since 2.0.0 */ -interface WatchEvent { - type: WatchEventKind +type WatchEvent = WatchEventKind & { paths: string[] - attrs: unknown + attrs: WatchEventAttributes } /** * @since 2.0.0 */ type WatchEventKind = - | 'any' - | { access: WatchEventKindAccess } - | { create: WatchEventKindCreate } - | { modify: WatchEventKindModify } - | { remove: WatchEventKindRemove } - | 'other' + | { type: 'any' } + | ({ type: 'access' } & WatchEventKindAccess) + | ({ type: 'create' } & WatchEventKindCreate) + | ({ type: 'modify' } & WatchEventKindModify) + | ({ type: 'remove' } & WatchEventKindRemove) + | { type: 'other' } /** * @since 2.0.0 @@ -1437,6 +1470,7 @@ export type { WatchOptions, DebouncedWatchOptions, WatchEvent, + WatchEventAttributes, WatchEventKind, WatchEventKindAccess, WatchEventKindCreate, diff --git a/plugins/fs/src/watcher.rs b/plugins/fs/src/watcher.rs index de9a85d31..8d5b1d5fc 100644 --- a/plugins/fs/src/watcher.rs +++ b/plugins/fs/src/watcher.rs @@ -102,3 +102,34 @@ pub fn watch( Ok(rid) } + +#[cfg(test)] +mod tests { + use notify::{ + event::{DataChange, ModifyKind}, + Event, EventKind, + }; + + // the `WatchEvent` type of the JavaScript API relies on this format + #[test] + fn event_kind_is_flattened_into_the_event() { + let event = Event::new(EventKind::Modify(ModifyKind::Data(DataChange::Content))) + .add_path("/tmp/file".into()); + assert_eq!( + serde_json::to_value(event).unwrap(), + serde_json::json!({ + "type": "modify", + "kind": "data", + "mode": "content", + "paths": ["/tmp/file"], + "attrs": {} + }) + ); + + let event = Event::new(EventKind::Any); + assert_eq!( + serde_json::to_value(event).unwrap(), + serde_json::json!({ "type": "any", "paths": [], "attrs": {} }) + ); + } +}