Skip to content

Commit 6c2ddc1

Browse files
committed
feat(utils): add path utilities, event kind resolver, and option validation
- Add baseName and normalize static methods for path handling - Add resolveEventKind to map Deno FsEvent kinds to EventKind - Add validateOptions with field-level validation for all WatcherOptions
1 parent 011588d commit 6c2ddc1

1 file changed

Lines changed: 104 additions & 0 deletions

File tree

src/utils.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import type * as Types from '@app/types.ts'
2+
3+
export class Utils {
4+
private static readonly validEventKinds = new Set<Types.EventKind>([
5+
'access',
6+
'create',
7+
'modify',
8+
'remove'
9+
])
10+
static baseName(filePath: string): string {
11+
return filePath.slice(filePath.lastIndexOf('/') + 1)
12+
}
13+
14+
static normalize(filePath: string): string {
15+
if (filePath === '') {
16+
return ''
17+
}
18+
const isNetworkPath = filePath.startsWith('\\\\') || filePath.startsWith('//')
19+
let normalized = filePath.replaceAll('\\', '/')
20+
normalized = normalized.replace(/\/+/g, '/')
21+
if (isNetworkPath && !normalized.startsWith('//')) {
22+
normalized = `/${normalized}`
23+
}
24+
return normalized
25+
}
26+
27+
static resolveEventKind(raw: string): Types.EventKind | null {
28+
if (raw === 'access' || raw === 'other') {
29+
return null
30+
}
31+
if (Utils.validEventKinds.has(raw as Types.EventKind)) {
32+
return raw as Types.EventKind
33+
}
34+
return 'modify'
35+
}
36+
37+
static validateOptions(options: Types.WatcherOptions): void {
38+
if (typeof options !== 'object' || options === null) {
39+
throw new TypeError('options must be an object')
40+
}
41+
Utils.validatePath(options.path)
42+
Utils.validateDebounceMs(options.debounceMs)
43+
if (typeof options.onChange !== 'function') {
44+
throw new TypeError('onChange must be a function')
45+
}
46+
if (options.recursive !== undefined && typeof options.recursive !== 'boolean') {
47+
throw new TypeError('recursive must be a boolean')
48+
}
49+
if (options.ignore !== undefined) {
50+
Utils.validateIgnore(options.ignore)
51+
}
52+
if (options.writeStable !== undefined) {
53+
Utils.validateWriteStable(options.writeStable)
54+
}
55+
}
56+
57+
private static validateDebounceMs(debounceMs: unknown): void {
58+
if (typeof debounceMs !== 'number' || !Number.isFinite(debounceMs) || debounceMs < 0) {
59+
throw new TypeError('debounceMs must be a finite number >= 0')
60+
}
61+
}
62+
63+
private static validateIgnore(ignore: unknown): void {
64+
if (!Array.isArray(ignore)) {
65+
throw new TypeError('ignore must be an array')
66+
}
67+
for (let i = 0; i < ignore.length; i++) {
68+
const matcher = ignore[i]
69+
const isValid = typeof matcher === 'string' ||
70+
matcher instanceof RegExp ||
71+
typeof matcher === 'function'
72+
if (!isValid) {
73+
throw new TypeError(`ignore[${i}] must be a string, RegExp, or function`)
74+
}
75+
}
76+
}
77+
78+
private static validatePath(path: unknown): void {
79+
if (Array.isArray(path)) {
80+
for (let i = 0; i < path.length; i++) {
81+
if (typeof path[i] !== 'string' || (path[i] as string).trim() === '') {
82+
throw new TypeError(`path[${i}] must be a non-empty string`)
83+
}
84+
}
85+
return
86+
}
87+
if (typeof path !== 'string' || path.trim() === '') {
88+
throw new TypeError('path must be a non-empty string or string[]')
89+
}
90+
}
91+
92+
private static validateWriteStable(writeStable: unknown): void {
93+
if (typeof writeStable !== 'object' || writeStable === null) {
94+
throw new TypeError('writeStable must be an object')
95+
}
96+
const { threshold, interval } = writeStable as Types.WriteStable
97+
if (typeof threshold !== 'number' || !Number.isFinite(threshold) || threshold <= 0) {
98+
throw new TypeError('writeStable.threshold must be a finite number > 0')
99+
}
100+
if (typeof interval !== 'number' || !Number.isFinite(interval) || interval <= 0) {
101+
throw new TypeError('writeStable.interval must be a finite number > 0')
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)