📦 deps(thirdparty): update snapshots
This commit is contained in:
+147
@@ -0,0 +1,147 @@
|
||||
# File Operations Patterns
|
||||
|
||||
Deep dive into @outfitter/file-ops patterns for safe file handling.
|
||||
|
||||
## Secure Paths
|
||||
|
||||
Prevent path traversal attacks with `securePath`:
|
||||
|
||||
```typescript
|
||||
import { securePath } from "@outfitter/file-ops";
|
||||
|
||||
const path = securePath("/data", userInput);
|
||||
// Throws if userInput tries to escape /data via ../
|
||||
```
|
||||
|
||||
## Atomic Writes
|
||||
|
||||
Write files atomically to prevent corruption:
|
||||
|
||||
```typescript
|
||||
import { writeFileAtomic } from "@outfitter/file-ops";
|
||||
|
||||
await writeFileAtomic("/path/to/file.json", JSON.stringify(data, null, 2));
|
||||
// Writes to temp file, then renames (atomic on POSIX)
|
||||
```
|
||||
|
||||
## File Locking
|
||||
|
||||
### Exclusive Lock
|
||||
|
||||
For write operations that need exclusive access:
|
||||
|
||||
```typescript
|
||||
import { withExclusiveLock } from "@outfitter/file-ops";
|
||||
|
||||
const result = await withExclusiveLock("/path/to/file.lock", async () => {
|
||||
const data = await Bun.file("/path/to/data.json").json();
|
||||
data.counter += 1;
|
||||
await writeFileAtomic("/path/to/data.json", JSON.stringify(data));
|
||||
return data;
|
||||
});
|
||||
|
||||
if (result.isErr()) {
|
||||
// Lock acquisition failed or operation threw
|
||||
}
|
||||
```
|
||||
|
||||
### Shared Lock (Reader-Writer)
|
||||
|
||||
Use `withSharedLock()` for read operations that can run concurrently:
|
||||
|
||||
```typescript
|
||||
import { withSharedLock, withExclusiveLock } from "@outfitter/file-ops";
|
||||
|
||||
// Multiple readers can hold shared locks simultaneously
|
||||
const readResult = await withSharedLock("/path/to/data.lock", async () => {
|
||||
return await Bun.file("/path/to/data.json").json();
|
||||
});
|
||||
|
||||
// Writers need exclusive lock (blocks readers)
|
||||
const writeResult = await withExclusiveLock("/path/to/data.lock", async () => {
|
||||
const data = await Bun.file("/path/to/data.json").json();
|
||||
data.updated = Date.now();
|
||||
await writeFileAtomic("/path/to/data.json", JSON.stringify(data));
|
||||
return data;
|
||||
});
|
||||
```
|
||||
|
||||
**Lock fairness note:** Reader-writer locks can cause starvation. With many concurrent readers, writers may wait indefinitely (and vice versa). For high-contention scenarios, consider using exclusive locks only or implementing application-level queuing.
|
||||
|
||||
### Lock Options
|
||||
|
||||
```typescript
|
||||
await withExclusiveLock("/path/to/file.lock", operation, {
|
||||
timeout: 5000, // Max wait time in ms (default: 10000)
|
||||
retryDelay: 100, // Delay between retries (default: 50)
|
||||
staleThreshold: 60000, // Consider lock stale after this many ms
|
||||
});
|
||||
```
|
||||
|
||||
### Lock File Conventions
|
||||
|
||||
- Use `.lock` extension for lock files
|
||||
- Place lock files alongside the protected resource
|
||||
- Use consistent lock file paths across all accessors
|
||||
|
||||
```typescript
|
||||
// Good: Lock file next to data file
|
||||
const dataPath = "/data/users.json";
|
||||
const lockPath = "/data/users.json.lock";
|
||||
|
||||
// Good: Named lock in XDG state
|
||||
import { getStatePath } from "@outfitter/config";
|
||||
const lockPath = getStatePath("myapp", "db.lock");
|
||||
```
|
||||
|
||||
## Safe Directory Operations
|
||||
|
||||
### Ensure Directory Exists
|
||||
|
||||
```typescript
|
||||
import { ensureDir } from "@outfitter/file-ops";
|
||||
|
||||
await ensureDir("/path/to/nested/dir");
|
||||
// Creates all parent directories if needed
|
||||
```
|
||||
|
||||
### Safe Removal
|
||||
|
||||
```typescript
|
||||
import { safeRemove } from "@outfitter/file-ops";
|
||||
|
||||
await safeRemove("/path/to/file-or-dir");
|
||||
// No error if doesn't exist, removes recursively if dir
|
||||
```
|
||||
|
||||
## Temp Files
|
||||
|
||||
### Create Temp File
|
||||
|
||||
```typescript
|
||||
import { createTempFile } from "@outfitter/file-ops";
|
||||
|
||||
const tempPath = await createTempFile("myapp", ".json");
|
||||
// Returns path like /tmp/myapp-abc123.json
|
||||
```
|
||||
|
||||
### With Cleanup
|
||||
|
||||
```typescript
|
||||
import { withTempFile } from "@outfitter/file-ops";
|
||||
|
||||
const result = await withTempFile("myapp", ".json", async (tempPath) => {
|
||||
await Bun.write(tempPath, JSON.stringify(data));
|
||||
return await processFile(tempPath);
|
||||
});
|
||||
// Temp file automatically cleaned up
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use atomic writes** for critical data
|
||||
2. **Lock before read-modify-write** operations
|
||||
3. **Use shared locks** for read-only operations to improve concurrency
|
||||
4. **Validate paths** with `securePath` before using user input
|
||||
5. **Clean up temp files** with `withTempFile` pattern
|
||||
6. **Use XDG paths** from `@outfitter/config` for state/cache files
|
||||
Reference in New Issue
Block a user