From 27c58d148a2e3a971db456acfdae1bddcb509d31 Mon Sep 17 00:00:00 2001 From: killagu Date: Mon, 17 Nov 2025 11:52:48 +0800 Subject: [PATCH] feat: add type definitions for additional OSS object operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive type definitions and tests for multiple OSS operations: - Batch operations: deleteMulti for deleting multiple objects - Object tagging: getObjectTagging, putObjectTagging, deleteObjectTagging - Multipart upload: initMultipartUpload, completeMultipartUpload, multipartUpload, multipartUploadCopy, abortMultipartUpload, listUploads, uploadPartCopy - Append operations: append for appendable objects All methods include complete TypeScript type definitions with options and result interfaces, along with comprehensive type tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- index.test-d.ts | 362 ++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 11 +- src/index.ts | 264 +++++++++++++++++++++++++++++++++++ 3 files changed, 633 insertions(+), 4 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index 96d1abb..7a9a5d0 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -8,6 +8,8 @@ import { ListObjectResult, PutObjectOptions, PutObjectResult, + AppendObjectOptions, + AppendObjectResult, HeadObjectOptions, HeadObjectResult, GetObjectResult, @@ -18,6 +20,28 @@ import { SignatureUrlOptions, DeleteObjectOptions, DeleteObjectResult, + DeleteMultiOptions, + DeleteMultiResult, + ObjectTaggingOptions, + GetObjectTaggingResult, + PutObjectTaggingResult, + DeleteObjectTaggingResult, + InitMultipartUploadOptions, + InitMultipartUploadResult, + CompleteMultipartUploadOptions, + CompleteMultipartUploadResult, + MultipartUploadOptions, + MultipartUploadResult, + MultipartUploadCopyOptions, + AbortMultipartUploadOptions, + AbortMultipartUploadResult, + ListUploadsQuery, + ListUploadsResult, + UploadPartCopySourceData, + UploadPartCopyOptions, + UploadPartCopyResult, + PartInfo, + SourceData, IncomingHttpHeaders, } from './src/index.js'; @@ -33,6 +57,10 @@ class SimpleClient implements IObjectSimple { console.log(name, file, options); return {} as any; } + async append(name: string, file: string | Buffer | Readable, options?: AppendObjectOptions): Promise { + console.log(name, file, options); + return {} as any; + } async head(name: string, options?: HeadObjectOptions): Promise { console.log(name, options); return { @@ -62,6 +90,13 @@ class SimpleClient implements IObjectSimple { return {} as any; } + async deleteMulti(names: string[], options?: DeleteMultiOptions): Promise; + async deleteMulti(names: Array<{ key: string; versionId?: string }>, options?: DeleteMultiOptions): Promise; + async deleteMulti(names: string[] | Array<{ key: string; versionId?: string }>, options?: DeleteMultiOptions): Promise { + console.log(names, options); + return {} as any; + } + async copy(name: string, sourceName: string, options?: CopyObjectOptions): Promise; async copy(name: string, sourceName: string, sourceBucket: string, options?: CopyObjectOptions): Promise; async copy(name: string, sourceName: string, sourceBucket?: string | CopyObjectOptions, options?: CopyObjectOptions): Promise { @@ -69,6 +104,56 @@ class SimpleClient implements IObjectSimple { return {} as any; } + async getObjectTagging(name: string, options?: ObjectTaggingOptions): Promise { + console.log(name, options); + return {} as any; + } + + async putObjectTagging(name: string, tag: Record, options?: ObjectTaggingOptions): Promise { + console.log(name, tag, options); + return {} as any; + } + + async deleteObjectTagging(name: string, options?: ObjectTaggingOptions): Promise { + console.log(name, options); + return {} as any; + } + + async initMultipartUpload(name: string, options?: InitMultipartUploadOptions): Promise { + console.log(name, options); + return {} as any; + } + + async completeMultipartUpload(name: string, uploadId: string, parts: PartInfo[], options?: CompleteMultipartUploadOptions): Promise { + console.log(name, uploadId, parts, options); + return {} as any; + } + + async multipartUpload(name: string, file: string | Buffer | Readable, options?: MultipartUploadOptions): Promise { + console.log(name, file, options); + return {} as any; + } + + async multipartUploadCopy(name: string, sourceData: SourceData, options?: MultipartUploadCopyOptions): Promise { + console.log(name, sourceData, options); + return {} as any; + } + + async abortMultipartUpload(name: string, uploadId: string, options?: AbortMultipartUploadOptions): Promise { + console.log(name, uploadId, options); + return {} as any; + } + + async listUploads(query?: ListUploadsQuery, options?: RequestOptions): Promise { + console.log(query, options); + return {} as any; + } + + async uploadPartCopy(name: string, uploadId: string, partNo: number, range: string, sourceData: UploadPartCopySourceData, options?: UploadPartCopyOptions): Promise { + console.log(name, uploadId, partNo, range, sourceData, options); + return {} as any; + } + async asyncSignatureUrl(name: string, options?: SignatureUrlOptions): Promise { console.log(name, options); return ''; @@ -114,3 +199,280 @@ expectType(await simpleClient.asyncSignatureUrl('foo', { body: 'foo', }, })); + +// Test deleteMulti with string array +const deleteMultiResult1 = await simpleClient.deleteMulti(['foo', 'bar']); +expectType(deleteMultiResult1.res.status); +expectType(deleteMultiResult1.deleted.length); + +// Test deleteMulti with string array and options +const deleteMultiResult2 = await simpleClient.deleteMulti(['foo', 'bar'], { quiet: true }); +expectType(deleteMultiResult2.res.status); +expectType(deleteMultiResult2.deleted.length); + +// Test deleteMulti with object array (versioned objects) +const deleteMultiResult3 = await simpleClient.deleteMulti([ + { key: 'foo', versionId: 'v1' }, + { key: 'bar' }, +]); +expectType(deleteMultiResult3.res.status); +expectType(deleteMultiResult3.deleted.length); + +// Test deleteMulti with object array and options +const deleteMultiResult4 = await simpleClient.deleteMulti([ + { key: 'foo', versionId: 'v1' }, + { key: 'bar', versionId: 'v2' }, +], { quiet: false, timeout: 30000 }); +expectType(deleteMultiResult4.res.status); +expectType(deleteMultiResult4.deleted.length); + +// Test getObjectTagging +const getTaggingResult1 = await simpleClient.getObjectTagging('foo'); +expectType(getTaggingResult1.status); +expectType(getTaggingResult1.res.status); +expectType>(getTaggingResult1.tag); + +// Test getObjectTagging with options +const getTaggingResult2 = await simpleClient.getObjectTagging('foo', { versionId: 'v1', timeout: 30000 }); +expectType(getTaggingResult2.status); +expectType>(getTaggingResult2.tag); + +// Test putObjectTagging +const putTaggingResult1 = await simpleClient.putObjectTagging('foo', { env: 'prod', team: 'backend' }); +expectType(putTaggingResult1.status); +expectType(putTaggingResult1.res.status); + +// Test putObjectTagging with options +const putTaggingResult2 = await simpleClient.putObjectTagging('foo', { env: 'dev' }, { versionId: 'v1', timeout: 30000 }); +expectType(putTaggingResult2.status); +expectType(putTaggingResult2.res.status); + +// Test deleteObjectTagging +const deleteTaggingResult1 = await simpleClient.deleteObjectTagging('foo'); +expectType(deleteTaggingResult1.status); +expectType(deleteTaggingResult1.res.status); + +// Test deleteObjectTagging with options +const deleteTaggingResult2 = await simpleClient.deleteObjectTagging('foo', { versionId: 'v1', timeout: 30000 }); +expectType(deleteTaggingResult2.status); +expectType(deleteTaggingResult2.res.status); + +// Test initMultipartUpload +const initResult1 = await simpleClient.initMultipartUpload('foo'); +expectType(initResult1.uploadId); +expectType(initResult1.bucket); +expectType(initResult1.name); +expectType(initResult1.res.status); + +// Test initMultipartUpload with options +const initResult2 = await simpleClient.initMultipartUpload('foo', { + mime: 'text/plain', + meta: { uid: '123' }, + timeout: 30000 +}); +expectType(initResult2.uploadId); + +// Test completeMultipartUpload +const parts: PartInfo[] = [ + { number: 1, etag: 'etag1' }, + { number: 2, etag: 'etag2' } +]; +const completeResult1 = await simpleClient.completeMultipartUpload('foo', 'uploadId123', parts); +expectType(completeResult1.etag); +expectType(completeResult1.bucket); +expectType(completeResult1.name); +expectType(completeResult1.res.status); + +// Test completeMultipartUpload with callback +const completeResult2 = await simpleClient.completeMultipartUpload('foo', 'uploadId123', parts, { + callback: { + url: 'http://example.com/callback', + body: 'key=$(key)&etag=$(etag)' + } +}); +expectType(completeResult2.data); + +// Test multipartUpload with string file path +const uploadResult1 = await simpleClient.multipartUpload('foo', '/path/to/file.txt'); +expectType(uploadResult1.etag); +expectType(uploadResult1.bucket); +expectType(uploadResult1.name); +expectType(uploadResult1.res.status); + +// Test multipartUpload with Buffer +const uploadResult2 = await simpleClient.multipartUpload('foo', Buffer.from('hello'), { + partSize: 1024 * 1024, + parallel: 3, + timeout: 60000 +}); +expectType(uploadResult2.etag); + +// Test multipartUpload with Readable stream +const stream = new Readable(); +const uploadResult3 = await simpleClient.multipartUpload('foo', stream, { + mime: 'application/octet-stream', + meta: { custom: 'value' } +}); +expectType(uploadResult3.data); + +// Test multipartUploadCopy +const sourceData: SourceData = { + sourceKey: 'source.txt', + sourceBucketName: 'source-bucket', + startOffset: 0, + endOffset: 1024000 +}; +const copyResult1 = await simpleClient.multipartUploadCopy('target.txt', sourceData); +expectType(copyResult1.etag); +expectType(copyResult1.bucket); +expectType(copyResult1.res.status); + +// Test multipartUploadCopy with options +const copyResult2 = await simpleClient.multipartUploadCopy('target.txt', sourceData, { + partSize: 1024 * 1024, + versionId: 'v123', + parallel: 5 +}); +expectType(copyResult2.name); + +// Test abortMultipartUpload +const abortResult1 = await simpleClient.abortMultipartUpload('foo', 'uploadId123'); +expectType(abortResult1.res.status); + +// Test abortMultipartUpload with options +const abortResult2 = await simpleClient.abortMultipartUpload('foo', 'uploadId123', { timeout: 30000 }); +expectType(abortResult2.res.status); + +// Test listUploads without parameters +const listUploadsResult1 = await simpleClient.listUploads(); +expectType(listUploadsResult1.uploads.length); +expectType(listUploadsResult1.bucket); +expectType(listUploadsResult1.nextKeyMarker); +expectType(listUploadsResult1.nextUploadIdMarker); +expectType(listUploadsResult1.isTruncated); +expectType(listUploadsResult1.res.status); + +// Test listUploads with query +const listUploadsResult2 = await simpleClient.listUploads({ + prefix: 'uploads/', + 'max-uploads': 100 +}); +expectType(listUploadsResult2.uploads.length); + +// Test listUploads with full query parameters +const listUploadsResult3 = await simpleClient.listUploads({ + prefix: 'uploads/', + 'key-marker': 'marker1', + 'upload-id-marker': 'upload123', + 'max-uploads': '50', + 'encoding-type': 'url' +}); +expectType(listUploadsResult3.uploads[0]?.name); +expectType(listUploadsResult3.uploads[0]?.uploadId); +expectType(listUploadsResult3.uploads[0]?.initiated); + +// Test listUploads with query and options +const listUploadsResult4 = await simpleClient.listUploads( + { prefix: 'test/' }, + { timeout: 30000 } +); +expectType(listUploadsResult4.isTruncated); + +// Test append with string file path +const appendResult1 = await simpleClient.append('foo.log', '/path/to/data.txt'); +expectType(appendResult1.name); +expectType(appendResult1.url); +expectType(appendResult1.nextAppendPosition); +expectType(appendResult1.res.status); + +// Test append with position +const appendResult2 = await simpleClient.append('foo.log', Buffer.from('hello'), { + position: '1024' +}); +expectType(appendResult2.nextAppendPosition); + +// Test append with number position +const appendResult3 = await simpleClient.append('foo.log', Buffer.from('world'), { + position: 2048 +}); +expectType(appendResult3.nextAppendPosition); + +// Test append with Readable stream +const appendStream = new Readable(); +const appendResult4 = await simpleClient.append('foo.log', appendStream, { + position: '0', + mime: 'text/plain', + meta: { custom: 'value' } +}); +expectType(appendResult4.name); +expectType(appendResult4.url); + +// Test append with callback +const appendResult5 = await simpleClient.append('foo.log', Buffer.from('data'), { + position: 0, + callback: { + url: 'http://example.com/callback', + body: 'key=$(key)&etag=$(etag)' + } +}); +expectType(appendResult5.data); +expectType(appendResult5.nextAppendPosition); + +// Test uploadPartCopy basic usage +const partCopySourceData: UploadPartCopySourceData = { + sourceKey: 'source-object.txt', + sourceBucketName: 'source-bucket' +}; +const uploadPartCopyResult1 = await simpleClient.uploadPartCopy( + 'target.txt', + 'uploadId123', + 1, + '0-102400', + partCopySourceData +); +expectType(uploadPartCopyResult1.name); +expectType(uploadPartCopyResult1.etag); +expectType(uploadPartCopyResult1.res.status); + +// Test uploadPartCopy with versionId +const uploadPartCopyResult2 = await simpleClient.uploadPartCopy( + 'target.txt', + 'uploadId123', + 2, + '102401-204800', + partCopySourceData, + { versionId: 'v123' } +); +expectType(uploadPartCopyResult2.etag); + +// Test uploadPartCopy with full options +const uploadPartCopyResult3 = await simpleClient.uploadPartCopy( + 'target.txt', + 'uploadId456', + 3, + '204801-307200', + { + sourceKey: 'large-file.bin', + sourceBucketName: 'other-bucket' + }, + { + versionId: 'v456', + timeout: 60000, + headers: { + 'x-oss-server-side-encryption': 'AES256' + } + } +); +expectType(uploadPartCopyResult3.name); +expectType(uploadPartCopyResult3.etag); +expectType(uploadPartCopyResult3.res.status); + +// Test uploadPartCopy without range (empty string) +const uploadPartCopyResult4 = await simpleClient.uploadPartCopy( + 'target.txt', + 'uploadId789', + 4, + '', + partCopySourceData +); +expectType(uploadPartCopyResult4.etag); diff --git a/package.json b/package.json index 413e17f..35ef03b 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ ], "type": "module", "tshy": { + "selfLink": false, "exports": { "./package.json": "./package.json", ".": "./src/index.ts" @@ -52,12 +53,14 @@ "devDependencies": { "@eggjs/tsconfig": "^1.3.3", "@types/node": "^20.6.2", - "tsd": "^0.29.0", - "tshy": "^1.0.0", - "tshy-after": "^1.0.0" + "tsd": "^0.33.0", + "tshy": "^3.1.0", + "tshy-after": "^1.4.1" }, "dependencies": { "type-fest": "^4.3.1" }, - "types": "./dist/commonjs/index.d.ts" + "types": "./dist/commonjs/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js" } diff --git a/src/index.ts b/src/index.ts index ed68449..efecf25 100644 --- a/src/index.ts +++ b/src/index.ts @@ -121,6 +121,11 @@ export interface PutObjectResult { res: NormalSuccessResponse; } +export interface AppendObjectOptions extends PutObjectOptions { + /** append position, default is '0' */ + position?: string | number; +} + export interface AppendObjectResult { name: string; /** the url of oss */ @@ -128,6 +133,8 @@ export interface AppendObjectResult { res: NormalSuccessResponse; /** the next position */ nextAppendPosition: string; + /** only exists with callback request */ + data?: object; } export interface HeadObjectOptions extends RequestOptions { @@ -207,6 +214,202 @@ export interface DeleteObjectResult { rt: number; } +export interface DeleteMultiOptions extends RequestOptions { + /** quiet mode, if true, return empty deleted list */ + quiet?: boolean; +} + +export interface DeletedObject { + Key: string; + VersionId?: string; + DeleteMarker?: boolean; + DeleteMarkerVersionId?: string; +} + +export interface DeleteMultiResult { + res: NormalSuccessResponse; + /** deleted objects list */ + deleted: DeletedObject[]; +} + +export interface ObjectTaggingOptions extends RequestOptions { + versionId?: string; +} + +export interface GetObjectTaggingResult { + /** response status */ + status: number; + res: NormalSuccessResponse; + /** object tags, key-value pairs */ + tag: Record; +} + +export interface PutObjectTaggingResult { + /** response status */ + status: number; + res: NormalSuccessResponse; +} + +export interface DeleteObjectTaggingResult { + /** response status */ + status: number; + res: NormalSuccessResponse; +} + +export interface InitMultipartUploadOptions extends RequestOptions { + /** custom mime, will send with Content-Type entity header */ + mime?: string; + /** user meta, will send with x-oss-meta- prefix string */ + meta?: UserMeta; + headers?: IncomingHttpHeaders; +} + +export interface InitMultipartUploadResult { + res: NormalSuccessResponse; + bucket: string; + name: string; + uploadId: string; +} + +export interface PartInfo { + /** part number */ + number: number; + /** part etag */ + etag: string; +} + +export interface CompleteMultipartUploadOptions extends RequestOptions { + callback?: ObjectCallback; + headers?: IncomingHttpHeaders; + /** progress callback */ + progress?: (percent: number, checkpoint: any, res: NormalSuccessResponse) => void | Promise; +} + +export interface CompleteMultipartUploadResult { + res: NormalSuccessResponse; + bucket: string; + name: string; + etag: string; + /** callback response data */ + data?: object; +} + +export interface MultipartUploadOptions extends RequestOptions { + /** part size, default is 1MB, minimum is 100KB */ + partSize?: number; + /** custom mime */ + mime?: string; + /** user meta */ + meta?: UserMeta; + callback?: ObjectCallback; + headers?: IncomingHttpHeaders; + /** progress callback */ + progress?: (percent: number, checkpoint?: any, res?: NormalSuccessResponse) => void | Promise; + /** parallel upload parts, default is 5 */ + parallel?: number; + /** checkpoint for resumable upload */ + checkpoint?: any; +} + +export interface MultipartUploadResult { + res: NormalSuccessResponse; + bucket: string; + name: string; + etag: string; + /** callback response data */ + data?: object; +} + +export interface SourceData { + /** source object name */ + sourceKey: string; + /** source bucket name */ + sourceBucketName: string; + /** data copy start byte offset */ + startOffset?: number; + /** data copy end byte offset */ + endOffset?: number; +} + +export interface MultipartUploadCopyOptions extends RequestOptions { + /** part size for copy */ + partSize?: number; + /** version id of source object */ + versionId?: string; + /** custom headers for copy */ + copyheaders?: IncomingHttpHeaders; + headers?: IncomingHttpHeaders; + /** progress callback */ + progress?: (percent: number, checkpoint: any, res: NormalSuccessResponse) => void | Promise; + /** parallel copy parts, default is 5 */ + parallel?: number; + /** checkpoint for resumable copy */ + checkpoint?: any; +} + +export interface AbortMultipartUploadOptions extends RequestOptions {} + +export interface AbortMultipartUploadResult { + res: NormalSuccessResponse; +} + +export interface ListUploadsQuery { + /** search object using prefix key */ + prefix?: string; + /** search start from marker, including marker key */ + 'key-marker'?: string; + /** uploadId marker */ + 'upload-id-marker'?: string; + /** max uploads, default is 1000 */ + 'max-uploads'?: string | number; + /** Specifies that the object names in the response are URL-encoded. */ + 'encoding-type'?: 'url' | ''; +} + +export interface Upload { + /** object name */ + name: string; + /** upload id */ + uploadId: string; + /** upload initiated time */ + initiated: string; +} + +export interface ListUploadsResult { + res: NormalSuccessResponse; + /** upload list */ + uploads: Upload[]; + /** bucket name */ + bucket: string; + /** next key marker for pagination */ + nextKeyMarker: string; + /** next upload id marker for pagination */ + nextUploadIdMarker: string; + /** whether the list is truncated */ + isTruncated: boolean; +} + +export interface UploadPartCopySourceData { + /** source object name */ + sourceKey: string; + /** source bucket name */ + sourceBucketName: string; +} + +export interface UploadPartCopyOptions extends RequestOptions { + /** version id of source object */ + versionId?: string; + headers?: IncomingHttpHeaders; + mime?: string; +} + +export interface UploadPartCopyResult { + name: string; + /** part etag */ + etag: string; + res: NormalSuccessResponse; +} + export type HTTPMethods = 'GET' | 'POST' | 'DELETE' | 'PUT'; export interface ResponseHeaderType { @@ -266,6 +469,11 @@ export interface IObjectSimple { */ put(name: string, file: string | Buffer | Readable, options?: PutObjectOptions): Promise; + /** + * Append content to an appendable object. + */ + append(name: string, file: string | Buffer | Readable, options?: AppendObjectOptions): Promise; + /** * Head an object and get the meta info. */ @@ -287,12 +495,68 @@ export interface IObjectSimple { */ delete(name: string, options?: DeleteObjectOptions): Promise; + /** + * Delete multiple objects from the bucket. + */ + deleteMulti(names: string[], options?: DeleteMultiOptions): Promise; + deleteMulti(names: Array<{ key: string; versionId?: string }>, options?: DeleteMultiOptions): Promise; + /** * Copy an object from sourceName to name. */ copy(name: string, sourceName: string, options?: CopyObjectOptions): Promise; copy(name: string, sourceName: string, sourceBucket: string, options?: CopyObjectOptions): Promise; + /** + * Get object tagging. + */ + getObjectTagging(name: string, options?: ObjectTaggingOptions): Promise; + + /** + * Put object tagging. + */ + putObjectTagging(name: string, tag: Record, options?: ObjectTaggingOptions): Promise; + + /** + * Delete object tagging. + */ + deleteObjectTagging(name: string, options?: ObjectTaggingOptions): Promise; + + /** + * Initiate a multipart upload transaction. + */ + initMultipartUpload(name: string, options?: InitMultipartUploadOptions): Promise; + + /** + * Complete a multipart upload transaction. + */ + completeMultipartUpload(name: string, uploadId: string, parts: PartInfo[], options?: CompleteMultipartUploadOptions): Promise; + + /** + * Upload a file to OSS using multipart uploads. + */ + multipartUpload(name: string, file: string | Buffer | Readable, options?: MultipartUploadOptions): Promise; + + /** + * Multipart copy object from source bucket/object. + */ + multipartUploadCopy(name: string, sourceData: SourceData, options?: MultipartUploadCopyOptions): Promise; + + /** + * Abort a multipart upload transaction. + */ + abortMultipartUpload(name: string, uploadId: string, options?: AbortMultipartUploadOptions): Promise; + + /** + * List the on-going multipart uploads. + */ + listUploads(query?: ListUploadsQuery, options?: RequestOptions): Promise; + + /** + * Upload a part copy in a multipart from the source bucket/object. + */ + uploadPartCopy(name: string, uploadId: string, partNo: number, range: string, sourceData: UploadPartCopySourceData, options?: UploadPartCopyOptions): Promise; + /** * Signature a url for the object. * @param name