Skip to content

Commit

Permalink
feat(bos)!: 支持绑定bucket和object调用API
Browse files Browse the repository at this point in the history
BREAKING CHANGE: 放弃对浏览器的兼容
  • Loading branch information
otakustay committed Sep 7, 2022
1 parent 81a4920 commit 7d1d4e0
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 127 deletions.
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
1. 全TypeScript实现,带类型。
2. 只支持HTTPS协议,什么年代了去他的裸HTTP吧。
3. 摆脱低版本Node运行的需求,使用新语方特性,诸如`URLSearchParams`
4. 一定程度上支持浏览器中运行,只要你的浏览器够新
4. 原则上不支持在浏览器里使用
5. ESM和CommonJS双构建,支持Tree Shaking。
6. 基于原生`fetch``Stream`而得的更好的性能。

Expand Down Expand Up @@ -40,9 +40,3 @@ console.log(response.body.contents);
具体请参考各个客户端类的类型,基本就能知道怎么用了。

API文档建设中……

## 浏览器运行的要求

1. 必须支持`crypto.subtle``fetch`
2. 部分API只能在Node中使用,如`BosClient#putObjectFromFile`,这些API使用了异步的`await import('node:fs')`来避开在浏览器中直接执行。如果你使用Vite等构建工具,请将`node:*`模块置空。
3. 我们不保证在未来永远可以用于浏览器,但不适用于浏览器的情况下会发布大版本。
67 changes: 67 additions & 0 deletions src/bos/bucket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {Http} from '../shared/index.js';
import {BosObjectClient} from './object.js';

export interface ListObjectOptions {
delimiter?: string;
marker?: string;
maxKeys?: number;
prefix?: string;
}

export interface ObjectOwner {
id: string;
displayName: string;
}

export interface ObjectContent {
key: string;
lastModified: string;
eTag: string;
size: number;
storageClass: 'STANDARD' | 'COLD';
owner: ObjectOwner;
}

export interface CommonPrefix {
prefix: string;
}

export interface ListObjectResponse {
name: string;
prefix: string;
delimiter: string;
commonPrefixes?: CommonPrefix[];
isTruncated: boolean;
maxKeys: number;
marker: string;
nextMarker: string;
contents: ObjectContent[];
}

export class BosBucketClient {
private readonly http: Http;

constructor(http: Http, hostBase: string, bucketName: string) {
this.http = http.withHeaders({host: `${bucketName}.${hostBase}`});
}

withObject(objectKey: string) {
return new BosObjectClient(this.http, objectKey);
}

async listObjects(options?: ListObjectOptions) {
const response = await this.http.json<ListObjectResponse>(
'GET',
'/',
{
params: {
delimiter: options?.delimiter,
marker: options?.marker,
maxKeys: options?.maxKeys,
prefix: options?.prefix,
},
}
);
return response;
}
}
125 changes: 22 additions & 103 deletions src/bos/index.ts
Original file line number Diff line number Diff line change
@@ -1,137 +1,56 @@
import type {ReadStream} from 'node:fs';
import {RegionClientOptions, Http} from '../shared/index.js';
import {BosBucketClient, ListObjectOptions} from './bucket.js';
import {ObjectBody, PutObjectOptions} from './object.js';

// https://cloud.baidu.com/doc/BOS/s/Rjwvysdnp

export interface ListObjectOptions {
delimiter?: string;
marker?: string;
maxKeys?: number;
prefix?: string;
}

export interface ObjectOwner {
id: string;
displayName: string;
}

export interface ObjectContent {
key: string;
lastModified: string;
eTag: string;
size: number;
storageClass: 'STANDARD' | 'COLD';
owner: ObjectOwner;
}

export interface CommonPrefix {
prefix: string;
}
export type {ObjectBody, PutObjectOptions} from './object.js';
export type {CommonPrefix, ListObjectOptions, ListObjectResponse, ObjectContent, ObjectOwner} from './bucket.js';

export interface ListObjectResponse {
name: string;
prefix: string;
delimiter: string;
commonPrefixes?: CommonPrefix[];
isTruncated: boolean;
maxKeys: number;
marker: string;
nextMarker: string;
contents: ObjectContent[];
}

export interface PutObjectOptions {
headers?: Record<string, string>;
}

export type ObjectBody = string | Blob | ArrayBuffer | ReadStream;
// https://cloud.baidu.com/doc/BOS/s/Rjwvysdnp

export type BosOptions = RegionClientOptions;

export class BosClient {
private readonly hostBase: string;
private readonly http: Http;
protected readonly hostBase: string;
protected readonly http: Http;

constructor(options: BosOptions) {
this.hostBase = `${options.region}.bcebos.com`;
this.http = Http.fromEndpoint(this.hostBase, options);
}

withBucket(bucketName: string) {
return new BosBucketClient(this.http, this.hostBase, bucketName);
}

withObject(bucketName: string, objectKey: string) {
return this.withBucket(bucketName).withObject(objectKey);
}

async listObjects(bucketName: string, options?: ListObjectOptions) {
const response = await this.http.json<ListObjectResponse>(
'GET',
'/',
{
params: {
delimiter: options?.delimiter,
marker: options?.marker,
maxKeys: options?.maxKeys,
prefix: options?.prefix,
},
headers: {
'content-type': 'application/json',
host: `${bucketName}.${this.hostBase}`,
},
}
);
return response;
return this.withBucket(bucketName).listObjects(options);
}

async getObject(bucketName: string, key: string) {
const response = await this.http.text(
'GET',
`/${key}`,
{headers: {host: `${bucketName}.${this.hostBase}`}}
);
return response;
return this.withObject(bucketName, key).get();
}

async getObjectAsBlob(bucketName: string, key: string) {
const response = await this.http.blob(
'GET',
`/${key}`,
{headers: {host: `${bucketName}.${this.hostBase}`}}
);
return response;
return this.withObject(bucketName, key).getAsBlob();
}

async getObjectAsStream(bucketName: string, key: string) {
const response = await this.http.stream(
'GET',
`/${key}`,
{headers: {host: `${bucketName}.${this.hostBase}`}}
);
return response;
return this.withObject(bucketName, key).getAsStream();
}

async putObject(bucketName: string, key: string, body: ObjectBody, options?: PutObjectOptions) {
const response = await this.http.noContent(
'PUT',
`/${key}`,
{
body,
headers: {
...options?.headers,
host: `${bucketName}.${this.hostBase}`,
},
}
);
return response;
return this.withObject(bucketName, key).put(body, options);
}

async putObjectFromFile(bucketName: string, key: string, file: string, options?: PutObjectOptions) {
const fs = await import('node:fs');
const stream = fs.createReadStream(file);
const response = await this.putObject(bucketName, key, stream, options);
return response;
return this.withObject(bucketName, key).putFromFile(file, options);
}

async deleteObject(bucketName: string, key: string) {
const response = await this.http.noContent(
'DELETE',
`/${key}`,
{headers: {host: `${bucketName}.${this.hostBase}`}}
);
return response;
return this.withObject(bucketName, key).delete();
}
}
63 changes: 63 additions & 0 deletions src/bos/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import fs from 'node:fs';
import {Readable} from 'node:stream';
import {Http} from '../shared/index.js';

export interface PutObjectOptions {
headers?: Record<string, string>;
}

export type ObjectBody = string | Blob | ArrayBuffer | Readable;

export class BosObjectClient {
private readonly http: Http;
private readonly objectKey: string;

constructor(http: Http, objectKey: string) {
this.http = http;
this.objectKey = objectKey;
}

async get() {
const response = await this.http.text('GET', `/${this.objectKey}`);
return response;
}

async getAsBlob() {
const response = await this.http.blob('GET', `/${this.objectKey}`);
return response;
}

async getAsStream() {
const response = await this.http.stream('GET', `/${this.objectKey}`);
return {
headers: response.headers,
// @ts-expect-error 都是`WebStream`,只是Node的类型不兼容,实际是能用的
body: Readable.fromWeb(response.body),
};
}

async put(body: ObjectBody, options?: PutObjectOptions) {
const response = await this.http.noContent(
'PUT',
`/${this.objectKey}`,
{
body,
headers: {
...options?.headers,
},
}
);
return response;
}

async putFromFile(file: string, options?: PutObjectOptions) {
const stream = fs.createReadStream(file);
const response = await this.put(stream, options);
return response;
}

async delete() {
const response = await this.http.noContent('DELETE', `/${this.objectKey}`);
return response;
}
}
11 changes: 10 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
export * from './shared/index.js';
export {
RegionClientOptions,
BceCredential,
BceCredentialContext,
ClientResponse,
ClientResponseNoContent,
RequestOptions,
RequestError,
isRequestError,
} from './shared/index.js';
export * from './bls/index.js';
export * from './bos/index.js';
export * from './sts/index.js';
1 change: 1 addition & 0 deletions src/shared/authorization.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import crypto from 'node:crypto';
import {BceCredential} from './interface.js';

// https://cloud.baidu.com/doc/Reference/s/njwvz1yfu
Expand Down
Loading

0 comments on commit 7d1d4e0

Please sign in to comment.