0
# Filesystem Abstractions
1
2
Core filesystem interfaces and abstract base classes that define the filesystem API and provide the foundation for all filesystem implementations in @yarnpkg/fslib.
3
4
## Overview
5
6
The filesystem abstraction layer provides a unified interface for different filesystem implementations through abstract base classes and comprehensive type definitions. This allows for seamless switching between real filesystems, virtual filesystems, and specialized filesystem implementations.
7
8
## Core Types and Interfaces
9
10
### Basic Types
11
12
```typescript { .api }
13
// Buffer and encoding types
14
type BufferEncodingOrBuffer = BufferEncoding | 'buffer';
15
16
// Enhanced statistics types with optional CRC
17
interface Stats extends fs.Stats {
18
crc?: number;
19
}
20
21
interface BigIntStats extends fs.BigIntStats {
22
crc?: number;
23
}
24
25
// Directory entry types
26
interface Dirent<T extends Path> extends fs.Dirent {
27
name: Filename;
28
path: T;
29
}
30
31
interface DirentNoPath extends fs.Dirent {
32
name: Filename;
33
}
34
35
// Directory handle interface
36
interface Dir<P extends Path> extends AsyncIterable<Dirent<P>> {
37
readonly path: P;
38
read(): Promise<Dirent<P> | null>;
39
close(): Promise<void>;
40
}
41
42
// Symlink types
43
type SymlinkType = 'file' | 'dir' | 'junction';
44
```
45
46
### Options Types
47
48
```typescript { .api }
49
// Directory operations
50
interface OpendirOptions {
51
bufferSize?: number;
52
}
53
54
interface ReaddirOptions {
55
encoding?: BufferEncoding;
56
withFileTypes?: boolean;
57
}
58
59
interface MkdirOptions {
60
mode?: number;
61
recursive?: boolean;
62
}
63
64
interface RmdirOptions {
65
maxRetries?: number;
66
recursive?: boolean;
67
retryDelay?: number;
68
}
69
70
interface RmOptions {
71
force?: boolean;
72
maxRetries?: number;
73
recursive?: boolean;
74
retryDelay?: number;
75
}
76
77
// File operations
78
interface WriteFileOptions {
79
encoding?: BufferEncoding;
80
mode?: number;
81
flag?: string;
82
}
83
84
interface StatOptions {
85
bigint?: false;
86
}
87
88
interface StatSyncOptions {
89
bigint?: boolean;
90
throwIfNoEntry?: boolean;
91
}
92
93
// Stream operations
94
interface CreateReadStreamOptions {
95
encoding?: BufferEncoding;
96
fd?: number;
97
flags?: string;
98
mode?: number;
99
autoClose?: boolean;
100
emitClose?: boolean;
101
start?: number;
102
end?: number;
103
highWaterMark?: number;
104
}
105
106
interface CreateWriteStreamOptions {
107
encoding?: BufferEncoding;
108
fd?: number;
109
flags?: string;
110
mode?: number;
111
autoClose?: boolean;
112
emitClose?: boolean;
113
start?: number;
114
highWaterMark?: number;
115
}
116
117
// Watch operations
118
interface WatchOptions {
119
encoding?: BufferEncoding;
120
persistent?: boolean;
121
recursive?: boolean;
122
}
123
124
interface WatchFileOptions {
125
bigint?: boolean;
126
persistent?: boolean;
127
interval?: number;
128
}
129
130
interface ExtractHintOptions {
131
relevantExtensions?: Set<string>;
132
}
133
```
134
135
### Callback Types
136
137
```typescript { .api }
138
// Watch callback types
139
type WatchCallback = (eventType: string, filename: string | null) => void;
140
141
type WatchFileCallback = (current: Stats, previous: Stats) => void;
142
143
// Watcher interfaces
144
interface Watcher extends EventEmitter {
145
close(): void;
146
}
147
148
interface StatWatcher extends EventEmitter {
149
ref(): StatWatcher;
150
unref(): StatWatcher;
151
}
152
```
153
154
## Abstract Base Classes
155
156
### FakeFS<P extends Path>
157
158
The core abstract base class that defines the complete filesystem interface.
159
160
```typescript { .api }
161
import { FakeFS, type Path, type Stats, type Dirent } from '@yarnpkg/fslib';
162
163
abstract class FakeFS<P extends Path> {
164
// File reading operations
165
abstract readFilePromise(p: FSPath<P>, encoding?: null): Promise<Buffer>;
166
abstract readFilePromise(p: FSPath<P>, encoding: BufferEncoding): Promise<string>;
167
abstract readFilePromise(p: FSPath<P>, encoding?: BufferEncoding | null): Promise<Buffer | string>;
168
169
abstract readFileSync(p: FSPath<P>, encoding?: null): Buffer;
170
abstract readFileSync(p: FSPath<P>, encoding: BufferEncoding): string;
171
abstract readFileSync(p: FSPath<P>, encoding?: BufferEncoding | null): Buffer | string;
172
173
// File writing operations
174
abstract writeFilePromise(p: P, content: string | Buffer, options?: WriteFileOptions): Promise<void>;
175
abstract writeFileSync(p: P, content: string | Buffer, options?: WriteFileOptions): void;
176
177
abstract appendFilePromise(p: P, content: string | Buffer, options?: WriteFileOptions): Promise<void>;
178
abstract appendFileSync(p: P, content: string | Buffer, options?: WriteFileOptions): void;
179
180
// Directory operations
181
abstract mkdirPromise(p: P, options?: MkdirOptions): Promise<void>;
182
abstract mkdirSync(p: P, options?: MkdirOptions): void;
183
184
abstract readdirPromise(p: P): Promise<Filename[]>;
185
abstract readdirPromise(p: P, options: ReaddirOptions & { withFileTypes: true }): Promise<Dirent<P>[]>;
186
abstract readdirPromise(p: P, options?: ReaddirOptions): Promise<Filename[] | Dirent<P>[]>;
187
188
abstract readdirSync(p: P): Filename[];
189
abstract readdirSync(p: P, options: ReaddirOptions & { withFileTypes: true }): Dirent<P>[];
190
abstract readdirSync(p: P, options?: ReaddirOptions): Filename[] | Dirent<P>[];
191
192
// Metadata operations
193
abstract statPromise(p: FSPath<P>): Promise<Stats>;
194
abstract statPromise(p: FSPath<P>, options: StatOptions & { bigint: true }): Promise<BigIntStats>;
195
abstract statPromise(p: FSPath<P>, options?: StatOptions): Promise<Stats | BigIntStats>;
196
197
abstract statSync(p: FSPath<P>): Stats;
198
abstract statSync(p: FSPath<P>, options: StatSyncOptions & { bigint: true }): BigIntStats;
199
abstract statSync(p: FSPath<P>, options?: StatSyncOptions): Stats | BigIntStats;
200
201
abstract lstatPromise(p: P): Promise<Stats>;
202
abstract lstatPromise(p: P, options: StatOptions & { bigint: true }): Promise<BigIntStats>;
203
abstract lstatPromise(p: P, options?: StatOptions): Promise<Stats | BigIntStats>;
204
205
abstract lstatSync(p: P): Stats;
206
abstract lstatSync(p: P, options: StatSyncOptions & { bigint: true }): BigIntStats;
207
abstract lstatSync(p: P, options?: StatSyncOptions): Stats | BigIntStats;
208
209
// Permission and ownership
210
abstract chmodPromise(p: P, mode: number): Promise<void>;
211
abstract chmodSync(p: P, mode: number): void;
212
213
abstract chownPromise(p: P, uid: number, gid: number): Promise<void>;
214
abstract chownSync(p: P, uid: number, gid: number): void;
215
216
// File system operations
217
abstract copyFilePromise(source: P, destination: P, flags?: number): Promise<void>;
218
abstract copyFileSync(source: P, destination: P, flags?: number): void;
219
220
abstract renamePromise(oldPath: P, newPath: P): Promise<void>;
221
abstract renameSync(oldPath: P, newPath: P): void;
222
223
abstract unlinkPromise(p: P): Promise<void>;
224
abstract unlinkSync(p: P): void;
225
226
// Link operations
227
abstract linkPromise(existingPath: P, newPath: P): Promise<void>;
228
abstract linkSync(existingPath: P, newPath: P): void;
229
230
abstract symlinkPromise(target: P, p: P, type?: SymlinkType): Promise<void>;
231
abstract symlinkSync(target: P, p: P, type?: SymlinkType): void;
232
233
abstract readlinkPromise(p: P): Promise<P>;
234
abstract readlinkSync(p: P): P;
235
236
// Directory handle operations
237
abstract opendirPromise(p: P, options?: OpendirOptions): Promise<Dir<P>>;
238
abstract opendirSync(p: P, options?: OpendirOptions): Dir<P>;
239
240
// Stream operations
241
abstract createReadStream(p: P, options?: CreateReadStreamOptions): fs.ReadStream;
242
abstract createWriteStream(p: P, options?: CreateWriteStreamOptions): fs.WriteStream;
243
244
// Watch operations
245
abstract watchPromise(p: P, options?: WatchOptions): Promise<AsyncIterable<string>>;
246
abstract watch(p: P, options?: WatchOptions): Watcher;
247
abstract watch(p: P, options: WatchOptions, callback: WatchCallback): Watcher;
248
abstract watch(p: P, callback: WatchCallback): Watcher;
249
}
250
```
251
252
### BasePortableFakeFS
253
254
Specialized abstract base class for filesystems that use portable paths.
255
256
```typescript { .api }
257
import { BasePortableFakeFS, type PortablePath } from '@yarnpkg/fslib';
258
259
abstract class BasePortableFakeFS extends FakeFS<PortablePath> {
260
// Inherits all FakeFS methods with PortablePath as the path type
261
// Additional portable path specific utilities may be added here
262
}
263
```
264
265
## Utility Functions
266
267
### Line Ending Normalization
268
269
```typescript { .api }
270
import { normalizeLineEndings } from '@yarnpkg/fslib';
271
272
// Normalize line endings based on original content style
273
function normalizeLineEndings(originalContent: string, newContent: string): string;
274
275
// Usage example
276
const original = "line1\nline2\nline3"; // Unix line endings
277
const modified = "line1\r\nmodified\r\nline3"; // Windows line endings
278
const normalized = normalizeLineEndings(original, modified);
279
// Result: "line1\nmodified\nline3" (matches original style)
280
```
281
282
## Usage Patterns
283
284
### Implementing a Custom Filesystem
285
286
```typescript { .api }
287
import { BasePortableFakeFS, type PortablePath, type Stats } from '@yarnpkg/fslib';
288
289
class CustomFileSystem extends BasePortableFakeFS {
290
private data = new Map<string, Buffer>();
291
292
async readFilePromise(p: PortablePath, encoding?: BufferEncoding | null): Promise<Buffer | string> {
293
const content = this.data.get(p);
294
if (!content) {
295
throw new Error(`File not found: ${p}`);
296
}
297
298
return encoding && encoding !== 'buffer' ? content.toString(encoding) : content;
299
}
300
301
readFileSync(p: PortablePath, encoding?: BufferEncoding | null): Buffer | string {
302
// Synchronous implementation
303
const content = this.data.get(p);
304
if (!content) {
305
throw new Error(`File not found: ${p}`);
306
}
307
308
return encoding && encoding !== 'buffer' ? content.toString(encoding) : content;
309
}
310
311
async writeFilePromise(p: PortablePath, content: string | Buffer): Promise<void> {
312
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
313
this.data.set(p, buffer);
314
}
315
316
writeFileSync(p: PortablePath, content: string | Buffer): void {
317
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
318
this.data.set(p, buffer);
319
}
320
321
// Implement other required methods...
322
}
323
```
324
325
### Working with Directory Entries
326
327
```typescript { .api }
328
import { type Dirent, type PortablePath, type Filename } from '@yarnpkg/fslib';
329
330
async function processDirectoryEntries(fs: FakeFS<PortablePath>, dir: PortablePath): Promise<void> {
331
const entries = await fs.readdirPromise(dir, { withFileTypes: true });
332
333
for (const entry of entries) {
334
console.log(`Processing: ${entry.name} (${entry.isDirectory() ? 'dir' : 'file'})`);
335
336
if (entry.isDirectory()) {
337
// Recursively process subdirectory
338
const subPath = ppath.join(dir, entry.name);
339
await processDirectoryEntries(fs, subPath);
340
} else if (entry.isFile()) {
341
// Process file
342
const filePath = ppath.join(dir, entry.name);
343
const content = await fs.readFilePromise(filePath, 'utf8');
344
console.log(`File size: ${content.length} characters`);
345
}
346
}
347
}
348
```
349
350
### Stream Operations
351
352
```typescript { .api }
353
import { type FakeFS, type PortablePath } from '@yarnpkg/fslib';
354
import { pipeline } from 'stream/promises';
355
356
async function copyFileWithStreams(
357
fs: FakeFS<PortablePath>,
358
source: PortablePath,
359
destination: PortablePath
360
): Promise<void> {
361
const readStream = fs.createReadStream(source);
362
const writeStream = fs.createWriteStream(destination);
363
364
await pipeline(readStream, writeStream);
365
}
366
367
// Usage with transform streams
368
import { Transform } from 'stream';
369
370
async function processFileContent(
371
fs: FakeFS<PortablePath>,
372
inputPath: PortablePath,
373
outputPath: PortablePath,
374
transformer: Transform
375
): Promise<void> {
376
await pipeline(
377
fs.createReadStream(inputPath),
378
transformer,
379
fs.createWriteStream(outputPath)
380
);
381
}
382
```
383
384
### Watch Operations
385
386
```typescript { .api }
387
import { type FakeFS, type PortablePath, type WatchCallback } from '@yarnpkg/fslib';
388
389
function setupFileWatcher(fs: FakeFS<PortablePath>, path: PortablePath): void {
390
const callback: WatchCallback = (eventType, filename) => {
391
console.log(`File ${filename} changed: ${eventType}`);
392
};
393
394
const watcher = fs.watch(path, { recursive: true }, callback);
395
396
// Clean up after 10 seconds
397
setTimeout(() => {
398
watcher.close();
399
}, 10000);
400
}
401
402
// Async watch with iteration
403
async function watchDirectory(fs: FakeFS<PortablePath>, path: PortablePath): Promise<void> {
404
const watcher = await fs.watchPromise(path, { recursive: true });
405
406
for await (const event of watcher) {
407
console.log(`Directory change detected: ${event}`);
408
// Handle the event
409
}
410
}
411
```
412
413
## Error Handling
414
415
### Common Filesystem Errors
416
417
```typescript { .api }
418
import { errors, type FakeFS, type PortablePath } from '@yarnpkg/fslib';
419
420
async function safeFileOperation(fs: FakeFS<PortablePath>, path: PortablePath): Promise<string | null> {
421
try {
422
return await fs.readFilePromise(path, 'utf8');
423
} catch (error) {
424
if (error.code === 'ENOENT') {
425
// File doesn't exist
426
return null;
427
} else if (error.code === 'EACCES') {
428
// Permission denied
429
throw errors.EACCES(`Cannot access file: ${path}`);
430
} else {
431
// Re-throw other errors
432
throw error;
433
}
434
}
435
}
436
437
// Check file existence safely
438
async function fileExists(fs: FakeFS<PortablePath>, path: PortablePath): Promise<boolean> {
439
try {
440
await fs.statPromise(path);
441
return true;
442
} catch (error) {
443
if (error.code === 'ENOENT') {
444
return false;
445
}
446
throw error;
447
}
448
}
449
```
450
451
## Related Documentation
452
453
- [Path Handling](./path-handling.md) - Path utilities used by filesystem operations
454
- [Filesystem Implementations](./filesystem-implementations.md) - Concrete implementations of these abstractions
455
- [Constants and Utilities](./constants-and-utilities.md) - Error factories and statistics utilities
456
- [Advanced Features](./advanced-features.md) - Advanced operations building on these abstractions