Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor listobjects v1 api to ts #1368

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading