Skip to content

Commit 47bdda8

Browse files
committed
docs(readme): add project README and API documentation
- Add README with features, installation, and testing instructions - Add docs index with quick reference table of all exports - Add Superwatcher API docs with options, types, and usage examples
1 parent 7f2dfc9 commit 47bdda8

3 files changed

Lines changed: 321 additions & 0 deletions

File tree

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<div align='center'>
2+
3+
# Superwatcher
4+
5+
Zero-dependency Deno file watcher with debounced batch event callbacks
6+
7+
[![Deno](https://img.shields.io/badge/deno-%3E%3D2.5.4-ffcb00?logo=deno&logoColor=000000)](https://deno.com) [![Module type: Deno/ESM](https://img.shields.io/badge/module%20type-deno%2Fesm-brightgreen)](https://github.com/NeaByteLab/Superwatcher) [![JSR](https://jsr.io/badges/@neabyte/superwatcher)](https://jsr.io/@neabyte/superwatcher) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8+
9+
</div>
10+
11+
## Features
12+
13+
- **Zero dependencies** - No external packages, built entirely on Deno native file system APIs.
14+
- **Debounced batch events** - Groups rapid file changes into one batched callback per flush cycle.
15+
- **Ignore filters** - Skips unwanted paths using string suffix, RegExp, or custom function matchers.
16+
- **Write stability** - Delays event emission until file size stops changing after large writes.
17+
- **Atomic write detection** - Collapses delete-then-recreate within debounce window into single modify event.
18+
- **Multi-path support** - Watches any combination of directories and individual files in one instance.
19+
- **Recursive control** - Disables subdirectory watching when `recursive: false` is set on directories.
20+
- **Error isolation** - Catches thrown errors inside `onChange` callback without stopping the watcher.
21+
22+
## Installation
23+
24+
> [!NOTE]
25+
> **Prerequisites:** Deno >= 2.5.4 (install from [deno.com](https://deno.com/)).
26+
27+
**Deno (JSR):**
28+
29+
```bash
30+
deno add jsr:@neabyte/superwatcher
31+
```
32+
33+
Read [docs/README.md](docs/README.md) for full documentation.
34+
35+
## Testing
36+
37+
**Type check** - format, lint, and type-check:
38+
39+
```bash
40+
deno task check
41+
```
42+
43+
**Unit tests** - format/lint tests and run all tests:
44+
45+
```bash
46+
deno task test
47+
```
48+
49+
- Tests live under `tests/` (utils and watcher tests).
50+
- The test task uses `--allow-read`, `--allow-write`, and `--allow-env`.
51+
52+
## License
53+
54+
This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for details.

docs/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Documentation
2+
3+
Complete API documentation for `@neabyte/superwatcher`.
4+
5+
## Modules
6+
7+
**[Superwatcher](Superwatcher.md)** - File watcher with debounced batch events, ignore filters, and write stability.
8+
9+
## Quick Reference
10+
11+
| Export | Purpose | Usage |
12+
| ---------------- | ------------------------------ | ----------------------------------------- |
13+
| `Superwatcher` | File/directory watcher class | `new Superwatcher(options)` |
14+
| `EventKind` | Event type union | `'create' \| 'modify' \| 'remove'` |
15+
| `WatchEvent` | Single file change event | `{ kind: EventKind, path: string }` |
16+
| `WatcherOptions` | Constructor options | `{ path, debounceMs, onChange, ... }` |
17+
| `WriteStable` | Write stability polling config | `{ threshold: number, interval: number }` |
18+
| `IgnoreMatcher` | Ignore filter type | `string \| RegExp \| (path) => boolean` |

docs/Superwatcher.md

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# Superwatcher
2+
3+
File watcher with debounced batch events, ignore filters, and write stability polling.
4+
5+
## Table of Contents
6+
7+
- [Quick Start](#quick-start)
8+
- [Creating a Watcher](#creating-a-watcher)
9+
- [API Reference](#api-reference)
10+
- [Types](#types)
11+
12+
## Quick Start
13+
14+
```typescript
15+
import { Superwatcher } from '@neabyte/superwatcher'
16+
17+
const watcher = new Superwatcher({
18+
path: './src',
19+
debounceMs: 150,
20+
onChange: events => {
21+
for (const e of events) {
22+
console.log(`[${e.kind}] ${e.path}`)
23+
}
24+
}
25+
})
26+
27+
watcher.start()
28+
```
29+
30+
## Creating a Watcher
31+
32+
### `new Superwatcher(options: WatcherOptions)`
33+
34+
Creates a new watcher instance. All options are validated at construction and throw a descriptive `TypeError` on invalid input.
35+
36+
```typescript
37+
// Watch a directory
38+
const dirWatcher = new Superwatcher({
39+
path: './data',
40+
debounceMs: 100,
41+
onChange: events => console.log(events)
42+
})
43+
44+
// Watch a specific file
45+
const fileWatcher = new Superwatcher({
46+
path: './config.json',
47+
debounceMs: 50,
48+
onChange: events => console.log(events)
49+
})
50+
51+
// Watch multiple paths
52+
const multiWatcher = new Superwatcher({
53+
path: ['./src', './tests', './config.json'],
54+
debounceMs: 150,
55+
onChange: events => console.log(events)
56+
})
57+
```
58+
59+
### `WatcherOptions`
60+
61+
| Option | Type | Default | Description |
62+
| ------------- | -------------------------------- | ------- | ------------------------------------------------------------------ |
63+
| `path` | `string \| string[]` || File or directory path(s) to watch. Must exist at construction. |
64+
| `debounceMs` | `number` || Milliseconds to wait before flushing batched events. Must be >= 0. |
65+
| `onChange` | `(events: WatchEvent[]) => void` || Callback invoked with batched events after debounce. |
66+
| `recursive` | `boolean` | `true` | Watch subdirectories. Ignored when watching specific files. |
67+
| `ignore` | `IgnoreMatcher[]` || Array of matchers to filter out unwanted paths. |
68+
| `writeStable` | `WriteStable` || Poll file size to wait for writes to finish before emitting. |
69+
70+
### With Ignore Filters
71+
72+
Ignore matchers support three forms: string suffix, RegExp, or custom function.
73+
74+
```typescript
75+
const watcher = new Superwatcher({
76+
path: './project',
77+
debounceMs: 100,
78+
ignore: ['.tmp', /\.lock$/, path => path.includes('node_modules')],
79+
onChange: events => console.log(events)
80+
})
81+
```
82+
83+
> [!NOTE]
84+
> String matchers check both `endsWith` and exact filename match. For example, `'.tmp'` ignores both `/data/cache.tmp` and a file literally named `.tmp`.
85+
86+
### With Write Stability
87+
88+
For large file uploads or slow writes, `writeStable` delays the event until the file size stops changing.
89+
90+
```typescript
91+
const watcher = new Superwatcher({
92+
path: './uploads',
93+
debounceMs: 100,
94+
writeStable: {
95+
threshold: 500,
96+
interval: 50
97+
},
98+
onChange: events => {
99+
// Events fire only after file size is stable for 500ms
100+
for (const e of events) {
101+
console.log('Upload complete:', e.path)
102+
}
103+
}
104+
})
105+
```
106+
107+
### `WriteStable`
108+
109+
| Option | Type | Description |
110+
| ----------- | -------- | ----------------------------------------------------------------- |
111+
| `threshold` | `number` | Milliseconds the file size must remain unchanged. Must be > 0. |
112+
| `interval` | `number` | Polling interval in milliseconds to check file size. Must be > 0. |
113+
114+
> [!NOTE]
115+
> Write stability only applies to `create` and `modify` events. Remove events are emitted immediately without polling.
116+
117+
### Non-Recursive Watching
118+
119+
Disable subdirectory watching to only observe top-level changes.
120+
121+
```typescript
122+
const watcher = new Superwatcher({
123+
path: './config',
124+
recursive: false,
125+
debounceMs: 50,
126+
onChange: events => console.log(events)
127+
})
128+
```
129+
130+
## API Reference
131+
132+
### `start(): void`
133+
134+
Begin watching the configured path(s). Safe to call multiple times — each call disposes the previous watcher before starting a new one.
135+
136+
```typescript
137+
const watcher = new Superwatcher({
138+
path: './src',
139+
debounceMs: 100,
140+
onChange: events => console.log(events)
141+
})
142+
143+
watcher.start()
144+
```
145+
146+
> [!NOTE]
147+
> If the watcher was constructed with an empty path array, `start()` is a no-op.
148+
149+
### `dispose(): void`
150+
151+
Stop watching and clean up all internal timers and resources. Safe to call multiple times or without calling `start()` first.
152+
153+
```typescript
154+
const watcher = new Superwatcher({
155+
path: './src',
156+
debounceMs: 100,
157+
onChange: events => console.log(events)
158+
})
159+
160+
watcher.start()
161+
162+
// Later: clean up
163+
watcher.dispose()
164+
```
165+
166+
A common pattern for graceful shutdown:
167+
168+
```typescript
169+
Deno.addSignalListener('SIGINT', () => {
170+
watcher.dispose()
171+
Deno.exit(0)
172+
})
173+
```
174+
175+
## Types
176+
177+
### `WatchEvent`
178+
179+
Represents a single file system change event.
180+
181+
```typescript
182+
interface WatchEvent {
183+
readonly kind: EventKind
184+
readonly path: string
185+
}
186+
```
187+
188+
### `EventKind`
189+
190+
The type of file system change.
191+
192+
```typescript
193+
type EventKind = 'access' | 'create' | 'modify' | 'remove'
194+
```
195+
196+
| Kind | Description |
197+
| -------- | ----------------------------------- |
198+
| `create` | A new file or directory was created |
199+
| `modify` | An existing file was modified |
200+
| `remove` | A file or directory was deleted |
201+
202+
> [!NOTE]
203+
> The `access` kind is defined in the type but never emitted by the watcher. Deno `access` and `other` events are filtered out internally.
204+
205+
### `IgnoreMatcher`
206+
207+
A filter to exclude paths from events. Three forms are supported:
208+
209+
```typescript
210+
type IgnoreMatcher = string | RegExp | ((path: string) => boolean)
211+
```
212+
213+
| Form | Behavior |
214+
| ---------- | ------------------------------------------------------------------ |
215+
| `string` | Matches if the path ends with the string or the filename equals it |
216+
| `RegExp` | Matches if `regex.test(path)` returns true |
217+
| `function` | Matches if the function returns true for the path |
218+
219+
### Atomic Write Detection
220+
221+
When a file is deleted and recreated within the same debounce window (common in atomic save operations), the watcher resolves the event as `modify` instead of emitting a `remove` followed by a `create`.
222+
223+
```typescript
224+
// Editor saves file atomically:
225+
// 1. Delete original
226+
// 2. Write new file
227+
228+
// Watcher emits:
229+
// [{ kind: 'modify', path: '/project/file.ts' }]
230+
// Not:
231+
// [{ kind: 'remove', ... }, { kind: 'create', ... }]
232+
```
233+
234+
### Error Isolation
235+
236+
Errors thrown inside `onChange` are silently caught. The watcher continues operating normally.
237+
238+
```typescript
239+
const watcher = new Superwatcher({
240+
path: './src',
241+
debounceMs: 50,
242+
onChange: () => {
243+
throw new Error('handler crashed')
244+
}
245+
})
246+
247+
watcher.start()
248+
// Watcher keeps running despite the error
249+
```

0 commit comments

Comments
 (0)