({ body, contentType, metadata, signal })
| 210 | let endpointCheck: Promise<void> | null = null |
| 211 | return { |
| 212 | async deliver({ body, contentType, metadata, signal }) { |
| 213 | if (endpointCheck === null) endpointCheck = assertEndpointIsPublic(config.endpoint) |
| 214 | await endpointCheck |
| 215 | const key = buildObjectKey(config.prefix, metadata) |
| 216 | const keyBytes = Buffer.byteLength(key, 'utf8') |
| 217 | if (keyBytes > MAX_S3_KEY_BYTES) { |
| 218 | throw new Error( |
| 219 | `S3 object key is ${keyBytes} bytes, exceeds the ${MAX_S3_KEY_BYTES}-byte limit` |
| 220 | ) |
| 221 | } |
| 222 | const userMetadata: Record<string, string> = { |
| 223 | 'sim-drain-id': metadata.drainId, |
| 224 | 'sim-run-id': metadata.runId, |
| 225 | 'sim-source': metadata.source, |
| 226 | 'sim-sequence': metadata.sequence.toString(), |
| 227 | 'sim-row-count': metadata.rowCount.toString(), |
| 228 | } |
| 229 | let metadataBytes = 0 |
| 230 | for (const [k, v] of Object.entries(userMetadata)) { |
| 231 | metadataBytes += Buffer.byteLength(k, 'utf8') + Buffer.byteLength(v, 'utf8') |
| 232 | } |
| 233 | if (metadataBytes > MAX_S3_METADATA_BYTES) { |
| 234 | throw new Error( |
| 235 | `S3 user metadata is ${metadataBytes} bytes, exceeds the ${MAX_S3_METADATA_BYTES}-byte per-object limit` |
| 236 | ) |
| 237 | } |
| 238 | await withS3ErrorContext('put-object', () => |
| 239 | client.send( |
| 240 | new PutObjectCommand({ |
| 241 | Bucket: config.bucket, |
| 242 | Key: key, |
| 243 | Body: body, |
| 244 | ContentType: contentType, |
| 245 | ServerSideEncryption: 'AES256', |
| 246 | Metadata: userMetadata, |
| 247 | }), |
| 248 | { abortSignal: signal } |
| 249 | ) |
| 250 | ) |
| 251 | logger.debug('S3 chunk delivered', { bucket: config.bucket, key, bytes: body.byteLength }) |
| 252 | return { locator: `s3://${config.bucket}/${key}` } |
| 253 | }, |
| 254 | async close() { |
| 255 | client.destroy() |
| 256 | }, |
nothing calls this directly
no test coverage detected