0
# Advanced Object Operations
1
2
This document covers advanced object features including multipart uploads, object composition, S3 Select queries, object retention, legal hold, and sophisticated copy operations with conditions.
3
4
## Copy Operations
5
6
### CopySourceOptions Class
7
8
```typescript { .api }
9
import { CopySourceOptions } from 'minio'
10
11
class CopySourceOptions {
12
constructor(options: ICopySourceOptions)
13
14
// Methods
15
validate(): boolean
16
getHeaders(): RequestHeaders
17
}
18
19
interface ICopySourceOptions {
20
Bucket: string // Source bucket name
21
Object: string // Source object name
22
VersionID?: string // Source version ID
23
MatchETag?: string // Copy only if ETag matches
24
NoMatchETag?: string // Copy only if ETag doesn't match
25
MatchModifiedSince?: string | null // Copy if modified since date
26
MatchUnmodifiedSince?: string | null // Copy if unmodified since date
27
MatchRange?: boolean // Enable byte range matching
28
Start?: number // Start byte for range
29
End?: number // End byte for range
30
Encryption?: Encryption // Source object encryption
31
}
32
```
33
34
### CopyDestinationOptions Class
35
36
```typescript { .api }
37
import { CopyDestinationOptions } from 'minio'
38
39
class CopyDestinationOptions {
40
constructor(options: ICopyDestinationOptions)
41
42
// Methods
43
validate(): boolean
44
getHeaders(): RequestHeaders
45
}
46
47
interface ICopyDestinationOptions {
48
Bucket: string // Destination bucket name
49
Object: string // Destination object name
50
Encryption?: Encryption // Destination object encryption
51
UserMetadata?: ObjectMetaData // Custom metadata
52
UserTags?: Record<string, string> | string // Object tags
53
LegalHold?: 'on' | 'off' // Legal hold status
54
RetainUntilDate?: string // Retention until date
55
Mode?: RETENTION_MODES // Retention mode
56
MetadataDirective?: 'COPY' | 'REPLACE' // Metadata handling
57
Headers?: Record<string, string> // Additional headers
58
}
59
```
60
61
### Advanced Copy Examples
62
63
```javascript { .api }
64
import {
65
CopySourceOptions,
66
CopyDestinationOptions,
67
RETENTION_MODES,
68
ENCRYPTION_TYPES
69
} from 'minio'
70
71
// Copy with conditions
72
const source = new CopySourceOptions({
73
Bucket: 'source-bucket',
74
Object: 'source-file.pdf',
75
MatchETag: '"abc123def456"', // Only copy if ETag matches
76
MatchModifiedSince: '2023-01-01T00:00:00Z' // Only copy if modified after date
77
})
78
79
const dest = new CopyDestinationOptions({
80
Bucket: 'dest-bucket',
81
Object: 'copied-file.pdf',
82
MetadataDirective: 'REPLACE', // Replace metadata
83
UserMetadata: {
84
'x-amz-meta-copied-by': 'system',
85
'x-amz-meta-copy-date': new Date().toISOString()
86
},
87
UserTags: {
88
'source-bucket': 'source-bucket',
89
'copy-reason': 'backup'
90
}
91
})
92
93
const result = await client.copyObject(source, dest)
94
95
// Copy with encryption
96
const encryptedSource = new CopySourceOptions({
97
Bucket: 'encrypted-bucket',
98
Object: 'secret-file.txt',
99
Encryption: {
100
type: ENCRYPTION_TYPES.SSEC,
101
SSECustomerKey: 'source-encryption-key-32-chars',
102
SSECustomerKeyMD5: 'md5-hash-of-source-key'
103
}
104
})
105
106
const encryptedDest = new CopyDestinationOptions({
107
Bucket: 'dest-bucket',
108
Object: 'copied-secret.txt',
109
Encryption: {
110
type: ENCRYPTION_TYPES.SSEC,
111
SSECustomerKey: 'dest-encryption-key-32-chars!!',
112
SSECustomerKeyMD5: 'md5-hash-of-dest-key'
113
}
114
})
115
116
await client.copyObject(encryptedSource, encryptedDest)
117
118
// Copy partial object (byte range)
119
const partialSource = new CopySourceOptions({
120
Bucket: 'large-files',
121
Object: 'huge-file.dat',
122
MatchRange: true,
123
Start: 1024, // Start at byte 1024
124
End: 2047 // End at byte 2047 (1024 bytes total)
125
})
126
127
const partialDest = new CopyDestinationOptions({
128
Bucket: 'excerpts',
129
Object: 'file-excerpt.dat'
130
})
131
132
await client.copyObject(partialSource, partialDest)
133
```
134
135
## Object Composition
136
137
### Compose Multiple Objects
138
139
```javascript { .api }
140
const result = await client.composeObject(destObjConfig, sourceObjList)
141
142
// Parameters
143
destObjConfig: CopyDestinationOptions // Destination configuration
144
sourceObjList: CopySourceOptions[] // Array of source objects
145
146
// Returns: Promise<CopyObjectResult>
147
```
148
149
#### CopyObjectResult Interface
150
151
```typescript { .api }
152
interface CopyObjectResult {
153
etag: string // Composed object ETag
154
lastModified: Date // Last modified timestamp
155
versionId?: string // Version ID if versioning enabled
156
}
157
```
158
159
#### Composition Examples
160
161
```javascript { .api }
162
// Compose multiple log files into one
163
const sources = [
164
new CopySourceOptions({ Bucket: 'logs', Object: 'app-2023-01.log' }),
165
new CopySourceOptions({ Bucket: 'logs', Object: 'app-2023-02.log' }),
166
new CopySourceOptions({ Bucket: 'logs', Object: 'app-2023-03.log' })
167
]
168
169
const destination = new CopyDestinationOptions({
170
Bucket: 'archives',
171
Object: 'app-q1-2023.log',
172
UserMetadata: {
173
'x-amz-meta-composed-from': '3-monthly-logs',
174
'x-amz-meta-quarter': 'Q1-2023'
175
}
176
})
177
178
const result = await client.composeObject(destination, sources)
179
console.log('Composed object ETag:', result.etag)
180
181
// Compose with partial objects
182
const partialSources = [
183
new CopySourceOptions({
184
Bucket: 'data',
185
Object: 'file1.dat',
186
MatchRange: true,
187
Start: 0,
188
End: 1023 // First 1024 bytes
189
}),
190
new CopySourceOptions({
191
Bucket: 'data',
192
Object: 'file2.dat',
193
MatchRange: true,
194
Start: 1024,
195
End: 2047 // Second 1024 bytes
196
})
197
]
198
199
const composedDest = new CopyDestinationOptions({
200
Bucket: 'combined',
201
Object: 'merged-data.dat'
202
})
203
204
await client.composeObject(composedDest, partialSources)
205
```
206
207
## S3 Select Operations
208
209
### Select Object Content
210
211
```javascript { .api }
212
const results = await client.selectObjectContent(bucketName, objectName, selectOpts)
213
214
// Parameters
215
bucketName: string // Bucket name
216
objectName: string // Object name
217
selectOpts: SelectOptions // Query options
218
219
// Returns: Promise<SelectResults | undefined>
220
```
221
222
#### SelectOptions Interface
223
224
```typescript { .api }
225
interface SelectOptions {
226
expression: string // SQL-like query expression
227
expressionType: 'SQL' // Query language (currently only SQL)
228
inputSerialization: { // Input format configuration
229
CSV?: {
230
FileHeaderInfo?: 'USE' | 'IGNORE' | 'NONE'
231
RecordDelimiter?: string // Record separator (default: \n)
232
FieldDelimiter?: string // Field separator (default: ,)
233
QuoteCharacter?: string // Quote character (default: ")
234
QuoteEscapeCharacter?: string // Quote escape (default: ")
235
Comments?: string // Comment prefix
236
AllowQuotedRecordDelimiter?: boolean
237
}
238
JSON?: {
239
Type: 'DOCUMENT' | 'LINES' // JSON format type
240
}
241
Parquet?: {} // Parquet format (empty object)
242
CompressionType?: 'NONE' | 'GZIP' | 'BZIP2' // Compression
243
}
244
outputSerialization: { // Output format configuration
245
CSV?: {
246
RecordDelimiter?: string // Record separator
247
FieldDelimiter?: string // Field separator
248
QuoteCharacter?: string // Quote character
249
QuoteEscapeCharacter?: string // Quote escape
250
QuoteFields?: 'ALWAYS' | 'ASNEEDED' // When to quote
251
}
252
JSON?: {
253
RecordDelimiter?: string // Record separator
254
}
255
}
256
requestProgress?: boolean // Include progress information
257
}
258
```
259
260
#### SelectResults Class
261
262
```typescript { .api }
263
class SelectResults {
264
// Methods
265
setStats(stats: string): void
266
getStats(): string
267
setProgress(progress: unknown): void
268
getProgress(): unknown
269
setResponse(response: unknown): void
270
getResponse(): unknown
271
setRecords(records: unknown): void
272
getRecords(): unknown
273
}
274
```
275
276
#### S3 Select Examples
277
278
```javascript { .api }
279
// Query CSV file
280
const csvSelectOptions = {
281
expression: 'SELECT name, age FROM s3object WHERE age > 25',
282
expressionType: 'SQL',
283
inputSerialization: {
284
CSV: {
285
FileHeaderInfo: 'USE', // First row contains headers
286
FieldDelimiter: ',',
287
RecordDelimiter: '\n'
288
},
289
CompressionType: 'NONE'
290
},
291
outputSerialization: {
292
CSV: {
293
FieldDelimiter: ',',
294
RecordDelimiter: '\n'
295
}
296
},
297
requestProgress: true
298
}
299
300
const results = await client.selectObjectContent('data-bucket', 'users.csv', csvSelectOptions)
301
if (results) {
302
console.log('Query results:', results.getRecords())
303
console.log('Query stats:', results.getStats())
304
}
305
306
// Query JSON Lines file
307
const jsonSelectOptions = {
308
expression: 'SELECT * FROM s3object[*] WHERE status = "active"',
309
expressionType: 'SQL',
310
inputSerialization: {
311
JSON: { Type: 'LINES' },
312
CompressionType: 'GZIP'
313
},
314
outputSerialization: {
315
JSON: { RecordDelimiter: '\n' }
316
}
317
}
318
319
const jsonResults = await client.selectObjectContent('logs', 'events.jsonl.gz', jsonSelectOptions)
320
321
// Query with aggregation
322
const aggregateQuery = {
323
expression: 'SELECT category, COUNT(*) as count, AVG(price) as avg_price FROM s3object GROUP BY category',
324
expressionType: 'SQL',
325
inputSerialization: {
326
CSV: {
327
FileHeaderInfo: 'USE',
328
FieldDelimiter: ','
329
}
330
},
331
outputSerialization: {
332
CSV: { FieldDelimiter: ',' }
333
}
334
}
335
336
const aggregateResults = await client.selectObjectContent('sales', 'products.csv', aggregateQuery)
337
```
338
339
## Object Retention and Legal Hold
340
341
### Object Retention
342
343
#### Get Object Retention
344
345
```javascript { .api }
346
const retention = await client.getObjectRetention(bucketName, objectName, getOpts?)
347
348
// Parameters
349
bucketName: string // Bucket name
350
objectName: string // Object name
351
getOpts?: GetObjectRetentionOpts // Options
352
353
// Returns: Promise<ObjectRetentionInfo | null>
354
```
355
356
#### Set Object Retention
357
358
```javascript { .api }
359
await client.putObjectRetention(bucketName, objectName, retentionOpts?)
360
361
// Parameters
362
bucketName: string // Bucket name
363
objectName: string // Object name
364
retentionOpts?: Retention // Retention configuration
365
366
// Returns: Promise<void>
367
```
368
369
#### Retention Types
370
371
```typescript { .api }
372
interface GetObjectRetentionOpts {
373
versionId?: string // Specific version ID
374
}
375
376
interface ObjectRetentionInfo {
377
mode: RETENTION_MODES // GOVERNANCE or COMPLIANCE
378
retainUntilDate: Date // Retention expiry date
379
}
380
381
interface Retention {
382
mode: RETENTION_MODES // Retention mode
383
retainUntilDate: Date // Retention until date
384
governanceBypass?: boolean // Bypass governance retention (requires permission)
385
}
386
```
387
388
#### Retention Examples
389
390
```javascript { .api }
391
import { RETENTION_MODES } from 'minio'
392
393
// Set governance retention for 30 days
394
const retentionDate = new Date()
395
retentionDate.setDate(retentionDate.getDate() + 30)
396
397
await client.putObjectRetention('compliance-bucket', 'important-doc.pdf', {
398
mode: RETENTION_MODES.GOVERNANCE,
399
retainUntilDate: retentionDate
400
})
401
402
// Set compliance retention (cannot be bypassed)
403
const complianceDate = new Date()
404
complianceDate.setFullYear(complianceDate.getFullYear() + 7) // 7 years
405
406
await client.putObjectRetention('legal-docs', 'contract.pdf', {
407
mode: RETENTION_MODES.COMPLIANCE,
408
retainUntilDate: complianceDate
409
})
410
411
// Check current retention
412
const retention = await client.getObjectRetention('compliance-bucket', 'important-doc.pdf')
413
if (retention) {
414
console.log('Retention mode:', retention.mode)
415
console.log('Retain until:', retention.retainUntilDate)
416
} else {
417
console.log('No retention set')
418
}
419
420
// Bypass governance retention (requires s3:BypassGovernanceRetention permission)
421
await client.putObjectRetention('compliance-bucket', 'temp-doc.pdf', {
422
mode: RETENTION_MODES.GOVERNANCE,
423
retainUntilDate: new Date(), // Immediate expiry
424
governanceBypass: true
425
})
426
```
427
428
### Legal Hold
429
430
#### Get Legal Hold Status
431
432
```javascript { .api }
433
const status = await client.getObjectLegalHold(bucketName, objectName, getOpts?)
434
435
// Parameters
436
bucketName: string // Bucket name
437
objectName: string // Object name
438
getOpts?: GetObjectLegalHoldOptions // Options
439
440
// Returns: Promise<LEGAL_HOLD_STATUS>
441
```
442
443
#### Set Legal Hold
444
445
```javascript { .api }
446
await client.setObjectLegalHold(bucketName, objectName, setOpts?)
447
448
// Parameters
449
bucketName: string // Bucket name
450
objectName: string // Object name
451
setOpts?: PutObjectLegalHoldOptions // Legal hold configuration
452
453
// Returns: Promise<void>
454
```
455
456
#### Legal Hold Types
457
458
```typescript { .api }
459
interface GetObjectLegalHoldOptions {
460
versionId?: string // Specific version ID
461
}
462
463
interface PutObjectLegalHoldOptions {
464
versionId?: string // Specific version ID
465
status: LEGAL_HOLD_STATUS // ON or OFF
466
}
467
468
enum LEGAL_HOLD_STATUS {
469
ENABLED = 'ON', // Legal hold is active
470
DISABLED = 'OFF' // Legal hold is not active
471
}
472
```
473
474
#### Legal Hold Examples
475
476
```javascript { .api }
477
import { LEGAL_HOLD_STATUS } from 'minio'
478
479
// Enable legal hold
480
await client.setObjectLegalHold('legal-bucket', 'evidence.pdf', {
481
status: LEGAL_HOLD_STATUS.ENABLED
482
})
483
484
// Check legal hold status
485
const holdStatus = await client.getObjectLegalHold('legal-bucket', 'evidence.pdf')
486
console.log('Legal hold status:', holdStatus) // 'ON' or 'OFF'
487
488
// Disable legal hold
489
await client.setObjectLegalHold('legal-bucket', 'evidence.pdf', {
490
status: LEGAL_HOLD_STATUS.DISABLED
491
})
492
493
// Set legal hold on specific version
494
await client.setObjectLegalHold('versioned-bucket', 'document.txt', {
495
versionId: 'version-123',
496
status: LEGAL_HOLD_STATUS.ENABLED
497
})
498
```
499
500
## Copy Conditions (Legacy)
501
502
The `CopyConditions` class provides legacy support for copy conditions:
503
504
```typescript { .api }
505
import { CopyConditions } from 'minio'
506
507
class CopyConditions {
508
// Properties
509
modified: string // Modified since condition
510
unmodified: string // Unmodified since condition
511
matchETag: string // ETag match condition
512
matchETagExcept: string // ETag not match condition
513
514
// Methods
515
setModified(date: Date): void
516
setUnmodified(date: Date): void
517
setMatchETag(etag: string): void
518
setMatchETagExcept(etag: string): void
519
}
520
```
521
522
#### Legacy Copy Example
523
524
```javascript { .api }
525
import { CopyConditions } from 'minio'
526
527
// Legacy copy conditions (deprecated - use CopySourceOptions instead)
528
const conditions = new CopyConditions()
529
conditions.setMatchETag('"abc123def456"')
530
conditions.setModified(new Date('2023-01-01'))
531
532
// Note: This is legacy API, prefer CopySourceOptions for new code
533
```
534
535
## Server-Side Encryption
536
537
### Encryption Types
538
539
```typescript { .api }
540
import { ENCRYPTION_TYPES } from 'minio'
541
542
type Encryption =
543
| {
544
type: ENCRYPTION_TYPES.SSEC // Server-side encryption with customer keys
545
SSECustomerKey?: string // Base64 encoded 256-bit key
546
SSECustomerKeyMD5?: string // MD5 hash of the key
547
}
548
| {
549
type: ENCRYPTION_TYPES.KMS // Server-side encryption with KMS
550
SSEAlgorithm?: string // Encryption algorithm
551
KMSMasterKeyID?: string // KMS key ID
552
}
553
```
554
555
### Encryption Examples
556
557
```javascript { .api }
558
import { ENCRYPTION_TYPES } from 'minio'
559
560
// Upload with SSE-C encryption
561
const sseCustomerKey = 'your-32-character-secret-key-here!!'
562
const uploadInfo = await client.putObject('encrypted-bucket', 'secret.txt', 'confidential data', undefined, {
563
'Content-Type': 'text/plain',
564
// Encryption headers are handled automatically when using CopySourceOptions/CopyDestinationOptions
565
})
566
567
// Copy with different encryption
568
const source = new CopySourceOptions({
569
Bucket: 'source-bucket',
570
Object: 'encrypted-file.txt',
571
Encryption: {
572
type: ENCRYPTION_TYPES.SSEC,
573
SSECustomerKey: 'source-key-32-characters-long!!!',
574
SSECustomerKeyMD5: 'md5-hash-of-source-key'
575
}
576
})
577
578
const dest = new CopyDestinationOptions({
579
Bucket: 'dest-bucket',
580
Object: 'reencrypted-file.txt',
581
Encryption: {
582
type: ENCRYPTION_TYPES.KMS,
583
KMSMasterKeyID: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
584
}
585
})
586
587
await client.copyObject(source, dest)
588
```
589
590
## Error Handling
591
592
```javascript { .api }
593
import { S3Error, InvalidArgumentError } from 'minio'
594
595
try {
596
await client.selectObjectContent('bucket', 'data.csv', selectOptions)
597
} catch (error) {
598
if (error instanceof S3Error) {
599
switch (error.code) {
600
case 'NoSuchKey':
601
console.error('Object not found')
602
break
603
case 'InvalidRequest':
604
console.error('Invalid S3 Select query:', error.message)
605
break
606
case 'AccessDenied':
607
console.error('Access denied for S3 Select operation')
608
break
609
default:
610
console.error('S3 Error:', error.code, error.message)
611
}
612
}
613
}
614
615
// Handle retention/legal hold errors
616
try {
617
await client.putObjectRetention('bucket', 'object', retention)
618
} catch (error) {
619
if (error instanceof S3Error) {
620
switch (error.code) {
621
case 'InvalidRequest':
622
console.error('Object locking not enabled on bucket')
623
break
624
case 'AccessDenied':
625
console.error('Insufficient permissions for retention operation')
626
break
627
}
628
}
629
}
630
```
631
632
## Best Practices
633
634
### 1. Copy Operations
635
- Use conditions to ensure data consistency
636
- Validate source objects exist before copying
637
- Consider storage classes for destination objects
638
- Handle large object copies with appropriate timeouts
639
640
### 2. Object Composition
641
- Limit the number of source objects (max 10,000)
642
- Ensure all source objects are in the same bucket
643
- Consider the total size of composed object
644
- Use composition for log aggregation and data merging
645
646
### 3. S3 Select
647
- Use S3 Select to reduce data transfer costs
648
- Optimize queries for performance
649
- Consider input format and compression
650
- Test queries with small datasets first
651
652
### 4. Retention and Legal Hold
653
- Plan retention policies carefully (compliance mode cannot be changed)
654
- Document legal hold processes and requirements
655
- Monitor retention expiry dates
656
- Ensure proper permissions for governance bypass
657
658
### 5. Encryption
659
- Use consistent encryption strategies
660
- Securely manage encryption keys
661
- Consider key rotation policies
662
- Document encryption requirements and procedures
663
664
---
665
666
**Next:** [Presigned Operations](./presigned-operations.md) - Learn about presigned URLs and POST policies