diff --git a/docs/API.md b/docs/API.md
index a09a505c..e5056a53 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -1158,18 +1158,17 @@ console.log(stat)
-### removeObject(bucketName, objectName [, removeOpts] [, callback])
+### removeObject(bucketName, objectName [, removeOpts])
Removes an object.
**Parameters**
-| Param | Type | Description |
-| --------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
-| `bucketName` | _string_ | Name of the bucket. |
-| `objectName` | _string_ | Name of the object. |
-| `removeOpts` | _object_ | Version of the object in the form `{versionId:"my-versionId", governanceBypass: true or false }`. Default is `{}`. (Optional) |
-| `callback(err)` | _function_ | Callback function is called with non `null` value in case of error. If no callback is passed, a `Promise` is returned. |
+| Param | Type | Description |
+| ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| `bucketName` | _string_ | Name of the bucket. |
+| `objectName` | _string_ | Name of the object. |
+| `removeOpts` | _object_ | Version of the object in the form `{versionId:"my-versionId", governanceBypass: true or false }`. Default is `{}`. (Optional) |
**Example 1**
@@ -1206,17 +1205,16 @@ Remove an object version locked with retention mode `GOVERNANCE` using the `gove
-### removeObjects(bucketName, objectsList[, callback])
+### removeObjects(bucketName, objectsList)
Remove all objects in the objectsList.
**Parameters**
-| Param | Type | Description |
-| --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `bucketName` | _string_ | Name of the bucket. |
-| `objectsList` | _object_ | list of objects in the bucket to be removed. any one of the formats: 1. List of Object names as array of strings which are object keys: `['objectname1','objectname2']` 2. List of Object name and VersionId as an object: [{name:"my-obj-name",versionId:"my-versionId"}] |
-| `callback(err)` | _function_ | Callback function is called with non `null` value in case of error. |
+| Param | Type | Description |
+| ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `bucketName` | _string_ | Name of the bucket. |
+| `objectsList` | _object_ | list of objects in the bucket to be removed. any one of the formats: 1. List of Object names as array of strings which are object keys: `['objectname1','objectname2']` 2. List of Object name and VersionId as an object: [{name:"my-obj-name",versionId:"my-versionId"}] |
**Example**
@@ -1234,13 +1232,8 @@ objectsStream.on('error', function (e) {
console.log(e)
})
-objectsStream.on('end', function () {
- s3Client.removeObjects('my-bucketname', objectsList, function (e) {
- if (e) {
- return console.log('Unable to remove Objects ', e)
- }
- console.log('Removed the objects successfully')
- })
+objectsStream.on('end', async () => {
+ await s3Client.removeObjects(bucket, objectsList)
})
```
@@ -1261,13 +1254,8 @@ objectsStream.on('data', function (obj) {
objectsStream.on('error', function (e) {
return console.log(e)
})
-objectsStream.on('end', function () {
- s3Client.removeObjects(bucket, objectsList, function (e) {
- if (e) {
- return console.log(e)
- }
- console.log('Success')
- })
+objectsStream.on('end', async () => {
+ await s3Client.removeObjects(bucket, objectsList)
})
```
diff --git a/examples/remove-objects.js b/examples/remove-objects.js
index c164cb5f..cc38dcd5 100644
--- a/examples/remove-objects.js
+++ b/examples/remove-objects.js
@@ -21,8 +21,6 @@ import * as Minio from 'minio'
const s3Client = new Minio.Client({
endPoint: 's3.amazonaws.com',
- port: 9000,
- useSSL: false,
accessKey: 'YOUR-ACCESSKEYID',
secretKey: 'YOUR-SECRETACCESSKEY',
})
@@ -48,13 +46,9 @@ function removeObjects(bucketName, prefix, recursive, includeVersion) {
return console.log(e)
})
- objectsStream.on('end', function () {
- s3Client.removeObjects(bucketName, objectsList, function (e) {
- if (e) {
- return console.log(e)
- }
- console.log('Success')
- })
+ objectsStream.on('end', async () => {
+ const delRes = await s3Client.removeObjects(bucketName, objectsList)
+ console.log(delRes)
})
}
@@ -62,18 +56,13 @@ removeObjects(bucketName, prefix, recursive, true) // Versioned objects of a buc
removeObjects(bucketName, prefix, recursive, false) // Normal objects of a bucket to be deleted.
// Delete Multiple objects and respective versions.
-function removeObjectsMultipleVersions() {
+async function removeObjectsMultipleVersions() {
const deleteList = [
{ versionId: '03ed08e1-34ff-4465-91ed-ba50c1e80f39', name: 'prefix-1/out.json.gz' },
{ versionId: '35517ae1-18cb-4a21-9551-867f53a10cfe', name: 'dir1/dir2/test.pdf' },
{ versionId: '3053f564-9aea-4a59-88f0-7f25d6320a2c', name: 'dir1/dir2/test.pdf' },
]
- s3Client.removeObjects('my-bucket', deleteList, function (e) {
- if (e) {
- return console.log(e)
- }
- console.log('Successfully deleted..')
- })
+ await s3Client.removeObjects('my-bucket', deleteList)
}
removeObjectsMultipleVersions()
diff --git a/package-lock.json b/package-lock.json
index 52fdc1ed..e3f9ee57 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "minio",
- "version": "8.0.0",
+ "version": "8.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "minio",
- "version": "8.0.0",
+ "version": "8.0.1",
"license": "Apache-2.0",
"dependencies": {
"async": "^3.2.4",
diff --git a/src/internal/client.ts b/src/internal/client.ts
index 5d438a16..bd7a5883 100644
--- a/src/internal/client.ts
+++ b/src/internal/client.ts
@@ -71,6 +71,9 @@ import type {
ObjectMetaData,
PutObjectLegalHoldOptions,
PutTaggingParams,
+ RemoveObjectsParam,
+ RemoveObjectsRequestEntry,
+ RemoveObjectsResponse,
RemoveTaggingParams,
ReplicationConfig,
ReplicationConfigOpts,
@@ -88,13 +91,13 @@ import type {
VersionIdentificator,
} from './type.ts'
import type { ListMultipartResult, UploadedPart } from './xml-parser.ts'
+import * as xmlParsers from './xml-parser.ts'
import {
parseCompleteMultipart,
parseInitiateMultipart,
parseObjectLegalHoldConfig,
parseSelectObjectContentResponse,
} from './xml-parser.ts'
-import * as xmlParsers from './xml-parser.ts'
const xml = new xml2js.Builder({ renderOpts: { pretty: false }, headless: true })
@@ -1109,19 +1112,7 @@ export class TypedClient {
}
}
- /**
- * Remove the specified object.
- * @deprecated use new promise style API
- */
- removeObject(bucketName: string, objectName: string, removeOpts: RemoveOptions, callback: NoResultCallback): void
- /**
- * @deprecated use new promise style API
- */
- // @ts-ignore
- removeObject(bucketName: string, objectName: string, callback: NoResultCallback): void
- async removeObject(bucketName: string, objectName: string, removeOpts?: RemoveOptions): Promise
-
- async removeObject(bucketName: string, objectName: string, removeOpts: RemoveOptions = {}): Promise {
+ async removeObject(bucketName: string, objectName: string, removeOpts?: RemoveOptions): Promise {
if (!isValidBucketName(bucketName)) {
throw new errors.InvalidBucketNameError(`Invalid bucket name: ${bucketName}`)
}
@@ -1136,15 +1127,15 @@ export class TypedClient {
const method = 'DELETE'
const headers: RequestHeaders = {}
- if (removeOpts.governanceBypass) {
+ if (removeOpts?.governanceBypass) {
headers['X-Amz-Bypass-Governance-Retention'] = true
}
- if (removeOpts.forceDelete) {
+ if (removeOpts?.forceDelete) {
headers['x-minio-force-delete'] = true
}
const queryParams: Record = {}
- if (removeOpts.versionId) {
+ if (removeOpts?.versionId) {
queryParams.versionId = `${removeOpts.versionId}`
}
const query = qs.stringify(queryParams)
@@ -2400,4 +2391,37 @@ export class TypedClient {
await this.makeRequestAsyncOmit({ method, bucketName, query }, '', [204])
}
+
+ async removeObjects(bucketName: string, objectsList: RemoveObjectsParam): Promise {
+ if (!isValidBucketName(bucketName)) {
+ throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
+ }
+ if (!Array.isArray(objectsList)) {
+ throw new errors.InvalidArgumentError('objectsList should be a list')
+ }
+
+ const runDeleteObjects = async (batch: RemoveObjectsParam): Promise => {
+ const delObjects: RemoveObjectsRequestEntry[] = batch.map((value) => {
+ return isObject(value) ? { Key: value.name, VersionId: value.versionId } : { Key: value }
+ })
+
+ const remObjects = { Delete: { Quiet: true, Object: delObjects } }
+ const payload = new xml2js.Builder({ headless: true }).buildObject(remObjects)
+ const headers: RequestHeaders = { 'Content-MD5': toMd5(payload) }
+
+ const res = await this.makeRequestAsync({ method: 'POST', bucketName, query: 'delete', headers }, payload)
+ const body = await readAsString(res)
+ return xmlParsers.removeObjectsParser(body)
+ }
+
+ const maxEntries = 1000 // max entries accepted in server for DeleteMultipleObjects API.
+ // Client side batching
+ const batches = []
+ for (let i = 0; i < objectsList.length; i += maxEntries) {
+ batches.push(objectsList.slice(i, i + maxEntries))
+ }
+
+ const batchResults = await Promise.all(batches.map(runDeleteObjects))
+ return batchResults.flat()
+ }
}
diff --git a/src/internal/type.ts b/src/internal/type.ts
index 21b5b086..58b03294 100644
--- a/src/internal/type.ts
+++ b/src/internal/type.ts
@@ -382,3 +382,28 @@ export type EncryptionRule = {
export type EncryptionConfig = {
Rule: EncryptionRule[]
}
+
+export type RemoveObjectsEntry = {
+ name: string
+ versionId?: string
+}
+export type ObjectName = string
+
+export type RemoveObjectsParam = ObjectName[] | RemoveObjectsEntry[]
+
+export type RemoveObjectsRequestEntry = {
+ Key: string
+ VersionId?: string
+}
+
+export type RemoveObjectsResponse =
+ | null
+ | undefined
+ | {
+ Error?: {
+ Code?: string
+ Message?: string
+ Key?: string
+ VersionId?: string
+ }
+ }
diff --git a/src/internal/xml-parser.ts b/src/internal/xml-parser.ts
index 5ce7232c..a6ce8a7c 100644
--- a/src/internal/xml-parser.ts
+++ b/src/internal/xml-parser.ts
@@ -548,3 +548,12 @@ export function parseLifecycleConfig(xml: string) {
export function parseBucketEncryptionConfig(xml: string) {
return parseXml(xml)
}
+
+export function removeObjectsParser(xml: string) {
+ const xmlObj = parseXml(xml)
+ if (xmlObj.DeleteResult && xmlObj.DeleteResult.Error) {
+ // return errors as array always. as the response is object in case of single object passed in removeObjects
+ return toArray(xmlObj.DeleteResult.Error)
+ }
+ return []
+}
diff --git a/src/minio.d.ts b/src/minio.d.ts
index c2fa9393..2f798317 100644
--- a/src/minio.d.ts
+++ b/src/minio.d.ts
@@ -162,9 +162,6 @@ export class Client extends TypedClient {
conditions: CopyConditions,
): Promise
- removeObjects(bucketName: string, objectsList: string[], callback: NoResultCallback): void
- removeObjects(bucketName: string, objectsList: string[]): Promise
-
removeIncompleteUpload(bucketName: string, objectName: string, callback: NoResultCallback): void
removeIncompleteUpload(bucketName: string, objectName: string): Promise
diff --git a/src/minio.js b/src/minio.js
index a96ab82f..78d2c7bd 100644
--- a/src/minio.js
+++ b/src/minio.js
@@ -19,7 +19,6 @@ import * as Stream from 'node:stream'
import async from 'async'
import _ from 'lodash'
import * as querystring from 'query-string'
-import { TextEncoder } from 'web-encoding'
import xml2js from 'xml2js'
import * as errors from './errors.ts'
@@ -47,7 +46,6 @@ import {
partsRequired,
pipesetup,
sanitizeETag,
- toMd5,
uriEscape,
uriResourceEscape,
} from './internal/helper.ts'
@@ -535,91 +533,6 @@ export class Client extends TypedClient {
return readStream
}
- // Remove all the objects residing in the objectsList.
- //
- // __Arguments__
- // * `bucketName` _string_: name of the bucket
- // * `objectsList` _array_: array of objects of one of the following:
- // * List of Object names as array of strings which are object keys: ['objectname1','objectname2']
- // * List of Object name and versionId as an object: [{name:"objectname",versionId:"my-version-id"}]
-
- removeObjects(bucketName, objectsList, cb) {
- if (!isValidBucketName(bucketName)) {
- throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
- }
- if (!Array.isArray(objectsList)) {
- throw new errors.InvalidArgumentError('objectsList should be a list')
- }
- if (!isFunction(cb)) {
- throw new TypeError('callback should be of type "function"')
- }
-
- const maxEntries = 1000
- const query = 'delete'
- const method = 'POST'
-
- let result = objectsList.reduce(
- (result, entry) => {
- result.list.push(entry)
- if (result.list.length === maxEntries) {
- result.listOfList.push(result.list)
- result.list = []
- }
- return result
- },
- { listOfList: [], list: [] },
- )
-
- if (result.list.length > 0) {
- result.listOfList.push(result.list)
- }
-
- const encoder = new TextEncoder()
- const batchResults = []
-
- async.eachSeries(
- result.listOfList,
- (list, batchCb) => {
- var objects = []
- list.forEach(function (value) {
- if (isObject(value)) {
- objects.push({ Key: value.name, VersionId: value.versionId })
- } else {
- objects.push({ Key: value })
- }
- })
- let deleteObjects = { Delete: { Quiet: true, Object: objects } }
- const builder = new xml2js.Builder({ headless: true })
- let payload = builder.buildObject(deleteObjects)
- payload = Buffer.from(encoder.encode(payload))
- const headers = {}
-
- headers['Content-MD5'] = toMd5(payload)
-
- let removeObjectsResult
- this.makeRequest({ method, bucketName, query, headers }, payload, [200], '', true, (e, response) => {
- if (e) {
- return batchCb(e)
- }
- pipesetup(response, transformers.removeObjectsTransformer())
- .on('data', (data) => {
- removeObjectsResult = data
- })
- .on('error', (e) => {
- return batchCb(e, null)
- })
- .on('end', () => {
- batchResults.push(removeObjectsResult)
- return batchCb(null, removeObjectsResult)
- })
- })
- },
- () => {
- cb(null, _.flatten(batchResults))
- },
- )
- }
-
// Generate a generic presigned URL which can be
// used for HTTP methods GET, PUT, HEAD and DELETE
//
@@ -1139,7 +1052,6 @@ export class Client extends TypedClient {
// Promisify various public-facing APIs on the Client module.
Client.prototype.copyObject = promisify(Client.prototype.copyObject)
-Client.prototype.removeObjects = promisify(Client.prototype.removeObjects)
Client.prototype.presignedUrl = promisify(Client.prototype.presignedUrl)
Client.prototype.presignedGetObject = promisify(Client.prototype.presignedGetObject)
@@ -1191,3 +1103,4 @@ Client.prototype.removeBucketLifecycle = callbackify(Client.prototype.removeBuck
Client.prototype.setBucketEncryption = callbackify(Client.prototype.setBucketEncryption)
Client.prototype.getBucketEncryption = callbackify(Client.prototype.getBucketEncryption)
Client.prototype.removeBucketEncryption = callbackify(Client.prototype.removeBucketEncryption)
+Client.prototype.removeObjects = callbackify(Client.prototype.removeObjects)
diff --git a/src/transformers.js b/src/transformers.js
index 7f79e8b2..1c30b86a 100644
--- a/src/transformers.js
+++ b/src/transformers.js
@@ -131,7 +131,3 @@ export function objectLegalHoldTransformer() {
export function uploadPartTransformer() {
return getConcater(xmlParsers.uploadPartParser)
}
-
-export function removeObjectsTransformer() {
- return getConcater(xmlParsers.removeObjectsParser)
-}
diff --git a/tests/unit/test.js b/tests/unit/test.js
index 3424dbc3..c4288e77 100644
--- a/tests/unit/test.js
+++ b/tests/unit/test.js
@@ -727,63 +727,68 @@ describe('Client', function () {
})
describe('#removeObject(bucket, object, callback)', () => {
- it('should fail on null bucket', (done) => {
- client.removeObject(null, 'hello', function (err) {
- if (err) {
- return done()
- }
- done(new Error('callback should receive error'))
- })
+ it('should fail on null bucket', async () => {
+ try {
+ await client.removeObject(null, 'hello')
+ } catch (err) {
+ return
+ }
+ throw new Error('callback should receive error')
})
- it('should fail on empty bucket', (done) => {
- client.removeObject('', 'hello', function (err) {
- if (err) {
- return done()
- }
- done(new Error('callback should receive error'))
- })
+
+ it('should fail on empty bucket', async () => {
+ try {
+ await client.removeObject('', 'hello')
+ } catch (err) {
+ return
+ }
+ throw new Error('callback should receive error')
})
- it('should fail on empty bucket', (done) => {
- client.removeObject(' \n \t ', 'hello', function (err) {
- if (err) {
- return done()
- }
- done(new Error('callback should receive error'))
- })
+
+ it('should fail invalid bucket name', async () => {
+ try {
+ await client.removeObject(' \n \t ', 'hello')
+ } catch (err) {
+ return
+ }
+ throw new Error('callback should receive error')
})
- it('should fail on null object', (done) => {
- client.removeObject('hello', null, function (err) {
- if (err) {
- return done()
- }
- done(new Error('callback should receive error'))
- })
+
+ it('should fail on null object', async () => {
+ try {
+ await client.removeObject('hello', null)
+ } catch (err) {
+ return
+ }
+ throw new Error('callback should receive error')
})
- it('should fail on empty object', (done) => {
- client.removeObject('hello', '', function (err) {
- if (err) {
- return done()
- }
- done(new Error('callback should receive error'))
- })
+
+ it('should fail on empty object', async () => {
+ try {
+ await client.removeObject('hello', '')
+ } catch (err) {
+ return
+ }
+ throw new Error('callback should receive error')
})
+
// Versioning related options as removeOpts
- it('should fail on empty (null) removeOpts object', (done) => {
- client.removeObject('hello', 'testRemoveOpts', null, function (err) {
- if (err) {
- return done()
- }
- done(new Error('callback should receive error'))
- })
+ it('should fail on empty (null) removeOpts object', async () => {
+ try {
+ await client.removeObject('hello', 'testRemoveOpts', null)
+ } catch (err) {
+ return
+ }
+ throw new Error('callback should receive error')
})
- it('should fail on empty (string) removeOpts', (done) => {
- client.removeObject('hello', 'testRemoveOpts', '', function (err) {
- if (err) {
- return done()
- }
- done(new Error('callback should receive error'))
- })
+ it('should fail on empty (string) removeOpts', async () => {
+ try {
+ await client.removeObject('hello', '', '')
+ } catch (err) {
+ return
+ }
+ throw new Error('callback should receive error')
})
})