Skip to content

Commit a4363a8

Browse files
committed
refactor listobjects v1 api to ts
1 parent 91dff4b commit a4363a8

File tree

9 files changed

+326
-281
lines changed

9 files changed

+326
-281
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/internal/client.ts

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ import type {
8888
ItemBucketMetadata,
8989
LifecycleConfig,
9090
LifeCycleConfigParam,
91+
ListObjectQueryOpts,
92+
ListObjectQueryRes,
93+
ObjectInfo,
9194
ObjectLockConfigParam,
9295
ObjectLockInfo,
9396
ObjectMetaData,
@@ -115,13 +118,14 @@ import type {
115118
UploadPartConfig,
116119
} from './type.ts'
117120
import type { ListMultipartResult, UploadedPart } from './xml-parser.ts'
118-
import * as xmlParsers from './xml-parser.ts'
119121
import {
120122
parseCompleteMultipart,
121123
parseInitiateMultipart,
124+
parseListObjects,
122125
parseObjectLegalHoldConfig,
123126
parseSelectObjectContentResponse,
124127
} from './xml-parser.ts'
128+
import * as xmlParsers from './xml-parser.ts'
125129

126130
const xml = new xml2js.Builder({ renderOpts: { pretty: false }, headless: true })
127131

@@ -3005,4 +3009,131 @@ export class TypedClient {
30053009
throw err
30063010
}
30073011
}
3012+
// list a batch of objects
3013+
async listObjectsQuery(bucketName: string, prefix?: string, marker?: string, listQueryOpts?: ListObjectQueryOpts) {
3014+
if (!isValidBucketName(bucketName)) {
3015+
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
3016+
}
3017+
if (!isString(prefix)) {
3018+
throw new TypeError('prefix should be of type "string"')
3019+
}
3020+
if (!isString(marker)) {
3021+
throw new TypeError('marker should be of type "string"')
3022+
}
3023+
3024+
if (listQueryOpts && !isObject(listQueryOpts)) {
3025+
throw new TypeError('listQueryOpts should be of type "object"')
3026+
}
3027+
let { Delimiter, MaxKeys, IncludeVersion } = listQueryOpts as ListObjectQueryOpts
3028+
3029+
if (!isString(Delimiter)) {
3030+
throw new TypeError('Delimiter should be of type "string"')
3031+
}
3032+
if (!isNumber(MaxKeys)) {
3033+
throw new TypeError('MaxKeys should be of type "number"')
3034+
}
3035+
3036+
const queries = []
3037+
// escape every value in query string, except maxKeys
3038+
queries.push(`prefix=${uriEscape(prefix)}`)
3039+
queries.push(`delimiter=${uriEscape(Delimiter)}`)
3040+
queries.push(`encoding-type=url`)
3041+
3042+
if (IncludeVersion) {
3043+
queries.push(`versions`)
3044+
}
3045+
3046+
if (marker) {
3047+
marker = uriEscape(marker)
3048+
if (IncludeVersion) {
3049+
queries.push(`key-marker=${marker}`)
3050+
} else {
3051+
queries.push(`marker=${marker}`)
3052+
}
3053+
}
3054+
3055+
// no need to escape maxKeys
3056+
if (MaxKeys) {
3057+
if (MaxKeys >= 1000) {
3058+
MaxKeys = 1000
3059+
}
3060+
queries.push(`max-keys=${MaxKeys}`)
3061+
}
3062+
queries.sort()
3063+
let query = ''
3064+
if (queries.length > 0) {
3065+
query = `${queries.join('&')}`
3066+
}
3067+
3068+
const method = 'GET'
3069+
const res = await this.makeRequestAsync({ method, bucketName, query })
3070+
const body = await readAsString(res)
3071+
const listQryList = parseListObjects(body)
3072+
return listQryList
3073+
}
3074+
3075+
listObjects(
3076+
bucketName: string,
3077+
prefix?: string,
3078+
recursive?: boolean,
3079+
listOpts?: ListObjectQueryOpts | undefined,
3080+
): BucketStream<ObjectInfo> {
3081+
if (prefix === undefined) {
3082+
prefix = ''
3083+
}
3084+
if (recursive === undefined) {
3085+
recursive = false
3086+
}
3087+
if (!isValidBucketName(bucketName)) {
3088+
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
3089+
}
3090+
if (!isValidPrefix(prefix)) {
3091+
throw new errors.InvalidPrefixError(`Invalid prefix : ${prefix}`)
3092+
}
3093+
if (!isString(prefix)) {
3094+
throw new TypeError('prefix should be of type "string"')
3095+
}
3096+
if (!isBoolean(recursive)) {
3097+
throw new TypeError('recursive should be of type "boolean"')
3098+
}
3099+
if (listOpts && !isObject(listOpts)) {
3100+
throw new TypeError('listOpts should be of type "object"')
3101+
}
3102+
let marker: string | undefined = ''
3103+
const listQueryOpts = {
3104+
Delimiter: recursive ? '' : '/', // if recursive is false set delimiter to '/'
3105+
MaxKeys: 1000,
3106+
IncludeVersion: listOpts?.IncludeVersion,
3107+
}
3108+
let objects: ObjectInfo[] = []
3109+
let ended = false
3110+
const readStream: stream.Readable = new stream.Readable({ objectMode: true })
3111+
readStream._read = async () => {
3112+
// push one object per _read()
3113+
if (objects.length) {
3114+
readStream.push(objects.shift())
3115+
return
3116+
}
3117+
if (ended) {
3118+
return readStream.push(null)
3119+
}
3120+
3121+
try {
3122+
const result: ListObjectQueryRes = await this.listObjectsQuery(bucketName, prefix, marker, listQueryOpts)
3123+
if (result.isTruncated) {
3124+
marker = result.nextMarker || result.versionIdMarker
3125+
} else {
3126+
ended = true
3127+
}
3128+
if (result.objects) {
3129+
objects = result.objects
3130+
}
3131+
// @ts-ignore
3132+
readStream._read()
3133+
} catch (err) {
3134+
readStream.emit('error', err)
3135+
}
3136+
}
3137+
return readStream
3138+
}
30083139
}

src/internal/type.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,78 @@ export type UploadPartConfig = {
465465
}
466466

467467
export type PreSignRequestParams = { [key: string]: string }
468+
469+
/** List object api types **/
470+
471+
// Common types
472+
export type CommonPrefix = {
473+
Prefix: string
474+
}
475+
476+
export type Owner = {
477+
ID: string
478+
DisplayName: string
479+
}
480+
481+
export type Metadata = {
482+
Items: MetadataItem[]
483+
}
484+
485+
export type ObjectInfo = {
486+
key?: string
487+
name?: string
488+
lastModified?: Date // time string of format "2006-01-02T15:04:05.000Z"
489+
etag?: string
490+
owner?: Owner
491+
storageClass?: string
492+
userMetadata?: Metadata
493+
userTags?: string
494+
prefix?: string
495+
size?: number
496+
}
497+
498+
export type ListObjectQueryRes = {
499+
isTruncated?: boolean
500+
nextMarker?: string
501+
versionIdMarker?: string
502+
objects?: ObjectInfo[]
503+
}
504+
505+
export type ListObjectQueryOpts = {
506+
Delimiter?: string
507+
MaxKeys?: number
508+
IncludeVersion?: boolean
509+
}
510+
/** List object api types **/
511+
512+
export type ObjectVersionEntry = {
513+
IsLatest?: string
514+
VersionId?: string
515+
}
516+
517+
export type ObjectRowEntry = ObjectVersionEntry & {
518+
Key: string
519+
LastModified?: Date | undefined
520+
ETag?: string
521+
Size?: string
522+
Owner?: Owner
523+
StorageClass?: string
524+
}
525+
526+
export interface ListBucketResultV1 {
527+
Name?: string
528+
Prefix?: string
529+
ContinuationToken?: string
530+
KeyCount?: string
531+
Marker?: string
532+
MaxKeys?: string
533+
Delimiter?: string
534+
IsTruncated?: boolean
535+
Contents?: ObjectRowEntry[]
536+
NextKeyMarker?: string
537+
CommonPrefixes?: CommonPrefix[]
538+
Version?: ObjectRowEntry[]
539+
DeleteMarker?: ObjectRowEntry[]
540+
VersionIdMarker?: string
541+
NextVersionIdMarker?: string
542+
}

0 commit comments

Comments
 (0)