@onivoro/server-aws-firehose 24.38.2 → 25.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -236
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -1,292 +1,112 @@
|
|
|
1
1
|
# @onivoro/server-aws-firehose
|
|
2
2
|
|
|
3
|
-
AWS
|
|
3
|
+
AWS Kinesis Data Firehose integration for NestJS applications: write JSON records to a delivery stream.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @onivoro/server-aws-firehose
|
|
8
|
+
npm install @onivoro/server-aws-firehose @aws-sdk/client-firehose @nestjs/common
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
This library provides AWS S3 integration for NestJS applications, offering file upload, download, deletion, and pre-signed URL generation.
|
|
11
|
+
`@aws-sdk/client-firehose` and `@nestjs/common` are peer dependencies.
|
|
14
12
|
|
|
15
13
|
## Module Setup
|
|
16
14
|
|
|
15
|
+
`ServerAwsFirehoseModule.configure(config)` takes the configuration object directly; the module does not read environment variables itself.
|
|
16
|
+
|
|
17
17
|
```typescript
|
|
18
18
|
import { Module } from '@nestjs/common';
|
|
19
|
-
import {
|
|
19
|
+
import { ServerAwsFirehoseModule } from '@onivoro/server-aws-firehose';
|
|
20
20
|
|
|
21
21
|
@Module({
|
|
22
22
|
imports: [
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
ServerAwsFirehoseModule.configure({
|
|
24
|
+
AWS_REGION: process.env.AWS_REGION!,
|
|
25
|
+
AWS_FIREHOSE_NAME: process.env.AWS_FIREHOSE_NAME!,
|
|
26
|
+
AWS_PROFILE: process.env.AWS_PROFILE, // optional
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
25
29
|
})
|
|
26
30
|
export class AppModule {}
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
The module is not global. It provides and exports `FirehoseService`, `ServerAwsFirehoseConfig`, a `FirehoseClient` instance, and the `AwsCredentials` provider from `@onivoro/server-aws-credential-providers`.
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
## Configuration
|
|
32
36
|
|
|
33
37
|
```typescript
|
|
34
|
-
export class
|
|
38
|
+
export class ServerAwsFirehoseConfig {
|
|
39
|
+
AWS_FIREHOSE_NAME: string; // delivery stream that putRecord writes to
|
|
40
|
+
AWS_PROFILE?: string; // optional named profile from ~/.aws
|
|
35
41
|
AWS_REGION: string;
|
|
36
|
-
AWS_PROFILE?: string; // Optional AWS profile
|
|
37
|
-
AWS_S3_BUCKET: string; // Default bucket name
|
|
38
|
-
AWS_S3_PREFIX?: string; // Optional prefix for all keys
|
|
39
42
|
}
|
|
40
43
|
```
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
### S3Service
|
|
45
|
-
|
|
46
|
-
The service provides file operations:
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
import { Injectable } from '@nestjs/common';
|
|
50
|
-
import { S3Service } from '@onivoro/server-aws-firehose';
|
|
51
|
-
|
|
52
|
-
@Injectable()
|
|
53
|
-
export class FileStorageService {
|
|
54
|
-
constructor(private readonly s3Service: S3Service) {}
|
|
55
|
-
|
|
56
|
-
// Upload a file
|
|
57
|
-
async uploadFile(key: string, body: Buffer | string, contentType: string) {
|
|
58
|
-
const result = await this.s3Service.upload(key, body, contentType);
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Upload a public file
|
|
63
|
-
async uploadPublicFile(key: string, body: Buffer | string, contentType: string) {
|
|
64
|
-
const result = await this.s3Service.uploadPublic(key, body, contentType);
|
|
65
|
-
return result;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Download a file
|
|
69
|
-
async downloadFile(key: string) {
|
|
70
|
-
const fileContent = await this.s3Service.download(key);
|
|
71
|
-
return fileContent;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Get a pre-signed download URL
|
|
75
|
-
async getDownloadUrl(key: string, expiresIn: number = 3600) {
|
|
76
|
-
const url = await this.s3Service.getPresignedDownloadUrl(key, expiresIn);
|
|
77
|
-
return url;
|
|
78
|
-
}
|
|
45
|
+
### AWS Credentials
|
|
79
46
|
|
|
80
|
-
|
|
81
|
-
async deleteFile(key: string) {
|
|
82
|
-
await this.s3Service.delete(key);
|
|
83
|
-
}
|
|
47
|
+
Credentials are resolved by [`@onivoro/server-aws-credential-providers`](../aws-credential-providers/):
|
|
84
48
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
await this.s3Service.deleteByPrefix(prefix);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
```
|
|
49
|
+
- If `AWS_PROFILE` is set, credentials are loaded from that profile in the shared credentials file. If that fails, `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` (or their lowercase forms) from the environment are used.
|
|
50
|
+
- If `AWS_PROFILE` is not set, the client uses the AWS SDK's default credential provider chain.
|
|
91
51
|
|
|
92
|
-
##
|
|
52
|
+
## FirehoseService
|
|
93
53
|
|
|
94
|
-
###
|
|
95
|
-
- **upload(key: string, body: Buffer | string, contentType: string, bucket?: string)** - Upload a private file
|
|
96
|
-
- **uploadPublic(key: string, body: Buffer | string, contentType: string, bucket?: string)** - Upload a publicly accessible file
|
|
54
|
+
### `putRecord(data: any, eventId: string): Promise<void>`
|
|
97
55
|
|
|
98
|
-
|
|
99
|
-
- **download(key: string, bucket?: string)** - Download file content
|
|
100
|
-
- **getPresignedDownloadUrl(key: string, expiresInSeconds?: number, bucket?: string)** - Generate pre-signed download URL
|
|
56
|
+
Serializes `data` with `JSON.stringify`, appends a newline (`\n`) so records are line-delimited in the destination, and sends a `PutRecordCommand` to the `AWS_FIREHOSE_NAME` delivery stream. On failure the error is logged with `console.error` and rethrown.
|
|
101
57
|
|
|
102
|
-
|
|
103
|
-
- **delete(key: string, bucket?: string)** - Delete a single object
|
|
104
|
-
- **deleteByPrefix(prefix: string, bucket?: string)** - Delete all objects with a specific prefix
|
|
105
|
-
|
|
106
|
-
## Direct Client Access
|
|
107
|
-
|
|
108
|
-
The service exposes the underlying S3 client for advanced operations:
|
|
58
|
+
The `eventId` argument is required by the signature but is currently not used.
|
|
109
59
|
|
|
110
60
|
```typescript
|
|
111
|
-
import {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
HeadObjectCommand,
|
|
115
|
-
GetObjectTaggingCommand
|
|
116
|
-
} from '@aws-sdk/client-firehose';
|
|
61
|
+
import { randomUUID } from 'node:crypto';
|
|
62
|
+
import { Injectable } from '@nestjs/common';
|
|
63
|
+
import { FirehoseService } from '@onivoro/server-aws-firehose';
|
|
117
64
|
|
|
118
65
|
@Injectable()
|
|
119
|
-
export class
|
|
120
|
-
constructor(private readonly
|
|
66
|
+
export class AuditLogService {
|
|
67
|
+
constructor(private readonly firehoseService: FirehoseService) {}
|
|
121
68
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const command = new ListObjectsV2Command({
|
|
125
|
-
Bucket: process.env.AWS_S3_BUCKET,
|
|
126
|
-
Prefix: prefix
|
|
127
|
-
});
|
|
69
|
+
async record(userId: string, action: string) {
|
|
70
|
+
const eventId = randomUUID();
|
|
128
71
|
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Copy object
|
|
133
|
-
async copyObject(sourceKey: string, destinationKey: string) {
|
|
134
|
-
const command = new CopyObjectCommand({
|
|
135
|
-
Bucket: process.env.AWS_S3_BUCKET,
|
|
136
|
-
CopySource: `${process.env.AWS_S3_BUCKET}/${sourceKey}`,
|
|
137
|
-
Key: destinationKey
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
return await this.s3Service.s3Client.send(command);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Get object metadata
|
|
144
|
-
async getObjectMetadata(key: string) {
|
|
145
|
-
const command = new HeadObjectCommand({
|
|
146
|
-
Bucket: process.env.AWS_S3_BUCKET,
|
|
147
|
-
Key: key
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return await this.s3Service.s3Client.send(command);
|
|
72
|
+
await this.firehoseService.putRecord({ eventId, userId, action, timestamp: new Date().toISOString() }, eventId);
|
|
151
73
|
}
|
|
152
74
|
}
|
|
153
75
|
```
|
|
154
76
|
|
|
155
|
-
##
|
|
77
|
+
## Direct Client Access
|
|
156
78
|
|
|
157
|
-
|
|
158
|
-
import { Module, Injectable, Controller, Post, Get, Delete, Param, UploadedFile, UseInterceptors } from '@nestjs/common';
|
|
159
|
-
import { FileInterceptor } from '@nestjs/platform-express';
|
|
160
|
-
import { ServerAwsS3Module, S3Service } from '@onivoro/server-aws-firehose';
|
|
79
|
+
The service keeps its client private, but the module exports the `FirehoseClient` provider, so you can inject it for operations the service doesn't cover:
|
|
161
80
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
})
|
|
167
|
-
export class DocumentModule {}
|
|
81
|
+
```typescript
|
|
82
|
+
import { Injectable } from '@nestjs/common';
|
|
83
|
+
import { FirehoseClient, PutRecordBatchCommand } from '@aws-sdk/client-firehose';
|
|
84
|
+
import { ServerAwsFirehoseConfig } from '@onivoro/server-aws-firehose';
|
|
168
85
|
|
|
169
86
|
@Injectable()
|
|
170
|
-
export class
|
|
171
|
-
constructor(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
key,
|
|
186
|
-
location: result.Location,
|
|
187
|
-
etag: result.ETag
|
|
188
|
-
};
|
|
189
|
-
} catch (error) {
|
|
190
|
-
console.error('Upload failed:', error);
|
|
191
|
-
throw error;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async getDocumentUrl(key: string) {
|
|
196
|
-
// Generate URL valid for 1 hour
|
|
197
|
-
const url = await this.s3Service.getPresignedDownloadUrl(key, 3600);
|
|
198
|
-
return { url };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async deleteUserDocuments(userId: string) {
|
|
202
|
-
const prefix = `documents/${userId}/`;
|
|
203
|
-
await this.s3Service.deleteByPrefix(prefix);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async uploadPublicAvatar(userId: string, imageBuffer: Buffer) {
|
|
207
|
-
const key = `avatars/${userId}.jpg`;
|
|
208
|
-
|
|
209
|
-
const result = await this.s3Service.uploadPublic(
|
|
210
|
-
key,
|
|
211
|
-
imageBuffer,
|
|
212
|
-
'image/jpeg'
|
|
87
|
+
export class BatchAuditLogService {
|
|
88
|
+
constructor(
|
|
89
|
+
private readonly firehoseClient: FirehoseClient,
|
|
90
|
+
private readonly config: ServerAwsFirehoseConfig,
|
|
91
|
+
) {}
|
|
92
|
+
|
|
93
|
+
async recordMany(events: object[]) {
|
|
94
|
+
return this.firehoseClient.send(
|
|
95
|
+
new PutRecordBatchCommand({
|
|
96
|
+
DeliveryStreamName: this.config.AWS_FIREHOSE_NAME,
|
|
97
|
+
Records: events.map((e) => ({ Data: Buffer.from(JSON.stringify(e) + '\n') })),
|
|
98
|
+
}),
|
|
213
99
|
);
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
key,
|
|
217
|
-
publicUrl: result.Location
|
|
218
|
-
};
|
|
219
100
|
}
|
|
220
101
|
}
|
|
221
|
-
|
|
222
|
-
@Controller('documents')
|
|
223
|
-
export class DocumentController {
|
|
224
|
-
constructor(private readonly documentService: DocumentService) {}
|
|
225
|
-
|
|
226
|
-
@Post('upload')
|
|
227
|
-
@UseInterceptors(FileInterceptor('file'))
|
|
228
|
-
async uploadDocument(
|
|
229
|
-
@UploadedFile() file: Express.Multer.File
|
|
230
|
-
) {
|
|
231
|
-
const userId = 'user123'; // Get from auth context
|
|
232
|
-
return await this.documentService.uploadDocument(userId, file);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
@Get(':key/url')
|
|
236
|
-
async getDocumentUrl(@Param('key') key: string) {
|
|
237
|
-
return await this.documentService.getDocumentUrl(key);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
@Delete('user/:userId')
|
|
241
|
-
async deleteUserDocuments(@Param('userId') userId: string) {
|
|
242
|
-
await this.documentService.deleteUserDocuments(userId);
|
|
243
|
-
return { message: 'Documents deleted successfully' };
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## Environment Variables
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
# Required
|
|
252
|
-
AWS_REGION=us-east-1
|
|
253
|
-
AWS_S3_BUCKET=my-bucket-name
|
|
254
|
-
|
|
255
|
-
# Optional
|
|
256
|
-
AWS_PROFILE=my-profile
|
|
257
|
-
AWS_S3_PREFIX=my-app/ # Prefix for all keys
|
|
258
102
|
```
|
|
259
103
|
|
|
260
|
-
##
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
try {
|
|
264
|
-
await s3Service.download('non-existent-key');
|
|
265
|
-
} catch (error) {
|
|
266
|
-
if (error.name === 'NoSuchKey') {
|
|
267
|
-
console.error('File not found');
|
|
268
|
-
} else if (error.name === 'AccessDenied') {
|
|
269
|
-
console.error('Permission denied');
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
## Limitations
|
|
275
|
-
|
|
276
|
-
- No multipart upload support (large files must be handled manually)
|
|
277
|
-
- No built-in bucket management operations
|
|
278
|
-
- No object tagging or versioning support
|
|
279
|
-
- Limited to basic CRUD operations
|
|
280
|
-
- For advanced features, use the exposed `s3Client` directly
|
|
281
|
-
|
|
282
|
-
## Best Practices
|
|
104
|
+
## Exports
|
|
283
105
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
4. **Cleanup**: Implement lifecycle policies for automatic object expiration
|
|
288
|
-
5. **Error Handling**: Always handle S3 errors appropriately
|
|
106
|
+
- `ServerAwsFirehoseModule` - dynamic module with `configure(config)`
|
|
107
|
+
- `ServerAwsFirehoseConfig` - configuration class (also injectable)
|
|
108
|
+
- `FirehoseService` - `putRecord(data, eventId)`
|
|
289
109
|
|
|
290
110
|
## License
|
|
291
111
|
|
|
292
|
-
MIT
|
|
112
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onivoro/server-aws-firehose",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "25.0.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -10,13 +10,12 @@
|
|
|
10
10
|
"url": "https://github.com/onivoro/monorepo.git"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@onivoro/server-aws-credential-providers": "
|
|
14
|
-
"@onivoro/server-common": "
|
|
13
|
+
"@onivoro/server-aws-credential-providers": "25.0.2",
|
|
14
|
+
"@onivoro/server-common": "25.0.2",
|
|
15
15
|
"tslib": "^2.3.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"@aws-sdk/client-firehose": "*",
|
|
19
|
-
"@aws-sdk/s3-request-presigner": "*",
|
|
20
19
|
"@nestjs/common": "*"
|
|
21
20
|
},
|
|
22
21
|
"files": [
|