npm package: @lark-apaas/file-service
Description: File Service Core Package
Latest version: 0.1.2

# @lark-apaas/file-service

File Service Core Package - 一个基于 TypeScript 实现的 Node.js 文件服务 SDK,提供文件上传、下载、管理等完整功能。

## 特性

- TypeScript 编写,提供完整的类型定义
- 支持文件上传(支持 Buffer、Blob、File、Stream 等多种格式)
- 支持文件下载(Blob 和 Stream 两种模式,Blob 模式限制 50MB)
- 文件列表查询和搜索
- 文件元数据获取
- 文件删除
- 签名 URL 生成
- 获取当前应用对应的 bucket

## 环境要求

- Node.js >= 18.0.0

## 安装

```bash
npm install @lark-apaas/file-service
```

或使用 yarn:

```bash
yarn add @lark-apaas/file-service
```

## 快速开始

```typescript
import { FileService } from '@lark-apaas/file-service';

// 创建 FileService 实例
const fileService = new FileService(fetchClient);

// 上传文件
const result = await fileService.upload({
  appId: 'your-app-id',
  bucketId: 'your-bucket-id',
  fileBody: file,
  options: {
    fileName: 'example.txt',
    contentType: 'text/plain'
  }
});

// 下载文件
const { content, metadata } = await fileService.download({
  appId: 'your-app-id',
  bucketId: 'your-bucket-id',
  filePath: 'path/to/file.txt'
});
```

## API 文档

### 初始化

#### `new FileService(fetchClient: FetchClient)`

创建 FileService 实例。

**参数:**

- `fetchClient: FetchClient` - HTTP 客户端实例

```typescript
interface FetchClient {
  request(config: RequestInit): Promise<Response>;
}
```

---

### 文件上传

#### `upload(params: UploadParams): Promise<CallbackData | undefined>`

上传文件到指定的 bucket。

**参数:**

```typescript
interface UploadParams {
  appId: string;          // 应用 ID
  bucketId: string;       // 存储桶 ID
  fileBody: FileBody;     // 文件内容
  options?: UploadOptions; // 上传选项
}

type FileBody =
  | ArrayBuffer
  | Blob
  | Buffer
  | File
  | NodeJS.ArrayBufferView
  | NodeJS.ReadableStream      // Node.js 可读流
  | ReadableStream<Uint8Array> // Web 可读流
  | string;

interface UploadOptions {
  filePath?: string;           // 文件路径(可选)
  fileName?: string;           // 文件名(可选)
  cacheControl?: string | number; // 缓存控制(可选)
  contentType?: string;        // 内容类型(可选)
  upsert?: boolean;            // 是否覆盖已存在的文件(可选)
  contentDisposition?: string; // 内容处置(可选)
}
```

**返回值:**

```typescript
interface CallbackData {
  bucketID: string;    // 存储桶 ID
  filePath: string;    // 文件路径
  downloadURL: string; // 下载 URL
  fileName: string;    // 文件名
}
```

**示例:**

```typescript
const result = await fileService.upload({
  appId: 'app-123',
  bucketId: 'bucket-456',
  fileBody: fileBuffer,
  options: {
    fileName: 'document.pdf',
    contentType: 'application/pdf',
    upsert: true
  }
});

console.log(result.downloadURL); // 文件下载地址
```

---

### 文件下载

#### `download(params: DownloadParams): FileDownloadBuilder`

下载文件,返回 Builder 对象支持链式调用。

**参数:**

```typescript
interface DownloadParams {
  appId: string;    // 应用 ID
  bucketId: string; // 存储桶 ID
  filePath: string; // 文件路径
}
```

**返回值:**

返回 `FileDownloadBuilder` 实例,支持以下两种使用方式:

##### 1. 作为 Promise 使用(默认返回 Blob)

```typescript
Promise<DownloadResult<Blob>>

interface DownloadResult<T> {
  content: T;                  // 文件内容
  metadata: FileMeta | null;   // 文件元数据
}
```

**限制:**

- Blob 模式下文件大小不能超过 50MB,超过 50MB 请使用 `asStream()` 方法

##### 2. 使用 `asStream()` 方法获取流

```typescript
download(params).asStream(): Promise<DownloadResult<ReadableStream>>
```

**FileMeta 类型定义:**

```typescript
interface FileMeta {
  id: string;           // 文件 ID
  name: string;         // 文件名
  filePath: string;     // 文件路径
  metadata: {
    contentLength: string; // 文件大小
    mimeType: string;      // MIME 类型
  } & Record<string, string>;
  downloadURL: string;  // 下载 URL
  createdAt: string;    // 创建时间
  createdBy: User;      // 创建者
  updatedAt: string;    // 更新时间
  updatedBy: User;      // 更新者
  bucketID: string;     // 存储桶 ID
}

interface User {
  avatar: string;    // 用户头像
  status: UserStatus; // 用户状态
  userID: number;    // 用户 ID
  email: string;     // 用户邮箱
  name: string;      // 用户名称
  userType: string;  // 用户类型
}

enum UserStatus {
  active = 1,
  inactive,
}
```

**示例:**

```typescript
// 方式 1: 下载为 Blob (文件大小需小于 50MB)
const { content, metadata } = await fileService.download({
  appId: 'app-123',
  bucketId: 'bucket-456',
  filePath: 'documents/file.pdf'
});

console.log(metadata.name); // 文件名
console.log(metadata.metadata.contentLength); // 文件大小

// 方式 2: 下载为 Stream (推荐用于大文件)
const { content: stream, metadata } = await fileService.download({
  appId: 'app-123',
  bucketId: 'bucket-456',
  filePath: 'documents/large-file.pdf'
}).asStream();

// 使用流
stream.pipeTo(writableStream);
```

---

### 创建签名 URL

#### `createSignedUrl(params: SignedUrlParams): Promise<string>`

为文件创建临时访问的签名 URL。

**参数:**

```typescript
interface SignedUrlParams {
  appId: string;     // 应用 ID
  bucketId: string;  // 存储桶 ID
  filePath: string;  // 文件路径
  expiresIn: number; // 过期时间(秒)
}
```

**返回值:**

```typescript
string // 签名后的 URL
```

**示例:**

```typescript
const signedUrl = await fileService.createSignedUrl({
  appId: 'app-123',
  bucketId: 'bucket-456',
  filePath: 'documents/file.pdf',
  expiresIn: 3600 // 1小时后过期
});

console.log(signedUrl); // https://...
```

---

### 文件列表

#### `list(params: ListParams): Promise<ListResponse>`

获取指定前缀的文件列表。

**参数:**

```typescript
interface ListParams {
  appId: string;              // 应用 ID
  bucketId: string;           // 存储桶 ID
  prefix: string;             // 文件路径前缀
  searchOptions?: SearchOptions; // 搜索选项(可选)
}

interface SearchOptions {
  /**
   * 返回的文件数量限制
   * @default 100
   */
  maxKeys?: number;

  /**
   * 分页标记,用于获取下一页
   */
  continuationToken?: string;

  /**
   * 排序选项
   */
  sortBy?: SortBy;
}

interface SortBy {
  column?: string; // 排序列名
  order?: string;  // 排序顺序(asc/desc)
}
```

**返回值:**

```typescript
interface ListResponse {
  attachments: FileMeta[]; // 文件列表
  hasMore: boolean;        // 是否还有更多文件
}
```

**示例:**

```typescript
// 基础查询
const result = await fileService.list({
  appId: 'app-123',
  bucketId: 'bucket-456',
  prefix: 'documents/'
});

console.log(result.attachments); // 文件数组
console.log(result.hasMore); // 是否有更多

// 带分页和排序
const result = await fileService.list({
  appId: 'app-123',
  bucketId: 'bucket-456',
  prefix: 'documents/',
  searchOptions: {
    maxKeys: 50,
    sortBy: {
      column: 'createdAt',
      order: 'desc'
    }
  }
});
```

---

### 删除文件

#### `remove(params: RemoveParams): Promise<FileMeta[]>`

删除指定的文件。

**参数:**

```typescript
interface RemoveParams {
  appId: string;       // 应用 ID
  bucketId: string;    // 存储桶 ID
  filePaths: string[]; // 要删除的文件路径数组
}
```

**返回值:**

```typescript
FileMeta[] // 已删除的文件元数据数组
```

**示例:**

```typescript
const deletedFiles = await fileService.remove({
  appId: 'app-123',
  bucketId: 'bucket-456',
  filePaths: [
    'documents/file1.pdf',
    'documents/file2.pdf'
  ]
});

console.log(`已删除 ${deletedFiles.length} 个文件`);
```

---

### 获取文件元数据

#### `getFileMetadata(params: MetadataParams): Promise<FileMeta | null>`

获取指定文件的元数据信息。

**参数:**

```typescript
interface MetadataParams {
  appId: string;    // 应用 ID
  bucketId: string; // 存储桶 ID
  filePath: string; // 文件路径
}
```

**返回值:**

```typescript
FileMeta | null // 文件元数据,如果文件不存在则返回 null
```

**示例:**

```typescript
const metadata = await fileService.getFileMetadata({
  appId: 'app-123',
  bucketId: 'bucket-456',
  filePath: 'documents/file.pdf'
});

if (metadata) {
  console.log(metadata.name); // 文件名
  console.log(metadata.metadata.contentLength); // 文件大小
  console.log(metadata.metadata.mimeType); // MIME 类型
  console.log(metadata.createdAt); // 创建时间
} else {
  console.log('文件不存在');
}
```

---

### 获取默认 Bucket

#### `getDefaultBucket(appId: string): Promise<string>`

获取应用的默认存储桶 ID。

**参数:**

```typescript
appId: string // 应用 ID
```

**返回值:**

```typescript
string // 默认存储桶 ID
```

**示例:**

```typescript
const defaultBucketId = await fileService.getDefaultBucket('app-123');
console.log(defaultBucketId); // bucket-456
```

---

## 类型定义

### 完整类型导出

```typescript
export {
  FileService,
  FileDownloadBuilder,
  StreamDownloadBuilder,
  // 类型
  FileBody,
  ApiResponse,
  User,
  UserStatus,
  FileMeta,
  UploadOptions,
  SortBy,
  SearchOptions,
  FetchClient,
  RequestConfig,
  CustomError,
  DownloadResult,
  PreUploadResponse,
  CallbackResponse,
  GetPublishedV2Response,
  DownloadInnerResponse,
  ListResponse,
  RemoveResponse,
  GetMetaResponse
};
```

---

## 错误处理

SDK 可能抛出以下错误:

### HttpError

HTTP 请求失败时抛出。

```typescript
class HttpError extends Error {
  status: string | number; // HTTP 状态码或错误代码
  message: string;         // 错误消息
  body?: string;           // 响应体内容(可选)
}
```

### 常见错误

- **Blob 下载超限**: `Can't download file blob larger than 50MB, use asStream() instead.`
- **上传失败**: `preUpload failed, uploadURL or uploadID is empty`
- **下载失败**: `download failed, status: xxx`
- **Bucket 不存在**: `No bucket found from remote for appId xxx`
- **文件不存在**: 返回 `null` 而不抛出错误

**示例:**

```typescript
try {
  await fileService.upload({
    appId: 'app-123',
    bucketId: 'bucket-456',
    fileBody: largeFile
  });
} catch (error) {
  if (error instanceof Error) {
    console.error(error.message);
  }
}
```

---

## 高级用法

### 自定义 FetchClient

```typescript
import { FileService } from '@lark-apaas/file-service';

const customFetchClient = {
  async request(config: RequestInit): Promise<Response> {
    // 自定义请求逻辑,例如添加认证头
    const headers = {
      ...config.headers,
      'Authorization': 'Bearer your-token'
    };

    return fetch(config.url, {
      ...config,
      headers
    });
  }
};

const fileService = new FileService(customFetchClient);
```

### 流式上传

支持使用 Node.js Readable Stream 或 Web ReadableStream 上传文件:

```typescript
import { Readable } from 'stream';

// 方式 1: 使用 Node.js Readable Stream
const nodeStream = Readable.from(['Hello', ' ', 'World']);
await fileService.upload({
  appId: 'app-123',
  bucketId: 'bucket-456',
  fileBody: nodeStream,
  options: {
    fileName: 'stream-file.txt',
    contentType: 'text/plain'
  }
});

// 方式 2: 使用自定义 Readable Stream
const customStream = new Readable({
  read() {
    this.push('custom data');
    this.push(null); // 结束流
  }
});
await fileService.upload({
  appId: 'app-123',
  bucketId: 'bucket-456',
  fileBody: customStream,
  options: { fileName: 'custom-stream.txt' }
});

// 方式 3: 使用生成器创建流
async function* dataGenerator() {
  yield 'chunk1';
  yield 'chunk2';
  yield 'chunk3';
}
const generatorStream = Readable.from(dataGenerator());
await fileService.upload({
  appId: 'app-123',
  bucketId: 'bucket-456',
  fileBody: generatorStream,
  options: { fileName: 'generator-file.txt' }
});
```

### 流式下载大文件

```typescript
// 下载大文件并保存到磁盘
const { content: stream } = await fileService.download({
  appId: 'app-123',
  bucketId: 'bucket-456',
  filePath: 'large-file.zip'
}).asStream();

// Node.js 环境
import { createWriteStream } from 'fs';
import { Readable } from 'stream';

const fileStream = createWriteStream('output.zip');
Readable.fromWeb(stream).pipe(fileStream);
```

### 批量操作

```typescript
// 批量上传
const files = [file1, file2, file3];
const uploadPromises = files.map(file =>
  fileService.upload({
    appId: 'app-123',
    bucketId: 'bucket-456',
    fileBody: file,
    options: { fileName: file.name }
  })
);
const results = await Promise.all(uploadPromises);

// 批量删除
const filePaths = results.map(r => r.filePath);
await fileService.remove({
  appId: 'app-123',
  bucketId: 'bucket-456',
  filePaths
});
```

---

## 许可证

MIT

---

## 更新日志

### 0.0.1-alpha.10

- 初始发布版本
- 支持文件上传、下载、列表、删除等核心功能
- 提供完整的 TypeScript 类型定义