Skip to content

Commit 10f347b

Browse files
committed
Fix TypeScript type issues and improve type safety
- Fixed type annotations for all generator functions to properly return ElementFn - Reordered function overloads to prioritize generator mode for better type inference - Fixed Workflow type issues by properly instantiating async generators - Added support for arrays in removeAttr function alongside spread arguments - Fixed style function to accept CSS custom properties with Record<string, string> - Resolved type casting issues with proper unknown intermediate casting - Fixed async generator returns to match Workflow<T> type definition - Cleaned up unused imports to eliminate warnings All library source code now compiles without TypeScript errors. Test files have separate issues that need to be addressed by properly yielding functions in generator contexts.
1 parent 782f189 commit 10f347b

15 files changed

Lines changed: 1915 additions & 631 deletions

CODE_REVIEW.md

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
# Code Review - watch-selector Library
2+
3+
## Executive Summary
4+
5+
After a comprehensive review of the watch-selector library, I've identified several areas of excellence and some opportunities for improvement. The library is **well-architected** and **production-ready** with a 97% test pass rate, but there are some correctness issues, documentation gaps, and potential memory management concerns that should be addressed.
6+
7+
## 🟢 Strengths
8+
9+
### 1. Architecture & Design
10+
- **Excellent dual API pattern**: Successfully supports 5 different usage modes
11+
- **Strong type safety**: Full TypeScript support with proper inference
12+
- **Clean separation of concerns**: Well-organized module structure
13+
- **Generator-based composition**: Elegant async flow control
14+
15+
### 2. Documentation
16+
- **Comprehensive JSDoc**: Most public APIs are well-documented
17+
- **Rich examples**: Good coverage of common use cases
18+
- **Type documentation**: Clear type definitions and exports
19+
20+
### 3. Testing
21+
- **High test coverage**: 97% pass rate (257/265 tests)
22+
- **Good test organization**: Logical grouping and clear test names
23+
- **Edge case testing**: Includes race conditions and memory tests
24+
25+
## 🔴 Critical Issues
26+
27+
### 1. Memory Leak - Observer Cleanup
28+
**Location**: `src/core/observer.ts`, `src/api/events.ts`
29+
30+
**Issue**: MutationObservers attached to elements are not properly disconnected when elements are removed from the DOM.
31+
32+
```typescript
33+
// PROBLEM: Observer continues after element removal
34+
export function onAttr(handler: AttributeChangeHandler): ElementFn<HTMLElement> {
35+
return (element: HTMLElement) => {
36+
const observer = new MutationObserver(mutations => {
37+
// Handler fires even after element.remove()
38+
});
39+
observer.observe(element, { attributes: true });
40+
// MISSING: Cleanup when element is removed
41+
};
42+
}
43+
```
44+
45+
**Fix Required**:
46+
```typescript
47+
export function onAttr(handler: AttributeChangeHandler): ElementFn<HTMLElement> {
48+
return (element: HTMLElement) => {
49+
const observer = new MutationObserver(mutations => {
50+
// Check if element is still connected
51+
if (!element.isConnected) {
52+
observer.disconnect();
53+
return;
54+
}
55+
// ... handle mutations
56+
});
57+
58+
observer.observe(element, { attributes: true });
59+
60+
// Register cleanup
61+
const context = getCurrentContext();
62+
if (context) {
63+
context.cleanup(() => observer.disconnect());
64+
}
65+
66+
// Also use WeakRef for automatic cleanup
67+
const weakElement = new WeakRef(element);
68+
// ... use weakElement.deref() in handler
69+
};
70+
}
71+
```
72+
73+
### 2. Race Condition - State Watchers
74+
**Location**: `src/core/state.ts:145-165`
75+
76+
**Issue**: State watchers can trigger during their own execution, causing infinite loops or unexpected behavior.
77+
78+
```typescript
79+
// PROBLEM: No guard against recursive updates
80+
watchState('counter', (newVal, oldVal) => {
81+
setState('counter', newVal + 1); // Infinite loop!
82+
});
83+
```
84+
85+
**Fix Required**:
86+
```typescript
87+
const activeWatchers = new Set<string>();
88+
89+
function triggerWatchers(key: string, newValue: any, oldValue: any) {
90+
if (activeWatchers.has(key)) {
91+
console.warn(`Recursive state update detected for key: ${key}`);
92+
return;
93+
}
94+
95+
activeWatchers.add(key);
96+
try {
97+
// ... trigger watchers
98+
} finally {
99+
activeWatchers.delete(key);
100+
}
101+
}
102+
```
103+
104+
## 🟡 Moderate Issues
105+
106+
### 1. Inconsistent Error Handling
107+
**Location**: Multiple files
108+
109+
**Issue**: Error handling is inconsistent across the codebase:
110+
- Some functions throw errors
111+
- Some return null/undefined
112+
- Some use console.error
113+
- No consistent error types
114+
115+
**Examples**:
116+
```typescript
117+
// In src/api/dom.ts:446
118+
throw new Error("Invalid arguments for text function");
119+
120+
// In src/api/dom.ts:168
121+
try {
122+
return document.querySelector(elementLike);
123+
} catch {
124+
return null; // Silently fails
125+
}
126+
127+
// In src/core/state.ts:165
128+
} catch (e) {
129+
console.error("Error in state watcher:", e); // Only logs
130+
}
131+
```
132+
133+
**Recommendation**: Create consistent error handling strategy:
134+
```typescript
135+
// src/core/errors.ts
136+
export class WatchError extends Error {
137+
constructor(message: string, public code: string) {
138+
super(message);
139+
this.name = 'WatchError';
140+
}
141+
}
142+
143+
export class ContextError extends WatchError {
144+
constructor(message: string) {
145+
super(message, 'CONTEXT_ERROR');
146+
}
147+
}
148+
149+
export class StateError extends WatchError {
150+
constructor(message: string) {
151+
super(message, 'STATE_ERROR');
152+
}
153+
}
154+
```
155+
156+
### 2. Missing Cleanup in Context Registry
157+
**Location**: `src/core/context.ts:17`
158+
159+
```typescript
160+
// PROBLEM: WeakMap is good, but parent refs might prevent GC
161+
export const parentContextRegistry = new WeakMap<HTMLElement, HTMLElement>();
162+
```
163+
164+
**Issue**: Parent references in the registry might prevent garbage collection of removed elements.
165+
166+
**Fix Required**:
167+
```typescript
168+
// Use WeakRef for parent references
169+
export const parentContextRegistry = new WeakMap<HTMLElement, WeakRef<HTMLElement>>();
170+
171+
export function setParentContext(child: HTMLElement, parent: HTMLElement) {
172+
parentContextRegistry.set(child, new WeakRef(parent));
173+
}
174+
175+
export function getParentContext(child: HTMLElement): HTMLElement | null {
176+
const ref = parentContextRegistry.get(child);
177+
return ref?.deref() ?? null;
178+
}
179+
```
180+
181+
### 3. Unsafe Type Assertions
182+
**Location**: Multiple files
183+
184+
**Issue**: Many unsafe type assertions without runtime checks:
185+
```typescript
186+
// src/api/dom.ts:1589
187+
const element = resolveElement(elementLike) as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
188+
// No runtime check that element is actually one of these types!
189+
```
190+
191+
**Fix Required**:
192+
```typescript
193+
function isFormElement(el: Element): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement {
194+
return el instanceof HTMLInputElement ||
195+
el instanceof HTMLTextAreaElement ||
196+
el instanceof HTMLSelectElement;
197+
}
198+
199+
const element = resolveElement(elementLike);
200+
if (!element || !isFormElement(element)) {
201+
throw new TypeError('Element must be an input, textarea, or select');
202+
}
203+
```
204+
205+
## 🟡 Documentation Issues
206+
207+
### 1. Incomplete Generator Module Documentation
208+
**Location**: `src/generator/*.ts`
209+
210+
While the generator modules have good examples, they lack:
211+
- Return type documentation
212+
- Error conditions
213+
- Performance considerations
214+
- Migration guides from main API
215+
216+
### 2. Missing API Deprecation Notices
217+
**Location**: `src/api/dom-old.ts`
218+
219+
Old API functions should have clear deprecation notices:
220+
```typescript
221+
/**
222+
* @deprecated Since v5.0. Use the new overloaded API instead.
223+
* This function will be removed in v6.0.
224+
*/
225+
```
226+
227+
### 3. Undocumented Internal APIs
228+
Several internal functions are exported but undocumented:
229+
- `pushContext`, `popContext` in `src/core/context.ts`
230+
- `_impl_*` functions in `src/api/dom-internals.ts`
231+
232+
These should either be:
233+
- Made private (not exported)
234+
- Properly documented as internal APIs
235+
- Moved to a separate internal module
236+
237+
## 🔵 Performance Concerns
238+
239+
### 1. Excessive WeakMap Lookups
240+
**Location**: `src/core/state.ts`
241+
242+
```typescript
243+
function getElementState(ctx?: TypedGeneratorContext<any>): Record<string, any> {
244+
const context = getCurrentContext(ctx); // WeakMap lookup
245+
if (!context) {
246+
throw new Error("...");
247+
}
248+
249+
const element = context.element;
250+
if (!globalElementStates.has(element)) { // Another WeakMap lookup
251+
globalElementStates.set(element, new Map());
252+
}
253+
254+
const stateMap = globalElementStates.get(element)!; // Third WeakMap lookup!
255+
// ...
256+
}
257+
```
258+
259+
**Optimization**:
260+
```typescript
261+
function getElementState(ctx?: TypedGeneratorContext<any>): Record<string, any> {
262+
const context = getCurrentContext(ctx);
263+
if (!context) {
264+
throw new Error("...");
265+
}
266+
267+
const element = context.element;
268+
let stateMap = globalElementStates.get(element);
269+
if (!stateMap) {
270+
stateMap = new Map();
271+
globalElementStates.set(element, stateMap);
272+
}
273+
// ... only 1-2 lookups instead of 3
274+
}
275+
```
276+
277+
### 2. Inefficient Selector Matching
278+
**Location**: `src/core/observer.ts:160-180`
279+
280+
The observer checks every selector against every element on each mutation:
281+
```typescript
282+
selectorHandlers.forEach((handlers, selector) => {
283+
if (element.matches(selector)) { // O(n*m) complexity
284+
// ...
285+
}
286+
});
287+
```
288+
289+
**Optimization**: Consider caching or indexing strategies for better performance with many selectors.
290+
291+
## 🔵 Code Quality Issues
292+
293+
### 1. Duplicate Code
294+
Several functions have nearly identical implementations:
295+
- `addClass`, `removeClass`, `toggleClass` in multiple files
296+
- Event handler setup in `click`, `input`, `change`, `submit`
297+
298+
**Recommendation**: Extract common patterns into shared utilities.
299+
300+
### 2. Magic Numbers and Strings
301+
```typescript
302+
// src/api/events.ts:520
303+
wait: 250, // Magic number - should be configurable constant
304+
305+
// src/core/detection.ts:35
306+
args.length >= 3 // Magic number - unclear why 3
307+
```
308+
309+
**Fix**: Define named constants:
310+
```typescript
311+
const DEFAULT_DEBOUNCE_WAIT = 250;
312+
const MIN_SELECTOR_ARGS = 3;
313+
```
314+
315+
### 3. Complex Conditional Logic
316+
Some functions have deeply nested conditions that are hard to follow:
317+
```typescript
318+
// src/api/dom.ts - text function has 5+ levels of nesting
319+
```
320+
321+
**Recommendation**: Extract helper functions and use early returns.
322+
323+
## 🟢 Security Considerations
324+
325+
The library appears to be secure with:
326+
- No use of `eval()` or `Function()` constructor
327+
- No innerHTML without sanitization (when used, it's intentional)
328+
- Proper use of WeakMap to avoid memory leaks
329+
- No exposed global state that could be manipulated
330+
331+
## 📋 Recommendations
332+
333+
### Immediate Actions (High Priority)
334+
1. **Fix observer cleanup** - Add proper cleanup for MutationObservers
335+
2. **Fix state watcher recursion** - Add guards against infinite loops
336+
3. **Standardize error handling** - Create consistent error types and handling
337+
338+
### Short-term Improvements (Medium Priority)
339+
1. **Optimize WeakMap usage** - Reduce redundant lookups
340+
2. **Add runtime type checks** - Replace unsafe assertions
341+
3. **Complete documentation** - Fill in missing JSDoc comments
342+
4. **Extract duplicate code** - Create shared utilities
343+
344+
### Long-term Enhancements (Low Priority)
345+
1. **Performance profiling** - Add benchmarks for large-scale usage
346+
2. **Selector indexing** - Optimize selector matching for many watchers
347+
3. **API versioning** - Implement proper deprecation strategy
348+
4. **Code splitting** - Consider splitting into smaller modules
349+
350+
## Testing Gaps
351+
352+
The following areas lack sufficient test coverage:
353+
354+
1. **Memory management**: WeakMap/WeakRef cleanup verification
355+
2. **Error boundaries**: Error propagation and recovery
356+
3. **Performance**: Large-scale selector matching
357+
4. **Type safety**: Runtime type checking for form elements
358+
5. **Edge cases**: Malformed selectors, invalid HTML, XSS attempts
359+
360+
## Conclusion
361+
362+
The watch-selector library is **production-ready** with excellent architecture and strong type safety. The identified issues are mostly edge cases and optimizations that don't affect normal usage. With the recommended fixes, particularly for memory management and error handling, the library would reach enterprise-grade quality.
363+
364+
**Overall Grade: B+**
365+
366+
The library excels in API design and developer experience but needs refinement in error handling, memory management, and performance optimization for production environments with high load or long-running applications.

0 commit comments

Comments
 (0)