Skip to content

Commit

Permalink
refactor listobjects v1 api to ts (#1368)
Browse files Browse the repository at this point in the history
  • Loading branch information
prakashsvmx authored Dec 18, 2024
1 parent 91dff4b commit dec8b51
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 282 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

133 changes: 132 additions & 1 deletion src/internal/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ import type {
ItemBucketMetadata,
LifecycleConfig,
LifeCycleConfigParam,
ListObjectQueryOpts,
ListObjectQueryRes,
ObjectInfo,
ObjectLockConfigParam,
ObjectLockInfo,
ObjectMetaData,
Expand Down Expand Up @@ -115,13 +118,14 @@ import type {
UploadPartConfig,
} from './type.ts'
import type { ListMultipartResult, UploadedPart } from './xml-parser.ts'
import * as xmlParsers from './xml-parser.ts'
import {
parseCompleteMultipart,
parseInitiateMultipart,
parseListObjects,
parseObjectLegalHoldConfig,
parseSelectObjectContentResponse,
} from './xml-parser.ts'
import * as xmlParsers from './xml-parser.ts'

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

Expand Down Expand Up @@ -3005,4 +3009,131 @@ export class TypedClient {
throw err
}
}
// list a batch of objects
async listObjectsQuery(bucketName: string, prefix?: string, marker?: string, listQueryOpts?: ListObjectQueryOpts) {
if (!isValidBucketName(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
}
if (!isString(prefix)) {
throw new TypeError('prefix should be of type "string"')
}
if (!isString(marker)) {
throw new TypeError('marker should be of type "string"')
}

if (listQueryOpts && !isObject(listQueryOpts)) {
throw new TypeError('listQueryOpts should be of type "object"')
}
let { Delimiter, MaxKeys, IncludeVersion } = listQueryOpts as ListObjectQueryOpts

if (!isString(Delimiter)) {
throw new TypeError('Delimiter should be of type "string"')
}
if (!isNumber(MaxKeys)) {
throw new TypeError('MaxKeys should be of type "number"')
}

const queries = []
// escape every value in query string, except maxKeys
queries.push(`prefix=${uriEscape(prefix)}`)
queries.push(`delimiter=${uriEscape(Delimiter)}`)
queries.push(`encoding-type=url`)

if (IncludeVersion) {
queries.push(`versions`)
}

if (marker) {
marker = uriEscape(marker)
if (IncludeVersion) {
queries.push(`key-marker=${marker}`)
} else {
queries.push(`marker=${marker}`)
}
}

// no need to escape maxKeys
if (MaxKeys) {
if (MaxKeys >= 1000) {
MaxKeys = 1000
}
queries.push(`max-keys=${MaxKeys}`)
}
queries.sort()
let query = ''
if (queries.length > 0) {
query = `${queries.join('&')}`
}

const method = 'GET'
const res = await this.makeRequestAsync({ method, bucketName, query })
const body = await readAsString(res)
const listQryList = parseListObjects(body)
return listQryList
}

listObjects(
bucketName: string,
prefix?: string,
recursive?: boolean,
listOpts?: ListObjectQueryOpts | undefined,
): BucketStream<ObjectInfo> {
if (prefix === undefined) {
prefix = ''
}
if (recursive === undefined) {
recursive = false
}
if (!isValidBucketName(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
}
if (!isValidPrefix(prefix)) {
throw new errors.InvalidPrefixError(`Invalid prefix : ${prefix}`)
}
if (!isString(prefix)) {
throw new TypeError('prefix should be of type "string"')
}
if (!isBoolean(recursive)) {
throw new TypeError('recursive should be of type "boolean"')
}
if (listOpts && !isObject(listOpts)) {
throw new TypeError('listOpts should be of type "object"')
}
let marker: string | undefined = ''
const listQueryOpts = {
Delimiter: recursive ? '' : '/', // if recursive is false set delimiter to '/'
MaxKeys: 1000,
IncludeVersion: listOpts?.IncludeVersion,
}
let objects: ObjectInfo[] = []
let ended = false
const readStream: stream.Readable = new stream.Readable({ objectMode: true })
readStream._read = async () => {
// push one object per _read()
if (objects.length) {
readStream.push(objects.shift())
return
}
if (ended) {
return readStream.push(null)
}

try {
const result: ListObjectQueryRes = await this.listObjectsQuery(bucketName, prefix, marker, listQueryOpts)
if (result.isTruncated) {
marker = result.nextMarker || result.versionIdMarker
} else {
ended = true
}
if (result.objects) {
objects = result.objects
}
// @ts-ignore
readStream._read()
} catch (err) {
readStream.emit('error', err)
}
}
return readStream
}
}
77 changes: 76 additions & 1 deletion src/internal/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ export type LifecycleRule = {
Prefix?: string
Status?: string
Expiration?: Expiration
RuleFilter?: RuleFilter
Filter?: RuleFilter
NoncurrentVersionExpiration?: NoncurrentVersionExpiration
NoncurrentVersionTransition?: NoncurrentVersionTransition
Transition?: Transition
Expand Down Expand Up @@ -465,3 +465,78 @@ export type UploadPartConfig = {
}

export type PreSignRequestParams = { [key: string]: string }

/** List object api types **/

// Common types
export type CommonPrefix = {
Prefix: string
}

export type Owner = {
ID: string
DisplayName: string
}

export type Metadata = {
Items: MetadataItem[]
}

export type ObjectInfo = {
key?: string
name?: string
lastModified?: Date // time string of format "2006-01-02T15:04:05.000Z"
etag?: string
owner?: Owner
storageClass?: string
userMetadata?: Metadata
userTags?: string
prefix?: string
size?: number
}

export type ListObjectQueryRes = {
isTruncated?: boolean
nextMarker?: string
versionIdMarker?: string
objects?: ObjectInfo[]
}

export type ListObjectQueryOpts = {
Delimiter?: string
MaxKeys?: number
IncludeVersion?: boolean
}
/** List object api types **/

export type ObjectVersionEntry = {
IsLatest?: string
VersionId?: string
}

export type ObjectRowEntry = ObjectVersionEntry & {
Key: string
LastModified?: Date | undefined
ETag?: string
Size?: string
Owner?: Owner
StorageClass?: string
}

export interface ListBucketResultV1 {
Name?: string
Prefix?: string
ContinuationToken?: string
KeyCount?: string
Marker?: string
MaxKeys?: string
Delimiter?: string
IsTruncated?: boolean
Contents?: ObjectRowEntry[]
NextKeyMarker?: string
CommonPrefixes?: CommonPrefix[]
Version?: ObjectRowEntry[]
DeleteMarker?: ObjectRowEntry[]
VersionIdMarker?: string
NextVersionIdMarker?: string
}
Loading

0 comments on commit dec8b51

Please sign in to comment.