@onivoro/server-aws-firehose 24.39.0 → 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.
Files changed (2) hide show
  1. package/README.md +56 -236
  2. package/package.json +3 -4
package/README.md CHANGED
@@ -1,292 +1,112 @@
1
1
  # @onivoro/server-aws-firehose
2
2
 
3
- AWS S3 integration for NestJS applications with file upload/download capabilities.
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
- ## Overview
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 { ServerAwsS3Module } from '@onivoro/server-aws-firehose';
19
+ import { ServerAwsFirehoseModule } from '@onivoro/server-aws-firehose';
20
20
 
21
21
  @Module({
22
22
  imports: [
23
- ServerAwsS3Module.configure()
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
- ## Configuration
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
- The module uses environment-based configuration:
35
+ ## Configuration
32
36
 
33
37
  ```typescript
34
- export class ServerAwsS3Config {
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
- ## Service
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
- // Delete a single file
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
- // Delete files by prefix
86
- async deleteFilesByPrefix(prefix: string) {
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
- ## Available Methods
52
+ ## FirehoseService
93
53
 
94
- ### Upload Operations
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
- ### Download Operations
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
- ### Delete Operations
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
- ListObjectsV2Command,
113
- CopyObjectCommand,
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 AdvancedS3Service {
120
- constructor(private readonly s3Service: S3Service) {}
66
+ export class AuditLogService {
67
+ constructor(private readonly firehoseService: FirehoseService) {}
121
68
 
122
- // List objects in bucket
123
- async listObjects(prefix?: string) {
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
- return await this.s3Service.s3Client.send(command);
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
- ## Complete Example
77
+ ## Direct Client Access
156
78
 
157
- ```typescript
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
- @Module({
163
- imports: [ServerAwsS3Module.configure()],
164
- controllers: [DocumentController],
165
- providers: [DocumentService]
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 DocumentService {
171
- constructor(private readonly s3Service: S3Service) {}
172
-
173
- async uploadDocument(userId: string, file: Express.Multer.File) {
174
- const key = `documents/${userId}/${Date.now()}-${file.originalname}`;
175
-
176
- try {
177
- // Upload to S3
178
- const result = await this.s3Service.upload(
179
- key,
180
- file.buffer,
181
- file.mimetype
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
- ## Error Handling
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
- 1. **Key Naming**: Use a consistent key naming strategy (e.g., `type/userId/timestamp-filename`)
285
- 2. **Content Types**: Always specify correct content types for proper browser handling
286
- 3. **Security**: Use pre-signed URLs for temporary access instead of public uploads when possible
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": "24.39.0",
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": "24.39.0",
14
- "@onivoro/server-common": "24.39.0",
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": [