@@ -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'
117120import type { ListMultipartResult , UploadedPart } from './xml-parser.ts'
118- import * as xmlParsers from './xml-parser.ts'
119121import {
120122 parseCompleteMultipart ,
121123 parseInitiateMultipart ,
124+ parseListObjects ,
122125 parseObjectLegalHoldConfig ,
123126 parseSelectObjectContentResponse ,
124127} from './xml-parser.ts'
128+ import * as xmlParsers from './xml-parser.ts'
125129
126130const 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}
0 commit comments