fsd 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,296 +1,1308 @@
1
1
  /// <reference types="node"/>
2
2
 
3
+ /**
4
+ * FSD (File System Driver) - 核心库类型定义
5
+ *
6
+ * 这是一个通用的文件存储驱动抽象层,提供统一的 API 接口,
7
+ * 支持多种存储后端(本地磁盘、阿里云 OSS、阿里云 VOD)。
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+
12
+ /**
13
+ * FSD 工厂函数配置选项
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const fsd = FSD({
18
+ * adapter: new FSAdapter({ root: '/uploads' })
19
+ * });
20
+ * ```
21
+ */
3
22
  export interface DriverOptions {
23
+ /**
24
+ * 存储适配器实例
25
+ *
26
+ * 适配器负责实际与底层存储系统交互(如本地文件系统、云存储等)。
27
+ * 所有适配器必须实现 Adapter 接口。
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // 本地文件系统适配器
32
+ * import FSAdapter from 'fsd-fs';
33
+ * const adapter = new FSAdapter({ root: '/uploads' });
34
+ *
35
+ * // 阿里云 OSS 适配器
36
+ * import OSSAdapter from 'fsd-oss';
37
+ * const adapter = new OSSAdapter({
38
+ * accessKeyId: 'xxx',
39
+ * accessKeySecret: 'xxx',
40
+ * region: 'oss-cn-hangzhou',
41
+ * bucket: 'my-bucket'
42
+ * });
43
+ *
44
+ * // 阿里云 VOD 适配器
45
+ * import VODAdapter from 'fsd-vod';
46
+ * const adapter = new VODAdapter({
47
+ * accessKeyId: 'xxx',
48
+ * accessKeySecret: 'xxx',
49
+ * privateKey: 'xxx'
50
+ * });
51
+ * ```
52
+ */
4
53
  adapter: Adapter<any>;
5
54
  }
6
55
 
56
+ /**
57
+ * 创建可读流的选项
58
+ *
59
+ * 用于控制流式读取文件时的起始位置和结束位置。
60
+ */
7
61
  export interface ReadStreamOptions {
62
+ /**
63
+ * 开始读取的字节位置(从 0 开始)
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * // 从第 100 字节开始读取
68
+ * const stream = await file.createReadStream({ start: 100 });
69
+ * ```
70
+ *
71
+ * @remarks
72
+ * 注意:fsd-oss 适配器不支持此选项
73
+ */
8
74
  start?: number;
75
+
76
+ /**
77
+ * 结束读取的字节位置(包含)
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * // 读取第 100 到 199 字节
82
+ * const stream = await file.createReadStream({ start: 100, end: 199 });
83
+ * ```
84
+ */
9
85
  end?: number;
10
86
  }
11
87
 
88
+ /**
89
+ * 创建可写流的选项
90
+ *
91
+ * 用于控制流式写入文件时的起始位置。
92
+ */
12
93
  export interface WriteStreamOptions {
13
- start?: number; // ! fsd-oss 不支持 start
94
+ /**
95
+ * 开始写入的字节位置(从 0 开始)
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * // 从第 100 字节开始写入
100
+ * const stream = await file.createWriteStream({ start: 100 });
101
+ * ```
102
+ *
103
+ * @remarks
104
+ * 注意:fsd-oss 和 fsd-vod 适配器不支持此选项
105
+ */
106
+ start?: number;
14
107
  }
15
108
 
109
+ /**
110
+ * 文件元数据
111
+ *
112
+ * 包含文件的基本信息,如大小和最后修改时间。
113
+ * 这些信息通常在文件查询时返回,用于缓存和优化。
114
+ */
16
115
  export interface FileMetadata {
116
+ /**
117
+ * 文件大小(字节)
118
+ *
119
+ * 目录始终返回 0 或 undefined。
120
+ */
17
121
  size?: number;
122
+
123
+ /**
124
+ * 文件最后修改时间
125
+ *
126
+ * 不同存储后端的精度可能不同。
127
+ */
18
128
  lastModified?: Date;
19
129
  }
20
130
 
21
131
  /**
22
- * 创建URL选项
132
+ * 创建访问 URL 的选项
133
+ *
134
+ * 用于生成临时访问链接,适用于文件下载、预览等场景。
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * // 创建 1 小时后过期的下载链接
139
+ * const url = await file.createUrl({ expires: 3600 });
140
+ *
141
+ * // 创建带自定义响应头的下载链接
142
+ * const url = await file.createUrl({
143
+ * expires: 7200,
144
+ * response: {
145
+ * 'content-disposition': 'attachment; filename="report.pdf"'
146
+ * }
147
+ * });
148
+ *
149
+ * // VOD 适配器:获取不同清晰度的视频播放地址
150
+ * const url = await file.createUrl({
151
+ * path: '/video/HD',
152
+ * expires: 3600
153
+ * });
154
+ * ```
23
155
  */
24
156
  export interface CreateUrlOptions {
25
157
  /**
26
- * 文件路径,可选,适用于VOD驱动获取不同清晰度的视频播放地址
158
+ * 文件路径,可选
159
+ *
160
+ * 主要用于 VOD 驱动获取不同清晰度的视频播放地址。
161
+ * 例如:'/video/HD' 表示高清版本,'/video/SD' 表示标清版本。
27
162
  */
28
163
  path?: string;
164
+
29
165
  /**
30
166
  * 缩略图规格名
167
+ *
168
+ * 用于 OSS/VOD 获取特定规格的缩略图 URL。
169
+ * 具体规格名需要根据云服务商的配置而定。
31
170
  */
32
171
  thumb?: string;
172
+
33
173
  /**
34
- * 链接过期时间,单位秒,默认值 3600
174
+ * 链接过期时间(秒)
175
+ *
176
+ * 默认值:3600(1 小时)
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * // 创建 10 分钟后过期的链接
181
+ * const url = await file.createUrl({ expires: 600 });
182
+ * ```
35
183
  */
36
184
  expires?: number;
185
+
37
186
  /**
38
- * 链接响应头,可选
187
+ * 链接响应头(可选)
188
+ *
189
+ * 用于控制下载时的浏览器行为。
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * const url = await file.createUrl({
194
+ * response: {
195
+ * 'content-type': 'application/pdf',
196
+ * 'content-disposition': 'attachment; filename="document.pdf"'
197
+ * }
198
+ * });
199
+ * ```
39
200
  */
40
201
  response?: {
202
+ /**
203
+ * 响应的 Content-Type
204
+ */
41
205
  'content-type'?: string;
206
+
207
+ /**
208
+ * 响应的 Content-Disposition
209
+ *
210
+ * 常用值:
211
+ * - 'inline' - 在浏览器中预览
212
+ * - 'attachment; filename="xxx"' - 强制下载并指定文件名
213
+ */
42
214
  'content-disposition'?: string;
43
215
  };
44
216
  }
45
217
 
218
+ /**
219
+ * 带有 Promise 属性的可写流
220
+ *
221
+ * 某些适配器的可写流会附加一个 promise 属性,
222
+ * 用于等待流写入完成。
223
+ */
46
224
  export interface WithPromise {
225
+ /**
226
+ * 流完成时的 Promise
227
+ *
228
+ * 如果存在此属性,可以 await 它来等待流写入完成。
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * const stream = await file.createWriteStream();
233
+ * if (stream.promise) {
234
+ * await stream.promise;
235
+ * }
236
+ * ```
237
+ */
47
238
  promise?: Promise<any>;
48
239
  }
49
240
 
241
+ /**
242
+ * 分片上传任务 ID
243
+ *
244
+ * 格式:`task:{number}` 或 `task://{UploadId}?{partNumber}`
245
+ *
246
+ * - `task:1` - 简单任务 ID(核心实现)
247
+ * - `task://abc123?1` - 适配器特定的任务 ID(包含 UploadId 和分片号)
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * const tasks = await file.initMultipartUpload(3);
252
+ * // tasks = ['task://uploadId123?1', 'task://uploadId123?2', 'task://uploadId123?3']
253
+ * ```
254
+ */
50
255
  export type Task = string;
256
+
257
+ /**
258
+ * 分片上传的分片信息
259
+ *
260
+ * 格式:`part://{UploadId}?{partNumber}#{etag}`
261
+ *
262
+ * 上传分片后会返回包含 ETag 的分片信息,用于完成分片上传。
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * const part = await file.writePart(task, data);
267
+ * // part = 'part://uploadId123?1#d41d8cd98f00b204e9800998ecf8427e'
268
+ * ```
269
+ */
51
270
  export type Part = string;
52
271
 
53
272
  /**
54
- * 文件对象
273
+ * FSD 文件对象
274
+ *
275
+ * FSDFile 是文件和目录操作的抽象层,提供统一的 API。
276
+ * 通过工厂函数 `FSD({ adapter })()` 创建文件对象实例。
277
+ *
278
+ * @remarks
279
+ * ### 路径约定(重要)
280
+ * - **文件路径**必须**以 `/` 结尾**(例如 `/uploads/`)
281
+ * - **文件路径**不能**以 `/` 结尾**(例如 `/file.txt`)
282
+ * - 所有路径会自动补全前导 `/`
283
+ *
284
+ * ### 路径验证
285
+ * 违反路径约定会抛出错误,例如:
286
+ * - `await fsd('/file/').read()` → Error: file path should not ends with /
287
+ * - `await fsd('/uploads').mkdir()` → Error: directory path should be ends with /
288
+ *
289
+ * ### 所有方法都是异步的
290
+ * 所有操作都返回 Promise,必须使用 await。
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * import FSD from 'fsd';
295
+ * import FSAdapter from 'fsd-fs';
296
+ *
297
+ * const fsd = FSD({ adapter: new FSAdapter({ root: '/uploads' }) });
298
+ *
299
+ * // 创建文件
300
+ * const file = fsd('/test.txt');
301
+ * await file.write('Hello, FSD!');
302
+ *
303
+ * // 读取文件
304
+ * const content = await file.read('utf8');
305
+ * console.log(content); // 'Hello, FSD!'
306
+ *
307
+ * // 创建目录
308
+ * const dir = fsd('/photos/');
309
+ * await dir.mkdir();
310
+ *
311
+ * // 列出目录
312
+ * const files = await dir.readdir();
313
+ * for (const f of files) {
314
+ * console.log(f.name);
315
+ * }
316
+ * ```
55
317
  */
56
318
  export interface FSDFile {
57
319
  /**
58
- * 是否为FSDFile实例
320
+ * 类型守卫:是否为 FSDFile 实例
321
+ *
322
+ * 用于运行时检查一个对象是否是 FSDFile 实例。
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * if (obj.instanceOfFSDFile) {
327
+ * console.log('This is an FSDFile');
328
+ * }
329
+ * ```
59
330
  */
60
331
  readonly instanceOfFSDFile: true;
332
+
61
333
  /**
62
- * 文件路径,例如 /path/to/file.txt
334
+ * 完整的文件路径
335
+ *
336
+ * 格式:绝对路径,前导 `/` 结尾取决于是文件还是目录
337
+ *
338
+ * @example
339
+ * ```typescript
340
+ * const file = fsd('/path/to/file.txt');
341
+ * console.log(file.path); // '/path/to/file.txt'
342
+ *
343
+ * const dir = fsd('/path/to/folder/');
344
+ * console.log(dir.path); // '/path/to/folder/'
345
+ * ```
63
346
  */
64
347
  readonly path: string;
348
+
65
349
  /**
66
- * 文件所在目录,例如 /path/to
350
+ * 文件所在目录路径
351
+ *
352
+ * 格式:以 `/` 结尾的目录路径
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * const file = fsd('/a/b/c.txt');
357
+ * console.log(file.dir); // '/a/b/'
358
+ *
359
+ * const dir = fsd('/a/b/');
360
+ * console.log(dir.dir); // '/a/b/'
361
+ * ```
67
362
  */
68
363
  readonly dir: string;
364
+
69
365
  /**
70
- * 文件名,包含扩展名,例如 file.txt
366
+ * 文件名(包含扩展名)
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * const file = fsd('/path/to/document.pdf');
371
+ * console.log(file.base); // 'document.pdf'
372
+ *
373
+ * const dir = fsd('/path/to/folder/');
374
+ * console.log(dir.base); // 'folder/'
375
+ * ```
71
376
  */
72
377
  readonly base: string;
378
+
73
379
  /**
74
- * 文件名,不包含扩展名,例如 file
380
+ * 文件名(不包含扩展名)
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * const file = fsd('/path/to/document.pdf');
385
+ * console.log(file.name); // 'document'
386
+ *
387
+ * const file = fsd('/archive.tar.gz');
388
+ * console.log(file.name); // 'archive.tar'
389
+ * ```
75
390
  */
76
391
  readonly name: string;
392
+
77
393
  /**
78
- * 文件扩展名,例如 .txt
394
+ * 文件扩展名(包含点号)
395
+ *
396
+ * @example
397
+ * ```typescript
398
+ * const file = fsd('/path/to/document.pdf');
399
+ * console.log(file.ext); // '.pdf'
400
+ *
401
+ * const file = fsd('/path/to/data.json');
402
+ * console.log(file.ext); // '.json'
403
+ * ```
79
404
  */
80
405
  readonly ext: string;
406
+
81
407
  /**
82
408
  * 是否需要确保目录存在
409
+ *
410
+ * 从适配器继承的属性:
411
+ * - `true`: 需要先创建目录(FSAdapter)
412
+ * - `false`: 自动处理目录(OSSAdapter, VODAdapter)
413
+ *
414
+ * @example
415
+ * ```typescript
416
+ * const file = fsd('/uploads/file.txt');
417
+ * if (file.needEnsureDir) {
418
+ * await fsd('/uploads/').mkdir();
419
+ * }
420
+ * ```
83
421
  */
84
422
  readonly needEnsureDir: boolean;
85
423
 
86
424
  /**
87
- * 给文件追加内容
88
- * @param {string|Buffer|NodeJS.ReadableStream} data 追加内容
425
+ * 向文件追加内容
426
+ *
427
+ * 如果文件不存在会自动创建,如果文件存在则追加到末尾。
428
+ *
429
+ * @param data - 要追加的内容,可以是字符串、Buffer 或可读流
430
+ *
431
+ * @example
432
+ * ```typescript
433
+ * // 追加字符串
434
+ * await file.append('Hello\n');
435
+ *
436
+ * // 追加 Buffer
437
+ * await file.append(Buffer.from('World'));
438
+ *
439
+ * // 从其他文件追加
440
+ * const source = fsd('/source.txt');
441
+ * const dest = fsd('/dest.txt');
442
+ * const sourceStream = await source.createReadStream();
443
+ * await dest.append(sourceStream);
444
+ * ```
89
445
  */
90
446
  append(data: string | Buffer | NodeJS.ReadableStream): Promise<void>;
91
447
 
92
448
  /**
93
- * 读取文件内容
94
- * @param {string} [encoding] 编码
449
+ * 读取文件内容(带编码)
450
+ *
451
+ * @param encoding - 文本编码,如 'utf8', 'base64', 'ascii'
452
+ * @returns 解码后的字符串内容
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * // 读取为 UTF-8 字符串
457
+ * const text = await file.read('utf8');
458
+ * console.log(text);
459
+ *
460
+ * // 读取为 Base64
461
+ * const base64 = await file.read('base64');
462
+ * console.log(base64);
463
+ * ```
95
464
  */
96
465
  read(encoding: BufferEncoding): Promise<string>;
97
466
 
98
467
  /**
99
- * 读取文件内容
100
- * @param {number} [position] 读取起始位置
101
- * @param {number} [length] 读取长度
468
+ * 读取文件内容(完整 Buffer)
469
+ *
470
+ * @returns 包含文件完整内容的 Buffer
471
+ *
472
+ * @example
473
+ * ```typescript
474
+ * const buffer = await file.read();
475
+ * console.log(buffer.length); // 文件字节数
476
+ * ```
102
477
  */
103
478
  read(position?: number, length?: number): Promise<Buffer>;
104
479
 
105
480
  /**
106
- * 读取文件内容
107
- * @param {number} position 读取起始位置
108
- * @param {number} length 读取长度
109
- * @param {string} encoding 编码
481
+ * 读取文件内容(指定位置和长度,带编码)
482
+ *
483
+ * @param position - 读取起始字节位置(从 0 开始)
484
+ * @param length - 读取的字节数
485
+ * @param encoding - 文本编码
486
+ * @returns 指定范围的解码后的字符串
487
+ *
488
+ * @example
489
+ * ```typescript
490
+ * // 读取前 10 个字节并解码为 UTF-8
491
+ * const text = await file.read(0, 10, 'utf8');
492
+ *
493
+ * // 读取第 100-109 字节
494
+ * const data = await file.read(100, 10, 'utf8');
495
+ * ```
110
496
  */
111
497
  read(position: number, length: number, encoding: BufferEncoding): Promise<string>;
112
498
 
113
499
  /**
114
500
  * 写入文件内容
115
- * @param {string|Buffer|NodeJS.ReadableStream} [data] 写入内容
501
+ *
502
+ * 如果文件存在会覆盖原有内容,如果文件不存在则创建。
503
+ *
504
+ * @param data - 要写入的内容,可以是字符串、Buffer 或可读流
505
+ * 省略此参数会创建空文件
506
+ *
507
+ * @example
508
+ * ```typescript
509
+ * // 写入字符串
510
+ * await file.write('Hello, World!');
511
+ *
512
+ * // 写入 Buffer
513
+ * await file.write(Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]));
514
+ *
515
+ * // 从其他文件复制
516
+ * const source = fsd('/source.pdf');
517
+ * const dest = fsd('/dest.pdf');
518
+ * const sourceStream = await source.createReadStream();
519
+ * await dest.write(sourceStream);
520
+ *
521
+ * // 创建空文件
522
+ * await file.write();
523
+ * ```
116
524
  */
117
525
  write(data?: string | Buffer | NodeJS.ReadableStream): Promise<void>;
118
526
 
119
527
  /**
120
- * 创建读取流
121
- * @param {object} options 读取流选项
528
+ * 创建可读流
529
+ *
530
+ * 适用于大文件的流式处理,避免一次性加载到内存。
531
+ *
532
+ * @param options - 流选项,可指定读取范围
533
+ * @returns 可读的 Node.js Stream
534
+ *
535
+ * @example
536
+ * ```typescript
537
+ * // 创建完整文件的可读流
538
+ * const stream = await file.createReadStream();
539
+ * stream.pipe(process.stdout);
540
+ *
541
+ * // 创建指定范围的可读流
542
+ * const partial = await file.createReadStream({ start: 100, end: 199 });
543
+ *
544
+ * // 流式下载到本地文件
545
+ * const fs = require('fs');
546
+ * const readStream = await file.createReadStream();
547
+ * const writeStream = fs.createWriteStream('/local/file.txt');
548
+ * readStream.pipe(writeStream);
549
+ * ```
122
550
  */
123
551
  createReadStream(options?: ReadStreamOptions): Promise<NodeJS.ReadableStream>;
124
552
 
125
553
  /**
126
- * 创建写入流
127
- * @param {object} options 写入流选项
554
+ * 创建可写流
555
+ *
556
+ * 适用于大文件的流式上传,避免一次性加载到内存。
557
+ *
558
+ * @param options - 流选项,可指定写入起始位置
559
+ * @returns 可写的 Node.js Stream(可能附加 promise 属性)
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * // 创建可写流并写入
564
+ * const stream = await file.createWriteStream();
565
+ * stream.write('Hello');
566
+ * stream.end();
567
+ * if (stream.promise) {
568
+ * await stream.promise;
569
+ * }
570
+ *
571
+ * // 管道本地文件到可写流
572
+ * const fs = require('fs');
573
+ * const readStream = fs.createReadStream('/local/file.txt');
574
+ * const writeStream = await file.createWriteStream();
575
+ * readStream.pipe(writeStream);
576
+ * await writeStream.promise;
577
+ *
578
+ * // 从指定位置开始写入(断点续传)
579
+ * const stream = await file.createWriteStream({ start: 1024 });
580
+ * ```
128
581
  */
129
582
  createWriteStream(options?: WriteStreamOptions): Promise<NodeJS.WritableStream>;
130
583
 
131
584
  /**
132
- * 删除文件
585
+ * 删除文件或目录
586
+ *
587
+ * 如果是目录,会递归删除目录下的所有内容。
588
+ *
589
+ * @example
590
+ * ```typescript
591
+ * // 删除文件
592
+ * await file.unlink();
593
+ *
594
+ * // 删除目录及其所有内容
595
+ * const dir = fsd('/temp/');
596
+ * await dir.unlink();
597
+ * ```
133
598
  */
134
599
  unlink(): Promise<void>;
135
600
 
136
601
  /**
137
- * 新建目录
138
- * @param {boolean} [recursive] 是否递归创建
602
+ * 创建目录
603
+ *
604
+ * @param recursive - 是否递归创建父目录
605
+ *
606
+ * @example
607
+ * ```typescript
608
+ * // 创建单级目录(父目录必须存在)
609
+ * const dir = fsd('/uploads/');
610
+ * await dir.mkdir();
611
+ *
612
+ * // 递归创建多级目录
613
+ * const dir = fsd('/a/b/c/');
614
+ * await dir.mkdir(true);
615
+ * ```
139
616
  */
140
617
  mkdir(recursive?: boolean): Promise<void>;
141
618
 
142
619
  /**
143
- * 读取目录
144
- * @param {boolean|string} [recursion] 是否递归读取,或者指定子目录glob规则
620
+ * 读取目录内容
621
+ *
622
+ * 返回目录下的文件和子目录列表。
623
+ *
624
+ * @param recursion - 递归选项:
625
+ * - `undefined`: 只列出直接子项
626
+ * - `true`: 递归列出所有子项
627
+ * - `string`: 使用 glob 模式匹配(如 `**\/*.jpg`)
628
+ * @returns FSDFile 对象数组
629
+ *
630
+ * @example
631
+ * ```typescript
632
+ * // 列出直接子项
633
+ * const files = await dir.readdir();
634
+ * for (const f of files) {
635
+ * console.log(f.name, f.isDirectory ? '(dir)' : '(file)');
636
+ * }
637
+ *
638
+ * // 递归列出所有文件
639
+ * const allFiles = await dir.readdir(true);
640
+ * console.log(`Total: ${allFiles.length} items`);
641
+ *
642
+ * // 使用 glob 模式筛选
643
+ * const images = await dir.readdir('**\/*.jpg');
644
+ * for (const img of images) {
645
+ * console.log(img.path);
646
+ * }
647
+ * ```
145
648
  */
146
649
  readdir(recursion?: true | string): Promise<FSDFile[]>;
147
650
 
148
651
  /**
149
- * 创建访问URL
150
- * @param {object} options 创建URL选项
652
+ * 创建可访问的 URL
653
+ *
654
+ * 生成临时访问链接,适用于文件下载、预览等场景。
655
+ *
656
+ * @param options - URL 生成选项
657
+ * @returns 可访问的 URL 字符串
658
+ *
659
+ * @example
660
+ * ```typescript
661
+ * // 创建默认链接(1 小时后过期)
662
+ * const url = await file.createUrl();
663
+ * console.log('Download:', url);
664
+ *
665
+ * // 创建 10 分钟后过期的链接
666
+ * const url = await file.createUrl({ expires: 600 });
667
+ *
668
+ * // 创建带下载提示的链接
669
+ * const url = await file.createUrl({
670
+ * expires: 3600,
671
+ * response: {
672
+ * 'content-disposition': 'attachment; filename="report.pdf"'
673
+ * }
674
+ * });
675
+ *
676
+ * // VOD 适配器:获取不同清晰度
677
+ * const hdUrl = await file.createUrl({ path: '/video/HD' });
678
+ * const sdUrl = await file.createUrl({ path: '/video/SD' });
679
+ * ```
151
680
  */
152
681
  createUrl(options?: CreateUrlOptions): Promise<string>;
153
682
 
154
683
  /**
155
- * 拷贝文件到目标路径
156
- * @param {string} dest 目标路径
684
+ * 拷贝文件或目录
685
+ *
686
+ * 将文件或目录复制到目标位置。
687
+ * 目录复制会递归复制所有内容。
688
+ *
689
+ * @param dest - 目标路径(相对或绝对路径)
690
+ * @returns 目标位置的 FSDFile 对象
691
+ *
692
+ * @example
693
+ * ```typescript
694
+ * // 复制文件到同级目录
695
+ * const source = fsd('/file.txt');
696
+ * const copy = await source.copy('file_copy.txt');
697
+ *
698
+ * // 复制文件到子目录
699
+ * const copy = await source.copy('backup/file.txt');
700
+ *
701
+ * // 复制文件到绝对路径
702
+ * const copy = await source.copy('/other/path/file.txt');
703
+ *
704
+ * // 复制目录
705
+ * const dir = fsd('/photos/');
706
+ * await dir.copy('photos_backup/');
707
+ * ```
157
708
  */
158
709
  copy(dest: string): Promise<FSDFile>;
159
710
 
160
711
  /**
161
- * 重命名文件到目标路径
162
- * @param {string} dest 目标路径
712
+ * 重命名或移动文件
713
+ *
714
+ * 将文件或目录移动到新位置(相当于重命名)。
715
+ *
716
+ * @param dest - 目标路径(相对或绝对路径)
717
+ *
718
+ * @example
719
+ * ```typescript
720
+ * // 重命名文件
721
+ * const file = fsd('/old_name.txt');
722
+ * await file.rename('new_name.txt');
723
+ *
724
+ * // 移动文件到子目录
725
+ * await file.rename('backup/file.txt');
726
+ *
727
+ * // 移动文件到绝对路径
728
+ * await file.rename('/other/path/file.txt');
729
+ *
730
+ * // 重命名目录
731
+ * const dir = fsd('/old_folder/');
732
+ * await dir.rename('new_folder/');
733
+ * ```
163
734
  */
164
735
  rename(dest: string): Promise<void>;
165
736
 
166
737
  /**
167
- * 判断文件是否存在
738
+ * 判断文件或目录是否存在
739
+ *
740
+ * @returns true 表示存在,false 表示不存在
741
+ *
742
+ * @example
743
+ * ```typescript
744
+ * if (await file.exists()) {
745
+ * console.log('File exists');
746
+ * } else {
747
+ * await file.write('content');
748
+ * }
749
+ * ```
168
750
  */
169
751
  exists(): Promise<boolean>;
170
752
 
171
753
  /**
172
- * 判断文件是否为文件
754
+ * 判断路径是否为文件
755
+ *
756
+ * @returns true 表示是文件,false 表示不是或不存在
757
+ *
758
+ * @example
759
+ * ```typescript
760
+ * if (await file.isFile()) {
761
+ * console.log('Size:', await file.size());
762
+ * }
763
+ * ```
173
764
  */
174
765
  isFile(): Promise<boolean>;
175
766
 
176
767
  /**
177
- * 判断文件是否为目录
768
+ * 判断路径是否为目录
769
+ *
770
+ * @returns true 表示是目录,false 表示不是或不存在
771
+ *
772
+ * @example
773
+ * ```typescript
774
+ * if (await dir.isDirectory()) {
775
+ * const files = await dir.readdir();
776
+ * console.log(`Contains ${files.length} items`);
777
+ * }
778
+ * ```
178
779
  */
179
780
  isDirectory(): Promise<boolean>;
180
781
 
181
782
  /**
182
783
  * 获取文件大小
784
+ *
785
+ * 目录返回 0。
786
+ * 值会被缓存在文件对象中,避免重复查询。
787
+ *
788
+ * @returns 文件大小(字节数)
789
+ *
790
+ * @example
791
+ * ```typescript
792
+ * const size = await file.size();
793
+ * console.log(`File size: ${size} bytes`);
794
+ *
795
+ * // 格式化显示
796
+ * const sizeMB = (await file.size()) / (1024 * 1024);
797
+ * console.log(`File size: ${sizeMB.toFixed(2)} MB`);
798
+ * ```
183
799
  */
184
800
  size(): Promise<number>;
185
801
 
186
802
  /**
187
803
  * 获取文件最后修改时间
804
+ *
805
+ * 值会被缓存在文件对象中,避免重复查询。
806
+ *
807
+ * @returns 最后修改时间的 Date 对象
808
+ *
809
+ * @example
810
+ * ```typescript
811
+ * const modified = await file.lastModified();
812
+ * console.log('Last modified:', modified.toISOString());
813
+ *
814
+ * // 判断文件是否过期
815
+ * const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
816
+ * if (modified < oneDayAgo) {
817
+ * console.log('File is older than 1 day');
818
+ * }
819
+ * ```
188
820
  */
189
821
  lastModified(): Promise<Date>;
190
822
 
191
823
  /**
192
- * 创建分片上传任务
193
- * @param {number} partCount 分片数量
824
+ * 初始化分片上传任务
825
+ *
826
+ * 用于大文件上传,将文件拆分为多个部分并行上传。
827
+ *
828
+ * @param partCount - 分片数量
829
+ * @returns 任务 ID 数组,每个 ID 对应一个分片
830
+ *
831
+ * @example
832
+ * ```typescript
833
+ * // 初始化 3 个分片
834
+ * const tasks = await file.initMultipartUpload(3);
835
+ * console.log(tasks);
836
+ * // ['task://uploadId?1', 'task://uploadId?2', 'task://uploadId?3']
837
+ * ```
194
838
  */
195
839
  initMultipartUpload(partCount: number): Promise<Task[]>;
196
840
 
197
841
  /**
198
- * 上传分片数据
199
- * @param {string} partTask 分片任务ID
200
- * @param {any} data 分片数据
201
- * @param {number} [size] 分片大小
842
+ * 上传一个分片
843
+ *
844
+ * 将数据块上传到指定分片任务。
845
+ *
846
+ * @param partTask - 分片任务 ID(由 initMultipartUpload 返回)
847
+ * @param data - 分片数据,可以是字符串、Buffer 或可读流
848
+ * @param size - 数据大小(字节数),当 data 为可读流时必须提供
849
+ * @returns 分片信息(包含 ETag)
850
+ *
851
+ * @example
852
+ * ```typescript
853
+ * const tasks = await file.initMultipartUpload(3);
854
+ *
855
+ * // 上传第一个分片(Buffer)
856
+ * const part1 = await file.writePart(tasks[0], Buffer.from('part1'));
857
+ *
858
+ * // 上传第二个分片(流)
859
+ * const stream = fs.createReadStream('/tmp/part2');
860
+ * const part2 = await file.writePart(tasks[1], stream, 1024);
861
+ *
862
+ * // 上传第三个分片(字符串)
863
+ * const part3 = await file.writePart(tasks[2], 'part3');
864
+ *
865
+ * // 完成上传
866
+ * await file.completeMultipartUpload([part1, part2, part3]);
867
+ * ```
202
868
  */
203
869
  writePart(
204
870
  partTask: Task,
205
871
  data: string | Buffer | NodeJS.ReadableStream,
206
872
  size?: number
207
873
  ): Promise<Part>;
874
+
875
+ /**
876
+ * 完成分片上传
877
+ *
878
+ * 所有分片上传完成后调用此方法,合并分片为完整文件。
879
+ *
880
+ * @param parts - 分片信息数组(由 writePart 返回)
881
+ *
882
+ * @example
883
+ * ```typescript
884
+ * const tasks = await file.initMultipartUpload(3);
885
+ * const parts: Part[] = [];
886
+ *
887
+ * for (let i = 0; i < tasks.length; i++) {
888
+ * const part = await file.writePart(tasks[i], getDataForPart(i));
889
+ * parts.push(part);
890
+ * }
891
+ *
892
+ * // 完成上传(注意:parts 顺序可以与 tasks 顺序不同)
893
+ * await file.completeMultipartUpload(parts);
894
+ * ```
895
+ */
208
896
  completeMultipartUpload(parts: Part[]): Promise<void>;
209
897
 
898
+ /**
899
+ * 转换为字符串
900
+ *
901
+ * @returns 文件路径字符串
902
+ *
903
+ * @example
904
+ * ```typescript
905
+ * const file = fsd('/path/to/file.txt');
906
+ * console.log(String(file)); // '/path/to/file.txt'
907
+ * console.log(file.toString()); // '/path/to/file.txt'
908
+ * ```
909
+ */
210
910
  toString(): string;
911
+
912
+ /**
913
+ * 转换为 JSON
914
+ *
915
+ * @returns 文件路径字符串
916
+ *
917
+ * @example
918
+ * ```typescript
919
+ * const file = fsd('/path/to/file.txt');
920
+ * console.log(JSON.stringify(file)); // '"/path/to/file.txt"'
921
+ * console.log(file.toJSON()); // '/path/to/file.txt'
922
+ * ```
923
+ */
211
924
  toJSON(): string;
212
925
  }
213
926
 
214
927
  /**
215
- * 文件分片选项
928
+ * 分配文件路径选项
929
+ *
930
+ * 主要用于 VOD 适配器,在上传前分配一个 Video ID 作为文件路径。
931
+ *
932
+ * @example
933
+ * ```typescript
934
+ * // VOD 适配器
935
+ * const videoId = await adapter.alloc({ name: 'video.mp4' });
936
+ * console.log(videoId); // '/1234567890abcdef'
937
+ * const file = fsd(videoId);
938
+ * await file.write(videoData);
939
+ * ```
216
940
  */
217
941
  export interface AllocOptions {
942
+ /**
943
+ * 文件路径
944
+ */
218
945
  path?: string;
946
+
947
+ /**
948
+ * 文件名
949
+ */
219
950
  name?: string;
951
+
952
+ /**
953
+ * 文件大小(字节数)
954
+ */
220
955
  size?: number;
956
+
957
+ /**
958
+ * 其他扩展选项
959
+ */
221
960
  [key: string]: any;
222
961
  }
223
962
 
963
+ /**
964
+ * 存储适配器抽象类
965
+ *
966
+ * 所有存储后端(本地文件系统、云存储等)必须实现此接口。
967
+ * 适配器负责与底层存储系统交互,实现具体的存储逻辑。
968
+ *
969
+ * @typeParam T - 适配器配置选项的类型
970
+ *
971
+ * @remarks
972
+ * ### 适配器实现要点
973
+ * 1. 必须设置 `instanceOfFSDAdapter = true`(类型守卫)
974
+ * 2. 必须设置 `name` 属性(适配器名称)
975
+ * 3. 必须设置 `needEnsureDir` 属性(是否需要预先创建目录)
976
+ * 4. 实现所有文件和目录操作方法
977
+ *
978
+ * ### 内置适配器
979
+ * - `FSAdapter` - 本地文件系统适配器(fsd-fs 包)
980
+ * - `OSSAdapter` - 阿里云 OSS 适配器(fsd-oss 包)
981
+ * - `VODAdapter` - 阿里云 VOD 适配器(fsd-vod 包)
982
+ *
983
+ * @example
984
+ * ```typescript
985
+ * class CustomAdapter extends Adapter<CustomOptions> {
986
+ * readonly instanceOfFSDAdapter = true;
987
+ * readonly name = 'CustomAdapter';
988
+ * readonly needEnsureDir = false;
989
+ *
990
+ * constructor(options: CustomOptions) {
991
+ * super(options);
992
+ * // 初始化逻辑
993
+ * }
994
+ *
995
+ * async write(path: string, data: any): Promise<void> {
996
+ * // 实现写入逻辑
997
+ * }
998
+ *
999
+ * async read(path: string): Promise<any> {
1000
+ * // 实现读取逻辑
1001
+ * }
1002
+ *
1003
+ * // ... 实现其他方法
1004
+ * }
1005
+ * ```
1006
+ */
224
1007
  export class Adapter<T> {
1008
+ /**
1009
+ * 类型守卫:是否为 Adapter 实例
1010
+ */
225
1011
  readonly instanceOfFSDAdapter: true;
1012
+
1013
+ /**
1014
+ * 适配器名称
1015
+ *
1016
+ * 用于识别适配器类型,如 'FSAdapter', 'OSSAdapter', 'VODAdapter'
1017
+ */
226
1018
  readonly name: string;
1019
+
227
1020
  /**
228
- * 适配器是否需要确保文件夹存在
229
- * FS适配器如果父文件夹不存在会报错,而OSS适配器不会
1021
+ * 是否需要确保文件夹存在
1022
+ *
1023
+ * - `true`: 适配器需要预先创建目录(如 FSAdapter)
1024
+ * - `false`: 适配器自动处理目录(如 OSSAdapter, VODAdapter)
1025
+ *
1026
+ * @remarks
1027
+ * 当 `needEnsureDir = true` 时,在写入文件前应该先调用 `mkdir()` 创建目录。
230
1028
  */
231
1029
  readonly needEnsureDir: boolean;
1030
+
232
1031
  /**
233
- * 分配文件路径
234
- * Vod上传前必须调用此方法分配一个VideoID作为文件path,不能直接使用 fsd(path)
1032
+ * 分配文件路径(可选)
1033
+ *
1034
+ * 某些适配器(如 VOD)需要在上传前分配一个 ID 作为文件路径。
1035
+ * 对于这些适配器,不能直接使用 `fsd(path)`,必须先调用此方法。
1036
+ *
1037
+ * @param options - 分配选项
1038
+ * @returns 分配的文件路径
1039
+ *
1040
+ * @example
1041
+ * ```typescript
1042
+ * // VOD 适配器必须先 alloc
1043
+ * const videoId = await adapter.alloc({ name: 'video.mp4' });
1044
+ * const file = fsd(videoId);
1045
+ * await file.write(videoData);
1046
+ * ```
235
1047
  */
236
1048
  alloc?: (options?: AllocOptions) => Promise<string>;
1049
+
237
1050
  /**
238
- * 创建上传凭证,如果支持边缘上传,否则不存在
239
- * @param path 文件路径
240
- * @param {any} [meta] 文件元数据
241
- * @param {number} [durationSeconds] 令牌有效时长,单位秒
1051
+ * 创建上传凭证(可选)
1052
+ *
1053
+ * 支持边缘上传的适配器(如 OSS)提供此方法。
1054
+ * 用于生成临时凭证,让客户端直接上传到云存储,无需通过服务器中转。
1055
+ *
1056
+ * @param path - 文件路径
1057
+ * @param meta - 文件元数据
1058
+ * @param durationSeconds - 令牌有效期(秒)
1059
+ * @returns 上传凭证对象
1060
+ *
1061
+ * @example
1062
+ * ```typescript
1063
+ * // OSS 适配器创建 STS 临时凭证
1064
+ * const token = await adapter.createUploadToken(
1065
+ * '/uploads/file.txt',
1066
+ * { contentType: 'image/jpeg' },
1067
+ * 3600 // 1 小时有效期
1068
+ * );
1069
+ *
1070
+ * // 将 token 发送给前端
1071
+ * res.json({ uploadToken: token });
1072
+ *
1073
+ * // 前端使用临时凭证直接上传
1074
+ * const oss = new OSS({
1075
+ * region: 'oss-cn-hangzhou',
1076
+ * accessKeyId: token.credentials.accessKeyId,
1077
+ * accessKeySecret: token.credentials.accessKeySecret,
1078
+ * stsToken: token.credentials.securityToken,
1079
+ * bucket: 'my-bucket'
1080
+ * });
1081
+ * await oss.putObject('/uploads/file.txt', fileData);
1082
+ * ```
242
1083
  */
243
1084
  createUploadToken?: (path: string, meta?: any, durationSeconds?: number) => Promise<any>;
244
1085
 
1086
+ /**
1087
+ * 适配器构造函数
1088
+ *
1089
+ * @param options - 适配器配置选项
1090
+ */
245
1091
  constructor(options: T);
1092
+
1093
+ /**
1094
+ * 向文件追加内容
1095
+ *
1096
+ * @param path - 文件路径
1097
+ * @param data - 要追加的内容
1098
+ */
246
1099
  append(path: string, data: string | Buffer | NodeJS.ReadableStream): Promise<void>;
1100
+
1101
+ /**
1102
+ * 创建可读流
1103
+ *
1104
+ * @param path - 文件路径
1105
+ * @param options - 流选项
1106
+ */
247
1107
  createReadStream(path: string, options?: ReadStreamOptions): Promise<NodeJS.ReadableStream>;
1108
+
1109
+ /**
1110
+ * 创建可写流
1111
+ *
1112
+ * @param path - 文件路径
1113
+ * @param options - 流选项
1114
+ * @returns 可写流(可能包含 promise 属性)
1115
+ */
248
1116
  createWriteStream(
249
1117
  path: string,
250
1118
  options?: WriteStreamOptions
251
1119
  ): Promise<NodeJS.WritableStream & WithPromise>;
1120
+
1121
+ /**
1122
+ * 删除文件或目录
1123
+ *
1124
+ * @param path - 文件或目录路径
1125
+ */
252
1126
  unlink(path: string): Promise<void>;
1127
+
1128
+ /**
1129
+ * 创建目录
1130
+ *
1131
+ * @param path - 目录路径
1132
+ * @param recursive - 是否递归创建父目录
1133
+ */
253
1134
  mkdir(path: string, recursive?: boolean): Promise<void>;
1135
+
1136
+ /**
1137
+ * 读取目录内容
1138
+ *
1139
+ * @param path - 目录路径
1140
+ * @param recursion - 递归选项
1141
+ * @returns 文件列表,每个文件包含名称和元数据
1142
+ */
254
1143
  readdir(
255
1144
  path: string,
256
1145
  recursion?: true | string | any
257
1146
  ): Promise<Array<{ name: string; metadata?: FileMetadata }>>;
1147
+
1148
+ /**
1149
+ * 创建访问 URL
1150
+ *
1151
+ * @param path - 文件路径
1152
+ * @param options - URL 选项
1153
+ * @returns 可访问的 URL
1154
+ */
258
1155
  createUrl(path: string, options?: CreateUrlOptions): Promise<string>;
1156
+
1157
+ /**
1158
+ * 拷贝文件或目录
1159
+ *
1160
+ * @param path - 源路径
1161
+ * @param dest - 目标路径
1162
+ */
259
1163
  copy(path: string, dest: string): Promise<void>;
1164
+
1165
+ /**
1166
+ * 重命名文件或目录
1167
+ *
1168
+ * @param path - 源路径
1169
+ * @param dest - 目标路径
1170
+ */
260
1171
  rename(path: string, dest: string): Promise<void>;
1172
+
1173
+ /**
1174
+ * 判断文件或目录是否存在
1175
+ *
1176
+ * @param path - 文件或目录路径
1177
+ */
261
1178
  exists(path: string): Promise<boolean>;
1179
+
1180
+ /**
1181
+ * 判断路径是否为文件
1182
+ *
1183
+ * @param path - 文件路径
1184
+ */
262
1185
  isFile(path: string): Promise<boolean>;
1186
+
1187
+ /**
1188
+ * 判断路径是否为目录
1189
+ *
1190
+ * @param path - 目录路径
1191
+ */
263
1192
  isDirectory(path: string): Promise<boolean>;
1193
+
1194
+ /**
1195
+ * 获取文件大小
1196
+ *
1197
+ * @param path - 文件路径
1198
+ * @returns 文件大小(字节数)
1199
+ */
264
1200
  size(path: string): Promise<number>;
1201
+
1202
+ /**
1203
+ * 获取文件最后修改时间
1204
+ *
1205
+ * @param path - 文件路径
1206
+ * @returns 最后修改时间
1207
+ */
265
1208
  lastModified(path: string): Promise<Date>;
1209
+
266
1210
  /**
267
- * 初始化一个多Part上传任务
268
- * @param {string} path 目标路径
269
- * @param {number} partCount Part数量
1211
+ * 初始化分片上传任务
1212
+ *
1213
+ * @param path - 目标文件路径
1214
+ * @param partCount - 分片数量
1215
+ * @returns 任务 ID 数组
270
1216
  */
271
1217
  initMultipartUpload(path: string, partCount: number): Promise<Task[]>;
1218
+
272
1219
  /**
273
- * 上传Part
274
- * 此方法调用时可不必先调用initMultipartUpload,能直接使用其他File对象initMultipartUpload方法返回的Task
275
- * @param {string} path 目标路径
276
- * @param {Task} partTask Part任务
277
- * @param {Stream} data 数据流
278
- * @param {number} size 数据块大小
1220
+ * 上传分片
1221
+ *
1222
+ * @param path - 目标文件路径
1223
+ * @param partTask - 分片任务 ID
1224
+ * @param data - 分片数据流
1225
+ * @param size - 分片大小(字节数)
1226
+ * @returns 分片信息
279
1227
  */
280
1228
  writePart(path: string, partTask: Task, data: NodeJS.ReadableStream, size: number): Promise<Part>;
1229
+
281
1230
  /**
282
- * 完成多Part上传
283
- * 此方法调用时可不必先调用writePart,能直接使用其他File对象writePart方法返回的Part
284
- * parts 参数顺序可以和initMultipartUpload返回的列表顺序不一样
285
- * @param {string} path 目标路径
286
- * @param {Part[]} parts
1231
+ * 完成分片上传
1232
+ *
1233
+ * @param path - 目标文件路径
1234
+ * @param parts - 分片信息数组
287
1235
  */
288
1236
  completeMultipartUpload(path: string, parts: Part[]): Promise<void>;
289
1237
  }
290
1238
 
1239
+ /**
1240
+ * 文件生成器函数
1241
+ *
1242
+ * FSD 工厂函数的返回类型,用于创建 FSDFile 实例。
1243
+ *
1244
+ * @remarks
1245
+ * 这是一个函数类型,调用时传入路径返回 FSDFile 对象。
1246
+ * 同时也是一个对象,包含 `adapter` 属性引用底层适配器。
1247
+ *
1248
+ * @example
1249
+ * ```typescript
1250
+ * const fsd = FSD({ adapter: new FSAdapter({ root: '/uploads' }) });
1251
+ *
1252
+ * // 作为函数调用:创建文件对象
1253
+ * const file = fsd('/test.txt');
1254
+ *
1255
+ * // 访问 adapter 属性
1256
+ * console.log(fsd.adapter.name); // 'FSAdapter'
1257
+ * ```
1258
+ */
291
1259
  export interface FileGenerator {
1260
+ /**
1261
+ * 底层存储适配器
1262
+ */
292
1263
  adapter: Adapter<any>;
1264
+
1265
+ /**
1266
+ * 创建文件对象
1267
+ *
1268
+ * @param path - 文件或目录路径
1269
+ * @returns FSDFile 实例
1270
+ */
293
1271
  (path: string): FSDFile;
294
1272
  }
295
1273
 
1274
+ /**
1275
+ * FSD 工厂函数
1276
+ *
1277
+ * 创建文件系统驱动实例的入口函数。
1278
+ *
1279
+ * @param options - 配置选项,必须指定存储适配器
1280
+ * @returns 文件生成器函数
1281
+ *
1282
+ * @example
1283
+ * ```typescript
1284
+ * import FSD from 'fsd';
1285
+ * import FSAdapter from 'fsd-fs';
1286
+ * import OSSAdapter from 'fsd-oss';
1287
+ *
1288
+ * // 使用本地文件系统
1289
+ * const fsd = FSD({
1290
+ * adapter: new FSAdapter({ root: '/uploads' })
1291
+ * });
1292
+ *
1293
+ * // 使用阿里云 OSS
1294
+ * const fsd = FSD({
1295
+ * adapter: new OSSAdapter({
1296
+ * accessKeyId: 'xxx',
1297
+ * accessKeySecret: 'xxx',
1298
+ * region: 'oss-cn-hangzhou',
1299
+ * bucket: 'my-bucket'
1300
+ * })
1301
+ * });
1302
+ *
1303
+ * // 创建文件对象
1304
+ * const file = fsd('/test.txt');
1305
+ * await file.write('Hello, FSD!');
1306
+ * ```
1307
+ */
296
1308
  export default function FSD(options: DriverOptions): FileGenerator;