@dataclouder/nest-storage 0.0.9 → 0.0.10

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 ADDED
@@ -0,0 +1,66 @@
1
+ # @dataclouder/nest-storage
2
+
3
+ NestJS library for storage services, providing a unified interface for Google Cloud Storage, Cloudflare R2, Minio, and Local storage.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @dataclouder/nest-storage
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Multi-Provider Support**: Integrated support for Google Cloud Storage, Cloudflare R2, and Minio.
14
+ - **Unified Interface**: Use the same methods regardless of the underlying storage service.
15
+ - **Image Processing**: Automatic conversion of images to WebP format for optimized web delivery.
16
+ - **Batch Operations**: Easily remove all storage files referenced within complex data objects or by URLs.
17
+ - **Security**: Generate signed URLs for temporary access to private files.
18
+ - **Auditable**: Built-in support for tracking uploads with auditable metadata.
19
+
20
+ ## Documentation
21
+
22
+ For detailed guides and API references, please check the documentation files:
23
+
24
+ - [Getting Started](./docs/index.md)
25
+ - [Usage Guide](./docs/usage.md)
26
+ - [Storage Providers](./docs/storage-providers.md)
27
+ - [Frontend Integration](./docs/frontend-integration.md)
28
+ - [Universal Storage](./docs/universal-storage.md)
29
+
30
+ ## Basic Usage
31
+
32
+ 1. **Register the module:**
33
+
34
+ ```typescript
35
+ import { StorageModule } from '@dataclouder/nest-storage';
36
+
37
+ @Module({
38
+ imports: [
39
+ StorageModule.register({
40
+ provider: 'GOOGLE', // or 'CLOUDFLARE', 'MINIO', 'LOCAL'
41
+ bucket: 'my-bucket',
42
+ // ... provider specific config
43
+ }),
44
+ ],
45
+ })
46
+ export class AppModule {}
47
+ ```
48
+
49
+ 2. **Inject the service:**
50
+
51
+ ```typescript
52
+ import { StorageService } from '@dataclouder/nest-storage';
53
+
54
+ @Injectable()
55
+ export class MyService {
56
+ constructor(private storageService: StorageService) {}
57
+
58
+ async uploadFile(file: Buffer, filename: string) {
59
+ return await this.storageService.upload(file, filename);
60
+ }
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ Developed by [dataclouder](https://github.com/dataclouder).
@@ -0,0 +1,98 @@
1
+ # Frontend Integration Guide
2
+
3
+ This guide explains how to connect your frontend application (e.g., Angular) to the Nest Storage service.
4
+
5
+ ## API Endpoint
6
+
7
+ - **URL**: `YOUR_BASE_URL/storage/upload`
8
+ - **Method**: `POST`
9
+ - **Content-Type**: `multipart/form-data`
10
+
11
+ ## Request Parameters
12
+
13
+ Metadata is passed via **Query Parameters** in the URL, while the file itself is sent in the `multipart/form-data` body.
14
+
15
+ ### Query Parameters
16
+
17
+ | Parameter | Type | Required | Description |
18
+ | :--- | :--- | :--- | :--- |
19
+ | `path` | `string` | No | The target directory/path in storage (e.g., `avatars`, `docs/2024`). |
20
+ | `provider` | `string` | No | The storage provider to use (`google`, `cloudflare`, `local`). |
21
+ | `keepOriginalName` | `boolean` | No | If `true`, the file will keep its original name. If `false` (default), a timestamp prefix will be added. |
22
+
23
+ ### Multipart Body
24
+
25
+ | Field | Type | Required | Description |
26
+ | :--- | :--- | :--- | :--- |
27
+ | `file` | `File` | **Yes** | The file object to be uploaded. |
28
+
29
+ > [!TIP]
30
+ > Using Query Parameters for metadata is the most reliable way to ensure the backend receives these values before processing the file stream.
31
+
32
+ ## Example Implementation (Angular)
33
+
34
+ ### 1. Storage Service
35
+
36
+ Create a service to handle the upload logic:
37
+
38
+ ```typescript
39
+ import { Injectable } from '@angular/core';
40
+ import { HttpClient, HttpParams } from '@angular/common/http';
41
+ import { Observable } from 'rxjs';
42
+
43
+ @Injectable({
44
+ providedIn: 'root'
45
+ })
46
+ export class FileUploadService {
47
+ private apiUrl = 'http://localhost:8091/storage/upload';
48
+
49
+ constructor(private http: HttpClient) {}
50
+
51
+ uploadFile(file: File, path?: string, provider?: string, keepOriginalName?: boolean): Observable<any> {
52
+ const formData = new FormData();
53
+ formData.append('file', file);
54
+
55
+ let params = new HttpParams();
56
+ if (path) params = params.set('path', path);
57
+ if (provider) params = params.set('provider', provider);
58
+ if (keepOriginalName) params = params.set('keepOriginalName', 'true');
59
+
60
+ return this.http.post(this.apiUrl, formData, { params });
61
+ }
62
+ }
63
+ ```
64
+
65
+ ### 2. Component Usage
66
+
67
+ ```typescript
68
+ onFileSelected(event: any) {
69
+ const file: File = event.target.files[0];
70
+ if (file) {
71
+ this.uploadService.uploadFile(file, 'user-uploads', 'cloudflare').subscribe({
72
+ next: (response) => {
73
+ console.log('Upload successful', response.url);
74
+ },
75
+ error: (err) => {
76
+ console.error('Upload failed', err);
77
+ }
78
+ });
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Handling Response
84
+
85
+ The service returns a `CloudFileStorage` object:
86
+
87
+ ```json
88
+ {
89
+ "url": "https://storage.googleapis.com/bucket/path/to/file.png",
90
+ "path": "path/to/file.png",
91
+ "bucket": "my-bucket",
92
+ "provider": "google",
93
+ "auditable": {
94
+ "createdBy": "system",
95
+ "updatedBy": "system"
96
+ }
97
+ }
98
+ ```
package/docs/index.md ADDED
@@ -0,0 +1,45 @@
1
+ # Nest Storage Library
2
+
3
+ The `@polilan/nest-storage` library provides a robust and flexible storage abstraction layer for NestJS applications. It simplifies file management by supporting multiple storage providers through a unified interface, allowing developers to switch between providers with minimal configuration changes.
4
+
5
+ ## Features
6
+
7
+ - **Multi-Provider Support**: Integrated support for Google Cloud Storage, Cloudflare R2, and Minio.
8
+ - **Unified Interface**: Use the same methods regardless of the underlying storage service.
9
+ - **Image Processing**: Automatic conversion of images to WebP format for optimized web delivery.
10
+ - **Batch Operations**: Easily remove all storage files referenced within complex data objects or by URLs.
11
+ - **Security**: Generate signed URLs for temporary access to private files.
12
+ - **Auditable**: Built-in support for tracking uploads with auditable metadata.
13
+ - **Simplified Integration**: Support for metadata via URL Query Parameters to avoid multipart ordering issues.
14
+
15
+ ## File Size Limits
16
+
17
+ By default, the application is configured to support file uploads up to **100MB**. This limit is defined at the application level in `src/main.ts` using two configurations:
18
+
19
+ 1. **Fastify Body Limit**: Controls the maximum total request size.
20
+ 2. **Multipart File Size**: Controls the maximum size for individual files.
21
+
22
+ To increase these limits, modify the following in `src/main.ts`:
23
+
24
+ ```typescript
25
+ // src/main.ts
26
+ const app = await NestFactory.create<NestFastifyApplication>(
27
+ AppModule,
28
+ new FastifyAdapter({ bodyLimit: 104857600 }), // 100MB
29
+ { rawBody: true },
30
+ );
31
+
32
+ app.register(multipart as any, {
33
+ limits: {
34
+ fileSize: 104857600, // 100MB
35
+ },
36
+ });
37
+ ```
38
+
39
+ ## References
40
+
41
+ - [Frontend Integration](./frontend-integration.md): How to connect your frontend app to this service.
42
+ - [Storage Providers](./storage-providers.md): Detailed configuration for GOOGLE, CLOUDFLARE, and LOCAL.
43
+ - [Universal Storage](./universal-storage.md): Guide on the universal multi-provider integration.
44
+ - [Usage Guide](./usage.md): Instructions on how to upload, download, and delete files.
45
+ - [Cloud Models](../src/models/cloud.model.ts): Type definitions for storage objects and interfaces.
@@ -0,0 +1,856 @@
1
+ {
2
+ "type": "excalidraw",
3
+ "version": 2,
4
+ "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
5
+ "elements": [
6
+ {
7
+ "id": "zKU82TpmIygY0Mi-qx8h_",
8
+ "type": "rectangle",
9
+ "x": 418.59765625,
10
+ "y": 545.86328125,
11
+ "width": 140.12109375000006,
12
+ "height": 90.33203125000003,
13
+ "angle": 0,
14
+ "strokeColor": "#1e1e1e",
15
+ "backgroundColor": "transparent",
16
+ "fillStyle": "solid",
17
+ "strokeWidth": 2,
18
+ "strokeStyle": "solid",
19
+ "roughness": 1,
20
+ "opacity": 100,
21
+ "groupIds": [],
22
+ "frameId": null,
23
+ "index": "a0",
24
+ "roundness": {
25
+ "type": 3
26
+ },
27
+ "seed": 45896797,
28
+ "version": 232,
29
+ "versionNonce": 972110067,
30
+ "isDeleted": false,
31
+ "boundElements": [
32
+ {
33
+ "type": "text",
34
+ "id": "gS0i9XpVwsd3uMaQ7Wc3B"
35
+ },
36
+ {
37
+ "id": "zDX82GFclXJyCi4qv9eyV",
38
+ "type": "arrow"
39
+ },
40
+ {
41
+ "id": "IxnxNhbSOmTXq5ja1mSVj",
42
+ "type": "arrow"
43
+ }
44
+ ],
45
+ "updated": 1754681035334,
46
+ "link": null,
47
+ "locked": false
48
+ },
49
+ {
50
+ "id": "gS0i9XpVwsd3uMaQ7Wc3B",
51
+ "type": "text",
52
+ "x": 440.3182373046875,
53
+ "y": 566.029296875,
54
+ "width": 96.679931640625,
55
+ "height": 50,
56
+ "angle": 0,
57
+ "strokeColor": "#1e1e1e",
58
+ "backgroundColor": "transparent",
59
+ "fillStyle": "solid",
60
+ "strokeWidth": 2,
61
+ "strokeStyle": "solid",
62
+ "roughness": 1,
63
+ "opacity": 100,
64
+ "groupIds": [],
65
+ "frameId": null,
66
+ "index": "a1",
67
+ "roundness": null,
68
+ "seed": 2034064253,
69
+ "version": 231,
70
+ "versionNonce": 736338579,
71
+ "isDeleted": false,
72
+ "boundElements": null,
73
+ "updated": 1754681035334,
74
+ "link": null,
75
+ "locked": false,
76
+ "text": "Cloudflare\nR2",
77
+ "fontSize": 20,
78
+ "fontFamily": 5,
79
+ "textAlign": "center",
80
+ "verticalAlign": "middle",
81
+ "containerId": "zKU82TpmIygY0Mi-qx8h_",
82
+ "originalText": "Cloudflare\nR2",
83
+ "autoResize": true,
84
+ "lineHeight": 1.25
85
+ },
86
+ {
87
+ "id": "hV2YoJhzBqy1AFhCQtS8g",
88
+ "type": "rectangle",
89
+ "x": 824.197265625,
90
+ "y": 542.203125,
91
+ "width": 168.81250000000003,
92
+ "height": 85,
93
+ "angle": 0,
94
+ "strokeColor": "#1e1e1e",
95
+ "backgroundColor": "transparent",
96
+ "fillStyle": "solid",
97
+ "strokeWidth": 2,
98
+ "strokeStyle": "solid",
99
+ "roughness": 1,
100
+ "opacity": 100,
101
+ "groupIds": [],
102
+ "frameId": null,
103
+ "index": "a2",
104
+ "roundness": {
105
+ "type": 3
106
+ },
107
+ "seed": 1485623059,
108
+ "version": 282,
109
+ "versionNonce": 75405715,
110
+ "isDeleted": false,
111
+ "boundElements": [
112
+ {
113
+ "type": "text",
114
+ "id": "_GqJ3NjTZFY2l6jGde73Q"
115
+ },
116
+ {
117
+ "id": "d_kxHYkH4IzFEfNCqooVY",
118
+ "type": "arrow"
119
+ },
120
+ {
121
+ "id": "U7YLjSVWgy18bJF54ZIgv",
122
+ "type": "arrow"
123
+ }
124
+ ],
125
+ "updated": 1754681036034,
126
+ "link": null,
127
+ "locked": false
128
+ },
129
+ {
130
+ "id": "_GqJ3NjTZFY2l6jGde73Q",
131
+ "type": "text",
132
+ "x": 870.4535446166992,
133
+ "y": 547.203125,
134
+ "width": 76.29994201660156,
135
+ "height": 75,
136
+ "angle": 0,
137
+ "strokeColor": "#1e1e1e",
138
+ "backgroundColor": "transparent",
139
+ "fillStyle": "solid",
140
+ "strokeWidth": 2,
141
+ "strokeStyle": "solid",
142
+ "roughness": 1,
143
+ "opacity": 100,
144
+ "groupIds": [],
145
+ "frameId": null,
146
+ "index": "a3",
147
+ "roundness": null,
148
+ "seed": 988247219,
149
+ "version": 297,
150
+ "versionNonce": 1262219059,
151
+ "isDeleted": false,
152
+ "boundElements": [],
153
+ "updated": 1754681036034,
154
+ "link": null,
155
+ "locked": false,
156
+ "text": "Google\nCloud\nStorage",
157
+ "fontSize": 20,
158
+ "fontFamily": 5,
159
+ "textAlign": "center",
160
+ "verticalAlign": "middle",
161
+ "containerId": "hV2YoJhzBqy1AFhCQtS8g",
162
+ "originalText": "Google\nCloud\nStorage",
163
+ "autoResize": true,
164
+ "lineHeight": 1.25
165
+ },
166
+ {
167
+ "id": "S7FmZauf9BUumx-eDV2Ui",
168
+ "type": "ellipse",
169
+ "x": 709.12109375,
170
+ "y": 163.42578125,
171
+ "width": 135.79687500000003,
172
+ "height": 118.01953125000001,
173
+ "angle": 0,
174
+ "strokeColor": "#1e1e1e",
175
+ "backgroundColor": "transparent",
176
+ "fillStyle": "solid",
177
+ "strokeWidth": 2,
178
+ "strokeStyle": "solid",
179
+ "roughness": 1,
180
+ "opacity": 100,
181
+ "groupIds": [],
182
+ "frameId": null,
183
+ "index": "a4",
184
+ "roundness": {
185
+ "type": 2
186
+ },
187
+ "seed": 877617245,
188
+ "version": 317,
189
+ "versionNonce": 201369885,
190
+ "isDeleted": false,
191
+ "boundElements": [
192
+ {
193
+ "type": "text",
194
+ "id": "HjJuYtJ9JGYBfzg7LOGUV"
195
+ }
196
+ ],
197
+ "updated": 1754681014838,
198
+ "link": null,
199
+ "locked": false
200
+ },
201
+ {
202
+ "id": "HjJuYtJ9JGYBfzg7LOGUV",
203
+ "type": "text",
204
+ "x": 738.0481170948844,
205
+ "y": 209.70934144533368,
206
+ "width": 77.91993713378906,
207
+ "height": 25,
208
+ "angle": 0,
209
+ "strokeColor": "#1e1e1e",
210
+ "backgroundColor": "transparent",
211
+ "fillStyle": "solid",
212
+ "strokeWidth": 2,
213
+ "strokeStyle": "solid",
214
+ "roughness": 1,
215
+ "opacity": 100,
216
+ "groupIds": [],
217
+ "frameId": null,
218
+ "index": "a5",
219
+ "roundness": null,
220
+ "seed": 291142557,
221
+ "version": 231,
222
+ "versionNonce": 2100235485,
223
+ "isDeleted": false,
224
+ "boundElements": null,
225
+ "updated": 1754681001315,
226
+ "link": null,
227
+ "locked": false,
228
+ "text": "Adapter",
229
+ "fontSize": 20,
230
+ "fontFamily": 5,
231
+ "textAlign": "center",
232
+ "verticalAlign": "middle",
233
+ "containerId": "S7FmZauf9BUumx-eDV2Ui",
234
+ "originalText": "Adapter",
235
+ "autoResize": true,
236
+ "lineHeight": 1.25
237
+ },
238
+ {
239
+ "id": "F-P1Bx_9mnTn9U1WE4Qew",
240
+ "type": "ellipse",
241
+ "x": 570.6796875,
242
+ "y": 166.439453125,
243
+ "width": 135.79687500000003,
244
+ "height": 118.01953125000001,
245
+ "angle": 0,
246
+ "strokeColor": "#1e1e1e",
247
+ "backgroundColor": "transparent",
248
+ "fillStyle": "solid",
249
+ "strokeWidth": 2,
250
+ "strokeStyle": "solid",
251
+ "roughness": 1,
252
+ "opacity": 100,
253
+ "groupIds": [],
254
+ "frameId": null,
255
+ "index": "a6",
256
+ "roundness": {
257
+ "type": 2
258
+ },
259
+ "seed": 161735165,
260
+ "version": 302,
261
+ "versionNonce": 1523756957,
262
+ "isDeleted": false,
263
+ "boundElements": [
264
+ {
265
+ "type": "text",
266
+ "id": "U69reRj_gVBHbb2H2COzv"
267
+ }
268
+ ],
269
+ "updated": 1754680999839,
270
+ "link": null,
271
+ "locked": false
272
+ },
273
+ {
274
+ "id": "U69reRj_gVBHbb2H2COzv",
275
+ "type": "text",
276
+ "x": 600.7067093190055,
277
+ "y": 212.72301332033368,
278
+ "width": 75.71994018554688,
279
+ "height": 25,
280
+ "angle": 0,
281
+ "strokeColor": "#1e1e1e",
282
+ "backgroundColor": "transparent",
283
+ "fillStyle": "solid",
284
+ "strokeWidth": 2,
285
+ "strokeStyle": "solid",
286
+ "roughness": 1,
287
+ "opacity": 100,
288
+ "groupIds": [],
289
+ "frameId": null,
290
+ "index": "a7",
291
+ "roundness": null,
292
+ "seed": 238066269,
293
+ "version": 225,
294
+ "versionNonce": 393524221,
295
+ "isDeleted": false,
296
+ "boundElements": [],
297
+ "updated": 1754680999839,
298
+ "link": null,
299
+ "locked": false,
300
+ "text": "Factory",
301
+ "fontSize": 20,
302
+ "fontFamily": 5,
303
+ "textAlign": "center",
304
+ "verticalAlign": "middle",
305
+ "containerId": "F-P1Bx_9mnTn9U1WE4Qew",
306
+ "originalText": "Factory",
307
+ "autoResize": true,
308
+ "lineHeight": 1.25
309
+ },
310
+ {
311
+ "id": "2ElIXzVKadL3xMuB_N3Sq",
312
+ "type": "rectangle",
313
+ "x": 633.78125,
314
+ "y": 459.375,
315
+ "width": 107.33984375,
316
+ "height": 60,
317
+ "angle": 0,
318
+ "strokeColor": "#1e1e1e",
319
+ "backgroundColor": "transparent",
320
+ "fillStyle": "solid",
321
+ "strokeWidth": 2,
322
+ "strokeStyle": "solid",
323
+ "roughness": 1,
324
+ "opacity": 100,
325
+ "groupIds": [],
326
+ "frameId": null,
327
+ "index": "a8",
328
+ "roundness": {
329
+ "type": 3
330
+ },
331
+ "seed": 53723069,
332
+ "version": 85,
333
+ "versionNonce": 1089732157,
334
+ "isDeleted": false,
335
+ "boundElements": [
336
+ {
337
+ "type": "text",
338
+ "id": "Iy5R5J0abW1NbkQIBvZDB"
339
+ },
340
+ {
341
+ "id": "U7YLjSVWgy18bJF54ZIgv",
342
+ "type": "arrow"
343
+ },
344
+ {
345
+ "id": "IxnxNhbSOmTXq5ja1mSVj",
346
+ "type": "arrow"
347
+ }
348
+ ],
349
+ "updated": 1754681031541,
350
+ "link": null,
351
+ "locked": false
352
+ },
353
+ {
354
+ "id": "Iy5R5J0abW1NbkQIBvZDB",
355
+ "type": "text",
356
+ "x": 640.381217956543,
357
+ "y": 464.375,
358
+ "width": 94.13990783691406,
359
+ "height": 50,
360
+ "angle": 0,
361
+ "strokeColor": "#1e1e1e",
362
+ "backgroundColor": "transparent",
363
+ "fillStyle": "solid",
364
+ "strokeWidth": 2,
365
+ "strokeStyle": "solid",
366
+ "roughness": 1,
367
+ "opacity": 100,
368
+ "groupIds": [],
369
+ "frameId": null,
370
+ "index": "a9",
371
+ "roundness": null,
372
+ "seed": 490620221,
373
+ "version": 41,
374
+ "versionNonce": 1005724093,
375
+ "isDeleted": false,
376
+ "boundElements": null,
377
+ "updated": 1754680992573,
378
+ "link": null,
379
+ "locked": false,
380
+ "text": "Storage\nInterface",
381
+ "fontSize": 20,
382
+ "fontFamily": 5,
383
+ "textAlign": "center",
384
+ "verticalAlign": "middle",
385
+ "containerId": "2ElIXzVKadL3xMuB_N3Sq",
386
+ "originalText": "Storage\nInterface",
387
+ "autoResize": true,
388
+ "lineHeight": 1.25
389
+ },
390
+ {
391
+ "id": "SZ0hGztGLOT86sZs4BldH",
392
+ "type": "ellipse",
393
+ "x": 677.87890625,
394
+ "y": 281.8203125,
395
+ "width": 53.12890625,
396
+ "height": 49.5703125,
397
+ "angle": 0,
398
+ "strokeColor": "#1e1e1e",
399
+ "backgroundColor": "transparent",
400
+ "fillStyle": "solid",
401
+ "strokeWidth": 2,
402
+ "strokeStyle": "solid",
403
+ "roughness": 1,
404
+ "opacity": 100,
405
+ "groupIds": [],
406
+ "frameId": null,
407
+ "index": "aB",
408
+ "roundness": {
409
+ "type": 2
410
+ },
411
+ "seed": 583586419,
412
+ "version": 64,
413
+ "versionNonce": 306634685,
414
+ "isDeleted": false,
415
+ "boundElements": [
416
+ {
417
+ "id": "d_kxHYkH4IzFEfNCqooVY",
418
+ "type": "arrow"
419
+ },
420
+ {
421
+ "id": "zDX82GFclXJyCi4qv9eyV",
422
+ "type": "arrow"
423
+ }
424
+ ],
425
+ "updated": 1754681023262,
426
+ "link": null,
427
+ "locked": false
428
+ },
429
+ {
430
+ "id": "d_kxHYkH4IzFEfNCqooVY",
431
+ "type": "arrow",
432
+ "x": 729.6631288573552,
433
+ "y": 331.37856879434014,
434
+ "width": 142.08407262383287,
435
+ "height": 204.9973806164336,
436
+ "angle": 0,
437
+ "strokeColor": "#1e1e1e",
438
+ "backgroundColor": "transparent",
439
+ "fillStyle": "solid",
440
+ "strokeWidth": 2,
441
+ "strokeStyle": "solid",
442
+ "roughness": 1,
443
+ "opacity": 100,
444
+ "groupIds": [],
445
+ "frameId": null,
446
+ "index": "aC",
447
+ "roundness": {
448
+ "type": 2
449
+ },
450
+ "seed": 1955501107,
451
+ "version": 73,
452
+ "versionNonce": 654151891,
453
+ "isDeleted": false,
454
+ "boundElements": null,
455
+ "updated": 1754681036034,
456
+ "link": null,
457
+ "locked": false,
458
+ "points": [
459
+ [
460
+ 0,
461
+ 0
462
+ ],
463
+ [
464
+ 142.08407262383287,
465
+ 204.9973806164336
466
+ ]
467
+ ],
468
+ "lastCommittedPoint": null,
469
+ "startBinding": {
470
+ "elementId": "SZ0hGztGLOT86sZs4BldH",
471
+ "focus": -0.1841803944506745,
472
+ "gap": 9.694575285123364
473
+ },
474
+ "endBinding": {
475
+ "elementId": "hV2YoJhzBqy1AFhCQtS8g",
476
+ "focus": -0.029487470482841595,
477
+ "gap": 11.8515625
478
+ },
479
+ "startArrowhead": null,
480
+ "endArrowhead": "arrow",
481
+ "elbowed": false
482
+ },
483
+ {
484
+ "id": "zDX82GFclXJyCi4qv9eyV",
485
+ "type": "arrow",
486
+ "x": 678.5735093188252,
487
+ "y": 321.70122012756815,
488
+ "width": 165.0295149975866,
489
+ "height": 216.32742106022715,
490
+ "angle": 0,
491
+ "strokeColor": "#1e1e1e",
492
+ "backgroundColor": "transparent",
493
+ "fillStyle": "solid",
494
+ "strokeWidth": 2,
495
+ "strokeStyle": "solid",
496
+ "roughness": 1,
497
+ "opacity": 100,
498
+ "groupIds": [],
499
+ "frameId": null,
500
+ "index": "aD",
501
+ "roundness": {
502
+ "type": 2
503
+ },
504
+ "seed": 603593309,
505
+ "version": 57,
506
+ "versionNonce": 857200691,
507
+ "isDeleted": false,
508
+ "boundElements": null,
509
+ "updated": 1754681035334,
510
+ "link": null,
511
+ "locked": false,
512
+ "points": [
513
+ [
514
+ 0,
515
+ 0
516
+ ],
517
+ [
518
+ -165.0295149975866,
519
+ 216.32742106022715
520
+ ]
521
+ ],
522
+ "lastCommittedPoint": null,
523
+ "startBinding": {
524
+ "elementId": "SZ0hGztGLOT86sZs4BldH",
525
+ "focus": 0.31546367126519276,
526
+ "gap": 3.8708631779442952
527
+ },
528
+ "endBinding": {
529
+ "elementId": "zKU82TpmIygY0Mi-qx8h_",
530
+ "focus": -0.1487326299751002,
531
+ "gap": 13.62890625
532
+ },
533
+ "startArrowhead": null,
534
+ "endArrowhead": "arrow",
535
+ "elbowed": false
536
+ },
537
+ {
538
+ "id": "U7YLjSVWgy18bJF54ZIgv",
539
+ "type": "arrow",
540
+ "x": 821.6725157020725,
541
+ "y": 560.631867333575,
542
+ "width": 68.79617719586997,
543
+ "height": 49.2424623316391,
544
+ "angle": 0,
545
+ "strokeColor": "#1e1e1e",
546
+ "backgroundColor": "transparent",
547
+ "fillStyle": "solid",
548
+ "strokeWidth": 2,
549
+ "strokeStyle": "solid",
550
+ "roughness": 1,
551
+ "opacity": 100,
552
+ "groupIds": [],
553
+ "frameId": null,
554
+ "index": "aE",
555
+ "roundness": {
556
+ "type": 2
557
+ },
558
+ "seed": 1511279475,
559
+ "version": 52,
560
+ "versionNonce": 156892787,
561
+ "isDeleted": false,
562
+ "boundElements": [
563
+ {
564
+ "type": "text",
565
+ "id": "1ypiZG3E9f79on1xod-iq"
566
+ }
567
+ ],
568
+ "updated": 1754681036034,
569
+ "link": null,
570
+ "locked": false,
571
+ "points": [
572
+ [
573
+ 0,
574
+ 0
575
+ ],
576
+ [
577
+ -68.79617719586997,
578
+ -49.2424623316391
579
+ ]
580
+ ],
581
+ "lastCommittedPoint": null,
582
+ "startBinding": {
583
+ "elementId": "hV2YoJhzBqy1AFhCQtS8g",
584
+ "focus": -0.3709598325944409,
585
+ "gap": 2.8762612304445034
586
+ },
587
+ "endBinding": {
588
+ "elementId": "2ElIXzVKadL3xMuB_N3Sq",
589
+ "focus": -0.3619318362760949,
590
+ "gap": 14.022437059284249
591
+ },
592
+ "startArrowhead": null,
593
+ "endArrowhead": "arrow",
594
+ "elbowed": false
595
+ },
596
+ {
597
+ "id": "1ypiZG3E9f79on1xod-iq",
598
+ "type": "text",
599
+ "x": 784.5248489379883,
600
+ "y": 522.84375,
601
+ "width": 4.8799896240234375,
602
+ "height": 25,
603
+ "angle": 0,
604
+ "strokeColor": "#1e1e1e",
605
+ "backgroundColor": "transparent",
606
+ "fillStyle": "solid",
607
+ "strokeWidth": 2,
608
+ "strokeStyle": "solid",
609
+ "roughness": 1,
610
+ "opacity": 100,
611
+ "groupIds": [],
612
+ "frameId": null,
613
+ "index": "aF",
614
+ "roundness": null,
615
+ "seed": 1437393331,
616
+ "version": 3,
617
+ "versionNonce": 56605331,
618
+ "isDeleted": false,
619
+ "boundElements": null,
620
+ "updated": 1754681027628,
621
+ "link": null,
622
+ "locked": false,
623
+ "text": "i",
624
+ "fontSize": 20,
625
+ "fontFamily": 5,
626
+ "textAlign": "center",
627
+ "verticalAlign": "middle",
628
+ "containerId": "U7YLjSVWgy18bJF54ZIgv",
629
+ "originalText": "i",
630
+ "autoResize": true,
631
+ "lineHeight": 1.25
632
+ },
633
+ {
634
+ "id": "IxnxNhbSOmTXq5ja1mSVj",
635
+ "type": "arrow",
636
+ "x": 567.2729560630362,
637
+ "y": 561.8194093598581,
638
+ "width": 57.09010671281408,
639
+ "height": 38.68367646340255,
640
+ "angle": 0,
641
+ "strokeColor": "#1e1e1e",
642
+ "backgroundColor": "transparent",
643
+ "fillStyle": "solid",
644
+ "strokeWidth": 2,
645
+ "strokeStyle": "solid",
646
+ "roughness": 1,
647
+ "opacity": 100,
648
+ "groupIds": [],
649
+ "frameId": null,
650
+ "index": "aG",
651
+ "roundness": {
652
+ "type": 2
653
+ },
654
+ "seed": 380405981,
655
+ "version": 55,
656
+ "versionNonce": 1567906259,
657
+ "isDeleted": false,
658
+ "boundElements": [
659
+ {
660
+ "type": "text",
661
+ "id": "QPaCdreTkf52nEzRe73Oz"
662
+ }
663
+ ],
664
+ "updated": 1754681035334,
665
+ "link": null,
666
+ "locked": false,
667
+ "points": [
668
+ [
669
+ 0,
670
+ 0
671
+ ],
672
+ [
673
+ 57.09010671281408,
674
+ -38.68367646340255
675
+ ]
676
+ ],
677
+ "lastCommittedPoint": null,
678
+ "startBinding": {
679
+ "elementId": "zKU82TpmIygY0Mi-qx8h_",
680
+ "focus": 0.2606141310890022,
681
+ "gap": 10.402882082131438
682
+ },
683
+ "endBinding": {
684
+ "elementId": "2ElIXzVKadL3xMuB_N3Sq",
685
+ "focus": 0.13333072838251991,
686
+ "gap": 14.97915134738753
687
+ },
688
+ "startArrowhead": null,
689
+ "endArrowhead": "arrow",
690
+ "elbowed": false
691
+ },
692
+ {
693
+ "id": "QPaCdreTkf52nEzRe73Oz",
694
+ "type": "text",
695
+ "x": 594.4916458129883,
696
+ "y": 528.78515625,
697
+ "width": 4.8799896240234375,
698
+ "height": 25,
699
+ "angle": 0,
700
+ "strokeColor": "#1e1e1e",
701
+ "backgroundColor": "transparent",
702
+ "fillStyle": "solid",
703
+ "strokeWidth": 2,
704
+ "strokeStyle": "solid",
705
+ "roughness": 1,
706
+ "opacity": 100,
707
+ "groupIds": [],
708
+ "frameId": null,
709
+ "index": "aH",
710
+ "roundness": null,
711
+ "seed": 1968330163,
712
+ "version": 3,
713
+ "versionNonce": 1007267475,
714
+ "isDeleted": false,
715
+ "boundElements": null,
716
+ "updated": 1754681033502,
717
+ "link": null,
718
+ "locked": false,
719
+ "text": "i",
720
+ "fontSize": 20,
721
+ "fontFamily": 5,
722
+ "textAlign": "center",
723
+ "verticalAlign": "middle",
724
+ "containerId": "IxnxNhbSOmTXq5ja1mSVj",
725
+ "originalText": "i",
726
+ "autoResize": true,
727
+ "lineHeight": 1.25
728
+ },
729
+ {
730
+ "id": "lKO56RjY9cHbgbHykBlkJ",
731
+ "type": "line",
732
+ "x": 182.98046875,
733
+ "y": 354.23046875,
734
+ "width": 13.94921875,
735
+ "height": 392.828125,
736
+ "angle": 0,
737
+ "strokeColor": "#1e1e1e",
738
+ "backgroundColor": "transparent",
739
+ "fillStyle": "solid",
740
+ "strokeWidth": 2,
741
+ "strokeStyle": "solid",
742
+ "roughness": 1,
743
+ "opacity": 100,
744
+ "groupIds": [],
745
+ "frameId": null,
746
+ "index": "aJ",
747
+ "roundness": {
748
+ "type": 2
749
+ },
750
+ "seed": 637308787,
751
+ "version": 35,
752
+ "versionNonce": 1915980701,
753
+ "isDeleted": false,
754
+ "boundElements": null,
755
+ "updated": 1754681043943,
756
+ "link": null,
757
+ "locked": false,
758
+ "points": [
759
+ [
760
+ 0,
761
+ 0
762
+ ],
763
+ [
764
+ 13.94921875,
765
+ 392.828125
766
+ ]
767
+ ],
768
+ "lastCommittedPoint": null,
769
+ "startBinding": null,
770
+ "endBinding": null,
771
+ "startArrowhead": null,
772
+ "endArrowhead": null
773
+ },
774
+ {
775
+ "id": "l7mubWsbCqajt3OheF5cK",
776
+ "type": "text",
777
+ "x": -76.84375,
778
+ "y": 375.33203125,
779
+ "width": 208.97984313964844,
780
+ "height": 25,
781
+ "angle": 0,
782
+ "strokeColor": "#1e1e1e",
783
+ "backgroundColor": "transparent",
784
+ "fillStyle": "solid",
785
+ "strokeWidth": 2,
786
+ "strokeStyle": "solid",
787
+ "roughness": 1,
788
+ "opacity": 100,
789
+ "groupIds": [],
790
+ "frameId": null,
791
+ "index": "aK",
792
+ "roundness": null,
793
+ "seed": 1878067293,
794
+ "version": 42,
795
+ "versionNonce": 1521645235,
796
+ "isDeleted": false,
797
+ "boundElements": null,
798
+ "updated": 1754681052002,
799
+ "link": null,
800
+ "locked": false,
801
+ "text": "Environment Variables",
802
+ "fontSize": 20,
803
+ "fontFamily": 5,
804
+ "textAlign": "left",
805
+ "verticalAlign": "top",
806
+ "containerId": null,
807
+ "originalText": "Environment Variables",
808
+ "autoResize": true,
809
+ "lineHeight": 1.25
810
+ },
811
+ {
812
+ "id": "iv8N0YohYV8J6G8rj-QsC",
813
+ "type": "text",
814
+ "x": -335.53515625,
815
+ "y": 449.0078125,
816
+ "width": 405.93975830078125,
817
+ "height": 150,
818
+ "angle": 0,
819
+ "strokeColor": "#1e1e1e",
820
+ "backgroundColor": "transparent",
821
+ "fillStyle": "solid",
822
+ "strokeWidth": 2,
823
+ "strokeStyle": "solid",
824
+ "roughness": 1,
825
+ "opacity": 100,
826
+ "groupIds": [],
827
+ "frameId": null,
828
+ "index": "aL",
829
+ "roundness": null,
830
+ "seed": 1286792915,
831
+ "version": 97,
832
+ "versionNonce": 647556573,
833
+ "isDeleted": false,
834
+ "boundElements": null,
835
+ "updated": 1754681097280,
836
+ "link": null,
837
+ "locked": false,
838
+ "text": "R2_ACCOUNT_ID=\nR2_ACCESS_KEY_ID=\nR2_SECRET_ACCESS_KEY=\nR2_BUCKET_NAME=polil..\nR2_ENDPOINT=https://.r2..com...\nR2_PUBLIC_DOMAIN=https://pu.r2.dev..",
839
+ "fontSize": 20,
840
+ "fontFamily": 5,
841
+ "textAlign": "left",
842
+ "verticalAlign": "top",
843
+ "containerId": null,
844
+ "originalText": "R2_ACCOUNT_ID=\nR2_ACCESS_KEY_ID=\nR2_SECRET_ACCESS_KEY=\nR2_BUCKET_NAME=polil..\nR2_ENDPOINT=https://.r2..com...\nR2_PUBLIC_DOMAIN=https://pu.r2.dev..",
845
+ "autoResize": true,
846
+ "lineHeight": 1.25
847
+ }
848
+ ],
849
+ "appState": {
850
+ "gridSize": 20,
851
+ "gridStep": 5,
852
+ "gridModeEnabled": false,
853
+ "viewBackgroundColor": "#ffffff"
854
+ },
855
+ "files": {}
856
+ }
@@ -0,0 +1,80 @@
1
+ # Storage Provider Configuration
2
+
3
+ This document explains how to configure the storage provider for the application. The system is designed to be flexible, allowing you to switch between different storage services by changing an environment variable.
4
+
5
+ ## Supported Providers
6
+
7
+ - `GOOGLE`: Uses Google Cloud Storage.
8
+ - `CLOUDFLARE`: Uses Cloudflare R2.
9
+ - `LOCAL`: Uses the server's local file system.
10
+
11
+ ## Configuration
12
+
13
+ To select a storage provider, set the `STORAGE_PROVIDER` environment variable in your `.env` file.
14
+
15
+ ```
16
+ STORAGE_PROVIDER=GOOGLE
17
+ ```
18
+
19
+ or
20
+
21
+ ```
22
+ STORAGE_PROVIDER=CLOUDFLARE
23
+ ```
24
+
25
+ If `STORAGE_PROVIDER` is not set, the system will default to `GOOGLE`.
26
+
27
+ ### Google Cloud Storage
28
+
29
+ No additional environment variables are required if you have the Google Cloud SDK configured on your machine. The service uses Application Default Credentials.
30
+
31
+ ### Cloudflare R2
32
+
33
+ To use Cloudflare R2, you must provide the following environment variables:
34
+
35
+ - `CLOUDFLARE_ACCOUNT_ID`: Your Cloudflare account ID.
36
+ - `CLOUDFLARE_R2_ACCESS_KEY_ID`: Your R2 access key ID.
37
+ - `CLOUDFLARE_R2_SECRET_ACCESS_KEY`: Your R2 secret access key.
38
+ - `STORAGE_BUCKET`: The name of your R2 bucket.
39
+ - `R2_PUBLIC_DOMAIN`: The public domain for your R2 bucket, if you have one configured.
40
+
41
+ Example:
42
+
43
+ ```
44
+ CLOUDFLARE_ACCOUNT_ID=...
45
+ CLOUDFLARE_R2_ACCESS_KEY_ID=...
46
+ CLOUDFLARE_R2_SECRET_ACCESS_KEY=...
47
+ STORAGE_BUCKET=my-r2-bucket
48
+ R2_PUBLIC_DOMAIN=https://my-public-bucket.example.com
49
+ ```
50
+
51
+ ### Local Storage
52
+
53
+ To use local storage, set the following environment variables:
54
+
55
+ - `LOCAL_STORAGE_PATH`: The absolute or relative path to the storage directory (defaults to `./uploads`).
56
+ - `LOCAL_STORAGE_BASE_URL`: The base URL used to construct public links (e.g., `http://localhost:3000/storage/files`).
57
+
58
+ Example:
59
+
60
+ ```
61
+ LOCAL_STORAGE_PATH=./uploads
62
+ LOCAL_STORAGE_BASE_URL=http://localhost:3000/storage/files
63
+ ```
64
+
65
+ ## How to Use
66
+
67
+ In your services, you can inject the storage service using the `IStorageService` token:
68
+
69
+ ```typescript
70
+ import { Inject, Injectable } from '@nestjs/common';
71
+ import { IStorageService } from '@polilan/nest-storage';
72
+
73
+ @Injectable()
74
+ export class MyService {
75
+ constructor(@Inject('IStorageService') private readonly storageService: IStorageService) {}
76
+
77
+ async uploadMyFile(file: Buffer) {
78
+ return this.storageService.uploadFile('my-bucket', 'my-file.txt', file);
79
+ }
80
+ }
@@ -0,0 +1,48 @@
1
+ # Universal Storage Integration
2
+
3
+ The `@polilan/nest-storage` library has been updated to support a "Universal" storage flow, allowing the backend to act as a proxy for file uploads. This is particularly useful for supporting local storage or when you want to centralize storage logic in the backend.
4
+
5
+ ## Architecture
6
+
7
+ The system uses a `StorageFactory` to return the appropriate `IStorageService` implementation based on the `STORAGE_PROVIDER` environment variable or a per-request override.
8
+
9
+ ### Supported Providers
10
+ - `GOOGLE`: Google Cloud Storage
11
+ - `CLOUDFLARE`: Cloudflare R2
12
+ - `LOCAL`: Local File System (New)
13
+
14
+ ## API Endpoints
15
+
16
+ The library now includes a `StorageController` that provides the following endpoints:
17
+
18
+ ### 1. Upload File
19
+ `POST /storage/upload`
20
+
21
+ **Body (Multipart Form Data):**
22
+ - `file`: The file to upload.
23
+ - `path` (optional): The target storage path (e.g., `avatars/user-1`).
24
+ - `provider` (optional): Override the default provider (e.g., `LOCAL`).
25
+
26
+ **Behavior:**
27
+ - If the file is an image, it is automatically converted to **WebP** format using Sharp.
28
+ - Returns a `CloudFileStorage` object containing the file URL and metadata.
29
+
30
+ ### 2. Serve Local Files
31
+ `GET /storage/files/:filename(*)`
32
+
33
+ **Behavior:**
34
+ - Serves files stored locally in the `LOCAL_STORAGE_PATH` directory.
35
+ - This endpoint is used when `STORAGE_PROVIDER=LOCAL` is active.
36
+
37
+ ## Local Storage Configuration
38
+
39
+ To use local storage, add the following to your `.env`:
40
+
41
+ ```env
42
+ STORAGE_PROVIDER=LOCAL
43
+ LOCAL_STORAGE_PATH=./uploads
44
+ LOCAL_STORAGE_BASE_URL=http://localhost:3000/storage/files
45
+ ```
46
+
47
+ - `LOCAL_STORAGE_PATH`: The directory where files will be saved on the server.
48
+ - `LOCAL_STORAGE_BASE_URL`: The URL used to access these files from the frontend (should point to the `/storage/files` endpoint).
package/docs/usage.md ADDED
@@ -0,0 +1,145 @@
1
+ # Usage Guide
2
+
3
+ This guide explains how to use the `@polilan/nest-storage` library to manage files in your application.
4
+
5
+ ## Getting Started
6
+
7
+ To use the storage service, you first need to configure the provider as described in the [Storage Providers](./storage-providers.md) documentation.
8
+
9
+
10
+ Then, you can inject the `IStorageService` into your controllers or services:
11
+
12
+ ```typescript
13
+ import { Inject, Injectable } from '@nestjs/common';
14
+ import { IStorageService } from '@polilan/nest-storage';
15
+
16
+ @Injectable()
17
+ export class MyService {
18
+ constructor(
19
+ @Inject('IStorageService') private readonly storageService: IStorageService
20
+ ) {}
21
+ }
22
+ ```
23
+
24
+ ## Saving Files to Storage
25
+
26
+ The library provides several methods to save files, depending on your needs.
27
+
28
+ ### 1. General File Upload
29
+
30
+ Use `uploadFile` for standard file uploads. This method requires an `auditable` object for tracking.
31
+
32
+ ```typescript
33
+ const fileBuffer = Buffer.from('Hello World');
34
+ const fileName = 'documents/hello.txt';
35
+ const auditable = { createdBy: 'user-id', updatedBy: 'user-id' };
36
+
37
+ const result = await this.storageService.uploadFile(
38
+ fileName,
39
+ fileBuffer,
40
+ auditable,
41
+ 'text/plain'
42
+ );
43
+
44
+ console.log('File URL:', result.url);
45
+ ```
46
+
47
+ ### 2. Public File Upload
48
+
49
+ Use `uploadPublicFile` when you want to upload a file and ensure it's publicly accessible.
50
+
51
+ ```typescript
52
+ const result = await this.storageService.uploadPublicFile({
53
+ fileName: 'public/avatar.png',
54
+ fileBuffer: imageBuffer,
55
+ contentType: 'image/png',
56
+ metadata: { userId: '123' }
57
+ });
58
+ ```
59
+
60
+ ### 3. WebP Image Upload
61
+
62
+ Use `uploadWebpImage` to automatically convert an image to WebP and resize it before uploading. This is recommended for web assets.
63
+
64
+ ```typescript
65
+ const result = await this.storageService.uploadWebpImage({
66
+ fileName: 'gallery/photo.jpg',
67
+ imageBuffer: largeImageBuffer,
68
+ resizeWidth: 800, // Optional, defaults to 900
69
+ });
70
+ ```
71
+
72
+ ## Deleting From Storage
73
+
74
+ ### 1. Delete a Single File
75
+
76
+ Use `deleteStorageFile` by providing the bucket and the path (file name).
77
+
78
+ ```typescript
79
+ await this.storageService.deleteStorageFile('my-bucket', 'documents/hello.txt');
80
+ ```
81
+
82
+ ### 2. Delete All Files in an Object
83
+
84
+ If you have a complex object that contains paths to several files, you can remove them all at once.
85
+
86
+ ```typescript
87
+ const myEntity = {
88
+ name: 'Project Alpha',
89
+ files: [
90
+ { path: 'file1.pdf', bucket: 'my-bucket' },
91
+ { path: 'file2.jpg', bucket: 'my-bucket' }
92
+ ]
93
+ };
94
+
95
+ await this.storageService.removeAllStorageFilesPresentInObject(myEntity);
96
+ ```
97
+
98
+ ### 3. Delete by URL
99
+
100
+ If you only have the public URLs of the files, use `removeAllStorageFilesByUrl`.
101
+
102
+ ```typescript
103
+ const urls = [
104
+ 'https://storage.googleapis.com/my-bucket/file1.pdf',
105
+ 'https://storage.googleapis.com/my-bucket/file2.jpg'
106
+ ];
107
+
108
+ await this.storageService.removeAllStorageFilesByUrl({ urls });
109
+ ```
110
+
111
+ ## HTTP API Endpoints
112
+
113
+ The library includes a `StorageController` that exposes endpoints for file management via HTTP.
114
+
115
+ ### 1. Upload a Single File
116
+
117
+ - **Endpoint**: `POST /storage/upload`
118
+ - **Body**: `multipart/form-data` with a `file` field.
119
+ - **Query Params**: `path`, `provider`, `keepOriginalName`.
120
+
121
+ **Example (cURL):**
122
+ ```bash
123
+ curl -X POST "http://localhost:8091/storage/upload?path=my-folder&keepOriginalName=true" \
124
+ -F "file=@/path/to/your/image.png"
125
+ ```
126
+
127
+ ### 2. Upload Multiple Files
128
+
129
+ - **Endpoint**: `POST /storage/upload-multiple`
130
+ - **Body**: `multipart/form-data` with multiple `file` fields (or multiple fields named `file`).
131
+ - **Query Params**: `path`, `provider`, `keepOriginalName`.
132
+
133
+ **Example (cURL):**
134
+ ```bash
135
+ curl -X POST "http://localhost:8091/storage/upload-multiple?path=batch-upload" \
136
+ -F "file=@image1.png" \
137
+ -F "file=@image2.png"
138
+ ```
139
+
140
+ ## Other Utility Methods
141
+
142
+ - **`getFile(bucket, path)`**: Downloads a file and returns it as a Buffer.
143
+ - **`fileExists(bucket, path)`**: Checks if a file exists.
144
+ - **`generateSignedUrl(bucket, path, expires)`**: Generates a temporary URL for private files.
145
+ - **`listFiles(bucket, prefix)`**: Lists files in a bucket, optionally filtered by prefix.
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@dataclouder/nest-storage",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "NestJS library for storage services",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "files": [
8
8
  "**/*.js",
9
9
  "**/*.d.ts",
10
- "**/*.js.map"
10
+ "**/*.js.map",
11
+ "README.md",
12
+ "docs"
11
13
  ],
12
14
  "scripts": {
13
15
  "build": "tsc -p tsconfig.lib.json",