@owlmeans/storage-resource 0.1.1 → 0.1.3

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
3
+ Copyright (c) 2026 OwlMeans Common — Fullstack typescript framework
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,512 +1,91 @@
1
1
  # @owlmeans/storage-resource
2
2
 
3
- Object storage resource integration for OwlMeans Common applications. This package provides S3-compatible object storage functionality with file upload, download, type validation, and stream processing capabilities designed for secure file management in distributed applications.
3
+ S3-compatible object storage resource for OwlMeans server applications.
4
4
 
5
5
  ## Overview
6
6
 
7
- The `@owlmeans/storage-resource` package extends the OwlMeans resource system to provide object storage functionality including:
8
-
9
- - **S3-Compatible Storage**: Integration with AWS S3 and S3-compatible storage services
10
- - **File Upload/Download**: Secure file operations with validation and type checking
11
- - **Stream Processing**: Efficient handling of file streams for large files
12
- - **Type Validation**: Automatic file type detection and validation
13
- - **Prefix Management**: Organized file storage with configurable prefixes
14
- - **Resource Integration**: Seamless integration with OwlMeans resource management system
15
-
16
- This package follows the OwlMeans resource pattern and provides server-side storage capabilities for applications requiring file management.
7
+ - `createStorageResource(alias?, configKey?)` — creates an S3-backed storage resource
8
+ - `appendStorageResource(ctx, alias?, configKey?)` — registers the resource in the context
9
+ - `StorageResource` — extends `Resource<StoredRecord>` for uploading and retrieving files
10
+ - `StoredConfigAppend` — config mixin type for storage bucket credentials
17
11
 
18
12
  ## Installation
19
13
 
20
14
  ```bash
21
- npm install @owlmeans/storage-resource
22
- ```
23
-
24
- ## Dependencies
25
-
26
- This package requires and integrates with:
27
- - `@owlmeans/resource`: Base resource management system
28
- - `@owlmeans/server-context`: Server context management
29
- - `@owlmeans/storage-common`: Storage utilities and error types
30
- - `@aws-sdk/client-s3`: AWS S3 SDK for storage operations
31
- - `file-type`: File type detection utilities
32
-
33
- ## Core Concepts
34
-
35
- ### Storage Resource
36
-
37
- A specialized resource that manages file storage operations including upload, download, and metadata management through S3-compatible APIs.
38
-
39
- ### File Validation
40
-
41
- Built-in file type validation ensures that uploaded files match their declared MIME types, providing security against file type spoofing.
42
-
43
- ### Stream Processing
44
-
45
- Efficient handling of file streams allows for processing large files without excessive memory usage.
46
-
47
- ### Bucket Configuration
48
-
49
- Support for multiple storage buckets with different configurations for organizing files by purpose or security level.
50
-
51
- ## API Reference
52
-
53
- ### Types
54
-
55
- #### `StoredRecord`
56
- Interface for stored file records extending ResourceRecord.
57
-
58
- ```typescript
59
- interface StoredRecord extends ResourceRecord {
60
- url?: string // File URL for access
61
- size?: number // File size in bytes
62
- prefix: string // Storage prefix/path
63
- stream?: Readable // File stream for upload
64
- format?: StoredFileFormat // File format information
65
- type?: string // MIME type
66
- bytes?: Uint8Array // File bytes (alternative to stream)
67
- base64?: string // Base64 encoded file data
68
- }
69
- ```
70
-
71
- #### `StorageConfig`
72
- Configuration for storage bucket access.
73
-
74
- ```typescript
75
- interface StorageConfig {
76
- url: string // Storage service URL
77
- apiKey: string // API key in format "keyId:keySecret"
78
- basePrefix: string // Base prefix for all files in this bucket
79
- }
80
- ```
81
-
82
- #### `StorageResource`
83
- Resource interface for storage operations.
84
-
85
- ```typescript
86
- interface StorageResource extends Resource<StoredRecord> {
87
- // Inherits all Resource methods for CRUD operations
88
- // Specialized for file storage with validation
89
- }
90
- ```
91
-
92
- #### `StoredConfigAppend`
93
- Configuration extension for storage bucket definitions.
94
-
95
- ```typescript
96
- interface StoredConfigAppend {
97
- storageBuckets: { [key: string]: StorageConfig }
98
- }
99
- ```
100
-
101
- ### Factory Functions
102
-
103
- #### `createStorageResource(alias?: string, configKey?: string): StorageResource`
104
-
105
- Creates a storage resource instance with S3-compatible operations.
106
-
107
- **Parameters:**
108
- - `alias` (optional): Resource alias (default: `DEFAULT_ALIAS`)
109
- - `configKey` (optional): Configuration key for bucket selection (defaults to alias)
110
-
111
- **Returns:** StorageResource instance
112
-
113
- **Features:**
114
- - File upload with type validation
115
- - Stream processing for efficient memory usage
116
- - S3-compatible storage operations
117
- - Automatic file type detection
118
- - Error handling for storage operations
119
-
120
- **Example:**
121
- ```typescript
122
- import { createStorageResource } from '@owlmeans/storage-resource'
123
-
124
- const storageResource = createStorageResource('files', 'main-bucket')
125
-
126
- // Use with context
127
- context.registerResource(storageResource)
128
- await context.configure().init()
129
-
130
- // Upload file
131
- const uploadedFile = await storageResource.create({
132
- stream: fileStream,
133
- size: fileSize,
134
- type: 'image/jpeg',
135
- prefix: 'uploads/images'
136
- })
137
- ```
138
-
139
- ### Storage Operations
140
-
141
- #### File Upload
142
-
143
- Upload files with validation and type checking:
144
-
145
- ```typescript
146
- const fileRecord = {
147
- stream: fileStream, // Readable stream
148
- size: fileSize, // File size in bytes
149
- type: 'image/jpeg', // Expected MIME type
150
- prefix: 'uploads/avatars' // Storage prefix
151
- }
152
-
153
- try {
154
- const storedFile = await storageResource.create(fileRecord)
155
- console.log(`File uploaded: ${storedFile.url}`)
156
- } catch (error) {
157
- if (error instanceof FileTypeError) {
158
- // Handle file type mismatch
159
- } else if (error instanceof FileStreamError) {
160
- // Handle stream errors
161
- }
162
- }
15
+ bun add @owlmeans/storage-resource
163
16
  ```
164
17
 
165
- #### File Retrieval
18
+ ## Usage
166
19
 
167
- Retrieve file metadata and URLs:
20
+ Add storage config type:
168
21
 
169
22
  ```typescript
170
- // Get file by ID
171
- const file = await storageResource.get(fileId)
172
- console.log(`File URL: ${file.url}`)
173
-
174
- // Search files by prefix
175
- const files = await storageResource.list({
176
- filter: { prefix: 'uploads/images' }
177
- })
178
- ```
179
-
180
- ### Error Handling
181
-
182
- The package provides specific error types for storage operations:
23
+ import type { StoredConfigAppend } from '@owlmeans/storage-resource'
183
24
 
184
- #### `FileStreamError`
185
- Thrown when file stream is missing or invalid.
186
-
187
- ```typescript
188
- if (record.stream == null) {
189
- throw new FileStreamError('no')
190
- }
25
+ interface AppConfig extends BasicConfig, StoredConfigAppend {}
191
26
  ```
192
27
 
193
- #### `FilePropertyError`
194
- Thrown when required file properties are missing.
28
+ Register the resource:
195
29
 
196
30
  ```typescript
197
- if (record.size == null) {
198
- throw new FilePropertyError('size')
199
- }
200
- ```
31
+ import { appendStorageResource } from '@owlmeans/storage-resource'
201
32
 
202
- #### `FileTypeError`
203
- Thrown when file type doesn't match declared MIME type.
204
-
205
- ```typescript
206
- if (type?.mime !== record.type) {
207
- throw new FileTypeError('mime-mismatch')
208
- }
33
+ appendStorageResource(context, 'images')
209
34
  ```
210
35
 
211
- #### `StorageApiError`
212
- Thrown when storage API operations fail.
213
-
214
- ### Constants
36
+ Config (`config.json`):
215
37
 
216
- #### `DEFAULT_ALIAS`
217
- Default alias for storage resources.
218
-
219
- ```typescript
220
- const DEFAULT_ALIAS = 'storage'
221
- ```
222
-
223
- ## Usage Examples
224
-
225
- ### Basic Storage Setup
226
-
227
- ```typescript
228
- import { createStorageResource } from '@owlmeans/storage-resource'
229
- import { makeServerContext } from '@owlmeans/server-context'
230
-
231
- // Configure server context with storage buckets
232
- const context = makeServerContext({
233
- service: 'file-service',
234
- type: AppType.Backend,
235
- layer: Layer.Service,
236
- storageBuckets: {
237
- 'main': {
238
- url: 'my-bucket.s3.amazonaws.com',
239
- apiKey: 'AKIAIOSFODNN7EXAMPLE:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
240
- basePrefix: 'files'
241
- }
242
- }
243
- })
244
-
245
- // Create and register storage resource
246
- const storageResource = createStorageResource('storage', 'main')
247
- context.registerResource(storageResource)
248
-
249
- await context.configure().init()
250
-
251
- // Storage resource is now ready for use
252
- ```
253
-
254
- ### File Upload with Validation
255
-
256
- ```typescript
257
- import { Readable } from 'stream'
258
-
259
- const uploadFile = async (fileBuffer: Buffer, mimeType: string, filename: string) => {
260
- const fileStream = Readable.from(fileBuffer)
261
-
262
- try {
263
- const uploadedFile = await storageResource.create({
264
- stream: fileStream,
265
- size: fileBuffer.length,
266
- type: mimeType,
267
- prefix: `uploads/${new Date().getFullYear()}`
268
- })
269
-
270
- return {
271
- id: uploadedFile.id,
272
- url: uploadedFile.url,
273
- size: uploadedFile.size
274
- }
275
- } catch (error) {
276
- if (error instanceof FileTypeError) {
277
- throw new Error('File type mismatch - uploaded file doesn\'t match declared type')
278
- } else if (error instanceof FilePropertyError) {
279
- throw new Error('Missing required file properties')
280
- } else {
281
- throw new Error('File upload failed')
38
+ ```json
39
+ {
40
+ "storageBuckets": {
41
+ "images": {
42
+ "url": "https://s3.amazonaws.com",
43
+ "apiKey": "...",
44
+ "basePrefix": "my-bucket/images"
282
45
  }
283
46
  }
284
47
  }
285
48
  ```
286
49
 
287
- ### Multiple Storage Buckets
50
+ Upload a file:
288
51
 
289
52
  ```typescript
290
- const context = makeServerContext({
291
- service: 'multi-storage-app',
292
- storageBuckets: {
293
- 'public': {
294
- url: 'public-bucket.s3.amazonaws.com',
295
- apiKey: 'public-key:public-secret',
296
- basePrefix: 'public'
297
- },
298
- 'private': {
299
- url: 'private-bucket.s3.amazonaws.com',
300
- apiKey: 'private-key:private-secret',
301
- basePrefix: 'private'
302
- }
303
- }
304
- })
305
-
306
- // Create resources for different buckets
307
- const publicStorage = createStorageResource('public-storage', 'public')
308
- const privateStorage = createStorageResource('private-storage', 'private')
309
-
310
- context.registerResource(publicStorage)
311
- context.registerResource(privateStorage)
312
-
313
- await context.configure().init()
314
-
315
- // Upload to public bucket
316
- const publicFile = await publicStorage.create({
317
- stream: publicFileStream,
318
- size: fileSize,
319
- type: 'image/jpeg',
320
- prefix: 'avatars'
321
- })
322
-
323
- // Upload to private bucket
324
- const privateFile = await privateStorage.create({
325
- stream: privateFileStream,
326
- size: fileSize,
327
- type: 'application/pdf',
328
- prefix: 'documents'
53
+ const storage = context.resource<StorageResource>('images')
54
+ await storage.save({
55
+ id: fileId,
56
+ prefix: 'uploads',
57
+ stream: readableStream,
58
+ type: 'image/jpeg'
329
59
  })
330
60
  ```
331
61
 
332
- ### File Type Validation
62
+ ## API
333
63
 
334
- ```typescript
335
- const uploadWithValidation = async (fileStream: Readable, declaredType: string, size: number) => {
336
- try {
337
- const result = await storageResource.create({
338
- stream: fileStream,
339
- size: size,
340
- type: declaredType,
341
- prefix: 'validated-uploads'
342
- })
343
-
344
- console.log('File upload successful with type validation')
345
- return result
346
- } catch (error) {
347
- if (error instanceof FileTypeError) {
348
- console.error('File type validation failed - file content doesn\'t match declared MIME type')
349
- throw error
350
- }
351
- throw error
352
- }
353
- }
64
+ ### `createStorageResource(alias?, configKey?): StorageResource`
354
65
 
355
- // Example usage
356
- const jpegStream = fs.createReadStream('image.jpg')
357
- await uploadWithValidation(jpegStream, 'image/jpeg', jpegStats.size)
358
- ```
66
+ Creates an S3 storage resource. `alias` defaults to `DEFAULT_ALIAS` (`'s3-storage'`).
359
67
 
360
- ### Alternative Upload Methods
68
+ ### `appendStorageResource<C, T>(ctx, alias?, configKey?): T`
361
69
 
362
- ```typescript
363
- // Upload from bytes
364
- const uploadFromBytes = async (bytes: Uint8Array, type: string) => {
365
- const stream = Readable.from(Buffer.from(bytes))
366
-
367
- return await storageResource.create({
368
- stream: stream,
369
- size: bytes.length,
370
- type: type,
371
- prefix: 'byte-uploads'
372
- })
373
- }
70
+ Registers the storage resource in the context.
374
71
 
375
- // Upload from base64
376
- const uploadFromBase64 = async (base64Data: string, type: string) => {
377
- const buffer = Buffer.from(base64Data, 'base64')
378
- const stream = Readable.from(buffer)
379
-
380
- return await storageResource.create({
381
- stream: stream,
382
- size: buffer.length,
383
- type: type,
384
- prefix: 'base64-uploads'
385
- })
386
- }
387
- ```
388
-
389
- ### File Retrieval and Management
390
-
391
- ```typescript
392
- // Get file metadata
393
- const getFileInfo = async (fileId: string) => {
394
- const file = await storageResource.get(fileId)
395
-
396
- return {
397
- id: file.id,
398
- url: file.url,
399
- size: file.size,
400
- type: file.type,
401
- prefix: file.prefix
402
- }
403
- }
404
-
405
- // List files by prefix
406
- const listFilesByPrefix = async (prefix: string) => {
407
- const files = await storageResource.list({
408
- filter: { prefix: prefix }
409
- })
410
-
411
- return files.map(file => ({
412
- id: file.id,
413
- url: file.url,
414
- size: file.size
415
- }))
416
- }
72
+ ### `StoredRecord`
417
73
 
418
- // Delete file
419
- const deleteFile = async (fileId: string) => {
420
- await storageResource.remove(fileId)
421
- }
422
- ```
74
+ Extends `ResourceRecord` with: `url?`, `size?`, `prefix`, `stream?`, `format?`, `type?`, `bytes?`, `base64?`
423
75
 
424
- ## Configuration
76
+ ### `StorageConfig`
425
77
 
426
- ### Storage Bucket Configuration
78
+ `{ url: string, apiKey: string, basePrefix: string }`
427
79
 
428
- Configure storage buckets in your server configuration:
80
+ ### `stripData<Input, Output>(file): Output`
429
81
 
430
- ```typescript
431
- interface ServerConfig {
432
- // ... other configuration
433
- storageBuckets: {
434
- [bucketAlias: string]: {
435
- url: string // S3 endpoint URL
436
- apiKey: string // Access key ID and secret key separated by ':'
437
- basePrefix: string // Base prefix for all files
438
- }
439
- }
440
- }
441
- ```
82
+ Strips raw data fields from a `StoredFileWithData` to produce a `StoredFile` (URL-only).
442
83
 
443
- ### Environment Variables
84
+ ### `supportedMimeTypes`
444
85
 
445
- Typical environment-based configuration:
446
-
447
- ```typescript
448
- const config = {
449
- storageBuckets: {
450
- main: {
451
- url: process.env.S3_BUCKET_URL,
452
- apiKey: `${process.env.S3_ACCESS_KEY}:${process.env.S3_SECRET_KEY}`,
453
- basePrefix: process.env.S3_BASE_PREFIX || 'files'
454
- }
455
- }
456
- }
457
- ```
458
-
459
- ## Security Considerations
460
-
461
- ### File Type Validation
462
- - Always validate file types to prevent malicious uploads
463
- - Use MIME type detection from file content, not just extensions
464
- - Implement file size limits to prevent abuse
465
-
466
- ### Access Control
467
- - Use separate buckets for different security levels
468
- - Implement proper API key management and rotation
469
- - Consider signed URLs for temporary access
470
-
471
- ### Storage Security
472
- - Use HTTPS for all storage operations
473
- - Implement proper bucket policies and access controls
474
- - Monitor storage access and usage patterns
475
-
476
- ## Performance Considerations
477
-
478
- - **Stream Processing**: Use streams for large files to minimize memory usage
479
- - **File Size Limits**: Implement appropriate file size limits
480
- - **Concurrent Uploads**: Consider rate limiting for multiple concurrent uploads
481
- - **CDN Integration**: Use CDN for frequently accessed files
482
-
483
- ## Integration with OwlMeans Ecosystem
484
-
485
- This package integrates with:
486
-
487
- - **@owlmeans/resource**: Base resource management patterns
488
- - **@owlmeans/server-context**: Server context and configuration
489
- - **@owlmeans/storage-common**: Shared storage utilities and errors
490
-
491
- ## Best Practices
492
-
493
- 1. **Validate file types** always from content, not just filename
494
- 2. **Use appropriate prefixes** to organize files logically
495
- 3. **Implement file size limits** to prevent abuse
496
- 4. **Handle errors gracefully** with specific error types
497
- 5. **Use separate buckets** for different security levels
498
- 6. **Monitor storage usage** and implement cleanup policies
86
+ Array of MIME types accepted for upload.
499
87
 
500
88
  ## Related Packages
501
89
 
502
- - **@owlmeans/storage-common**: Storage utilities and error types
503
- - **@owlmeans/resource**: Base resource management
504
- - **@owlmeans/server-context**: Server context management
505
-
506
- ## AWS S3 Compatibility
507
-
508
- This package works with:
509
- - **AWS S3**: Native Amazon S3 service
510
- - **MinIO**: Self-hosted S3-compatible storage
511
- - **DigitalOcean Spaces**: S3-compatible cloud storage
512
- - **Other S3-compatible services**: Any service implementing S3 API
90
+ - [`@owlmeans/storage-common`](../storage-common) — shared types (`StoredFileMeta`, `StoredFile`, etc.)
91
+ - [`@owlmeans/image-resource`](../image-resource) — image-specific resource built on top of this
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@owlmeans/storage-resource",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
+ "license": "MIT",
4
5
  "type": "module",
5
6
  "scripts": {
6
7
  "build": "tsc -b",
@@ -24,17 +25,18 @@
24
25
  },
25
26
  "dependencies": {
26
27
  "@aws-sdk/client-s3": "^3.701.0",
27
- "@owlmeans/context": "^0.1.1",
28
- "@owlmeans/error": "^0.1.1",
29
- "@owlmeans/resource": "^0.1.1",
30
- "@owlmeans/server-context": "^0.1.1",
31
- "@owlmeans/storage-common": "^0.1.1",
28
+ "@owlmeans/context": "^0.1.3",
29
+ "@owlmeans/error": "^0.1.3",
30
+ "@owlmeans/resource": "^0.1.3",
31
+ "@owlmeans/server-context": "^0.1.3",
32
+ "@owlmeans/storage-common": "^0.1.3",
32
33
  "file-type": "^19.6.0"
33
34
  },
34
35
  "devDependencies": {
36
+ "@owlmeans/dep-config": "workspace:*",
35
37
  "@types/node": "^24.10.1",
36
38
  "nodemon": "^3.1.11",
37
- "typescript": "^5.8.3"
39
+ "typescript": "^6.0.2"
38
40
  },
39
41
  "publishConfig": {
40
42
  "access": "public"
package/tsconfig.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "extends": [
3
- "../tsconfig.default.json",
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
4
5
  ],
5
6
  "compilerOptions": {
6
- "rootDir": "./src/", /* Specify the root folder within your source files. */
7
- "outDir": "./build/", /* Specify an output folder for all emitted files. */
8
- "moduleResolution": "Bundler",
7
+ "rootDir": "./src/",
8
+ "outDir": "./build/"
9
9
  },
10
10
  "exclude": [
11
11
  "./dist/**/*",