@owlmeans/storage-common 0.1.0
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 +21 -0
- package/README.md +599 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +12 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +14 -0
- package/build/consts.js.map +1 -0
- package/build/errors.d.ts +26 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +49 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/model.d.ts +8 -0
- package/build/model.d.ts.map +1 -0
- package/build/model.js +58 -0
- package/build/model.js.map +1 -0
- package/build/types.d.ts +33 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +37 -0
- package/src/consts.ts +12 -0
- package/src/errors.ts +61 -0
- package/src/index.ts +5 -0
- package/src/model.ts +64 -0
- package/src/types.ts +33 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
# @owlmeans/storage-common
|
|
2
|
+
|
|
3
|
+
Common storage interfaces and utilities for OwlMeans file storage systems. This package provides shared types, validation schemas, and error handling for object storage solutions including S3-compatible storage, image storage, and file management systems.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/storage-common` package serves as the foundation for OwlMeans storage implementations. It provides:
|
|
8
|
+
|
|
9
|
+
- **File Storage Interfaces**: Common types for stored files and file metadata
|
|
10
|
+
- **Validation Schemas**: AJV schemas for file validation and integrity
|
|
11
|
+
- **Status Management**: File status tracking and lifecycle management
|
|
12
|
+
- **Format Support**: Multiple file formats and encoding support
|
|
13
|
+
- **Scope-Based Access**: Authorization scopes for file access control
|
|
14
|
+
- **Error Types**: Specialized error types for storage operations
|
|
15
|
+
- **Instance Management**: Multiple file instances and versions support
|
|
16
|
+
|
|
17
|
+
This package is part of the OwlMeans storage ecosystem:
|
|
18
|
+
- **@owlmeans/storage-common**: Common storage interfaces *(this package)*
|
|
19
|
+
- **@owlmeans/storage-resource**: Storage resource implementations
|
|
20
|
+
- **@owlmeans/static-resource**: Static resource management
|
|
21
|
+
- **@owlmeans/image-resource**: Image-specific storage features
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @owlmeans/storage-common ajv
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Core Concepts
|
|
30
|
+
|
|
31
|
+
### Stored Files
|
|
32
|
+
Files are represented with metadata, multiple instances (different sizes/formats), and access control through scopes.
|
|
33
|
+
|
|
34
|
+
### File Instances
|
|
35
|
+
Each file can have multiple instances representing different versions, sizes, or formats of the same file.
|
|
36
|
+
|
|
37
|
+
### Scope-Based Access
|
|
38
|
+
Files use scope-based authorization to control who can access, modify, or delete files.
|
|
39
|
+
|
|
40
|
+
### Status Tracking
|
|
41
|
+
Files have status indicators to track their lifecycle from upload to processing to availability.
|
|
42
|
+
|
|
43
|
+
## API Reference
|
|
44
|
+
|
|
45
|
+
### Core Types
|
|
46
|
+
|
|
47
|
+
#### `StoredFileMeta`
|
|
48
|
+
|
|
49
|
+
Metadata interface for stored files with authorization and identification.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
interface StoredFileMeta {
|
|
53
|
+
entityId?: string // Associated entity identifier
|
|
54
|
+
sourceName?: string // Original filename from upload
|
|
55
|
+
name?: string // Display name for the file
|
|
56
|
+
title?: string // Human-readable title
|
|
57
|
+
scopes: string[] // Authorization scopes
|
|
58
|
+
mimeType: string // MIME type of the file
|
|
59
|
+
alias: string // Unique file identifier
|
|
60
|
+
status: StoredFileStatus // Current file status
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### `StoredFileInstance`
|
|
65
|
+
|
|
66
|
+
Represents a specific instance of a stored file.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface StoredFileInstance {
|
|
70
|
+
size: number // File size in bytes
|
|
71
|
+
alias: string // Instance identifier
|
|
72
|
+
url: string // Access URL for this instance
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### `StoredFilePayload`
|
|
77
|
+
|
|
78
|
+
Extended instance with actual file data for upload/download operations.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
interface StoredFilePayload extends StoredFileInstance {
|
|
82
|
+
format?: StoredFileFormat // File format information
|
|
83
|
+
bytes?: Uint8Array // Raw file bytes
|
|
84
|
+
base64?: string // Base64 encoded file data
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### `StoredFile`
|
|
89
|
+
|
|
90
|
+
Complete file representation with metadata and all instances.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
interface StoredFile extends StoredFileMeta {
|
|
94
|
+
instances: { [key: string]: StoredFileInstance }
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `StoredFileWithData`
|
|
99
|
+
|
|
100
|
+
File representation including actual file data for each instance.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface StoredFileWithData extends StoredFileMeta {
|
|
104
|
+
format?: StoredFileFormat
|
|
105
|
+
instances: { [key: string]: StoredFilePayload }
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Enums and Constants
|
|
110
|
+
|
|
111
|
+
#### `StoredFileStatus`
|
|
112
|
+
|
|
113
|
+
Enumeration of possible file statuses.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
enum StoredFileStatus {
|
|
117
|
+
Uploaded = 'uploaded', // File has been uploaded
|
|
118
|
+
Processing = 'processing', // File is being processed
|
|
119
|
+
Available = 'available', // File is available for use
|
|
120
|
+
Error = 'error', // File processing failed
|
|
121
|
+
Deleted = 'deleted' // File has been deleted
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### `StoredFileFormat`
|
|
126
|
+
|
|
127
|
+
Enumeration of supported file formats.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
enum StoredFileFormat {
|
|
131
|
+
Original = 'original', // Original uploaded format
|
|
132
|
+
Thumbnail = 'thumbnail', // Thumbnail version
|
|
133
|
+
Compressed = 'compressed', // Compressed version
|
|
134
|
+
Optimized = 'optimized' // Optimized version
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Validation Schemas
|
|
139
|
+
|
|
140
|
+
The package provides AJV schemas for runtime validation:
|
|
141
|
+
|
|
142
|
+
#### `StoredFileMetaSchema`
|
|
143
|
+
|
|
144
|
+
Validates stored file metadata.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const StoredFileMetaSchema: JSONSchemaType<StoredFileMeta>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### `StoredFileInstanceSchema`
|
|
151
|
+
|
|
152
|
+
Validates file instance data.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const StoredFileInstanceSchema: JSONSchemaType<StoredFileInstance>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### `StoredFilePayloadSchema`
|
|
159
|
+
|
|
160
|
+
Validates file payload including data.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const StoredFilePayloadSchema: JSONSchemaType<StoredFilePayload>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Error Types
|
|
167
|
+
|
|
168
|
+
The package defines storage-specific error types for comprehensive error handling.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { StorageError } from '@owlmeans/storage-common'
|
|
172
|
+
|
|
173
|
+
// Storage operation errors
|
|
174
|
+
class StorageError extends OwlMeansError {
|
|
175
|
+
// Storage-specific error handling
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Usage Examples
|
|
180
|
+
|
|
181
|
+
### Basic File Metadata Management
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import {
|
|
185
|
+
StoredFileMeta,
|
|
186
|
+
StoredFileStatus,
|
|
187
|
+
StoredFileMetaSchema
|
|
188
|
+
} from '@owlmeans/storage-common'
|
|
189
|
+
import Ajv from 'ajv'
|
|
190
|
+
|
|
191
|
+
const ajv = new Ajv()
|
|
192
|
+
const validateMeta = ajv.compile(StoredFileMetaSchema)
|
|
193
|
+
|
|
194
|
+
// Create file metadata
|
|
195
|
+
const fileMeta: StoredFileMeta = {
|
|
196
|
+
entityId: 'user-123',
|
|
197
|
+
sourceName: 'document.pdf',
|
|
198
|
+
name: 'Important Document',
|
|
199
|
+
title: 'Project Requirements Document',
|
|
200
|
+
scopes: ['read:documents', 'entity:user-123'],
|
|
201
|
+
mimeType: 'application/pdf',
|
|
202
|
+
alias: 'doc-abc123',
|
|
203
|
+
status: StoredFileStatus.Uploaded
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Validate metadata
|
|
207
|
+
if (validateMeta(fileMeta)) {
|
|
208
|
+
console.log('File metadata is valid')
|
|
209
|
+
} else {
|
|
210
|
+
console.error('Validation errors:', validateMeta.errors)
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### File Instance Management
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import {
|
|
218
|
+
StoredFile,
|
|
219
|
+
StoredFileInstance,
|
|
220
|
+
StoredFileFormat
|
|
221
|
+
} from '@owlmeans/storage-common'
|
|
222
|
+
|
|
223
|
+
// Create file with multiple instances
|
|
224
|
+
const fileWithInstances: StoredFile = {
|
|
225
|
+
// ... metadata
|
|
226
|
+
entityId: 'user-123',
|
|
227
|
+
sourceName: 'photo.jpg',
|
|
228
|
+
name: 'Profile Photo',
|
|
229
|
+
scopes: ['read:photos', 'entity:user-123'],
|
|
230
|
+
mimeType: 'image/jpeg',
|
|
231
|
+
alias: 'photo-xyz789',
|
|
232
|
+
status: StoredFileStatus.Available,
|
|
233
|
+
|
|
234
|
+
instances: {
|
|
235
|
+
original: {
|
|
236
|
+
size: 2048000,
|
|
237
|
+
alias: 'photo-xyz789-original',
|
|
238
|
+
url: 'https://storage.example.com/photos/original/photo-xyz789.jpg'
|
|
239
|
+
},
|
|
240
|
+
thumbnail: {
|
|
241
|
+
size: 51200,
|
|
242
|
+
alias: 'photo-xyz789-thumb',
|
|
243
|
+
url: 'https://storage.example.com/photos/thumbnails/photo-xyz789.jpg'
|
|
244
|
+
},
|
|
245
|
+
compressed: {
|
|
246
|
+
size: 512000,
|
|
247
|
+
alias: 'photo-xyz789-compressed',
|
|
248
|
+
url: 'https://storage.example.com/photos/compressed/photo-xyz789.jpg'
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Access different file instances
|
|
254
|
+
const originalUrl = fileWithInstances.instances.original.url
|
|
255
|
+
const thumbnailUrl = fileWithInstances.instances.thumbnail.url
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### File Upload with Payload
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import {
|
|
262
|
+
StoredFileWithData,
|
|
263
|
+
StoredFilePayload,
|
|
264
|
+
StoredFileFormat
|
|
265
|
+
} from '@owlmeans/storage-common'
|
|
266
|
+
|
|
267
|
+
// File upload with data
|
|
268
|
+
const uploadFile = async (fileData: Uint8Array, metadata: StoredFileMeta): Promise<StoredFileWithData> => {
|
|
269
|
+
const fileWithData: StoredFileWithData = {
|
|
270
|
+
...metadata,
|
|
271
|
+
format: StoredFileFormat.Original,
|
|
272
|
+
instances: {
|
|
273
|
+
original: {
|
|
274
|
+
size: fileData.length,
|
|
275
|
+
alias: `${metadata.alias}-original`,
|
|
276
|
+
url: '', // Will be set after upload
|
|
277
|
+
format: StoredFileFormat.Original,
|
|
278
|
+
bytes: fileData
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return fileWithData
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Usage
|
|
287
|
+
const fileBytes = new Uint8Array([/* file data */])
|
|
288
|
+
const metadata: StoredFileMeta = {
|
|
289
|
+
sourceName: 'upload.png',
|
|
290
|
+
name: 'User Upload',
|
|
291
|
+
scopes: ['read:uploads'],
|
|
292
|
+
mimeType: 'image/png',
|
|
293
|
+
alias: 'upload-123',
|
|
294
|
+
status: StoredFileStatus.Uploaded
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const uploadedFile = await uploadFile(fileBytes, metadata)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Base64 File Handling
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { StoredFilePayload, StoredFileFormat } from '@owlmeans/storage-common'
|
|
304
|
+
|
|
305
|
+
// Convert base64 to file payload
|
|
306
|
+
const base64ToPayload = (base64Data: string, alias: string): StoredFilePayload => {
|
|
307
|
+
// Decode base64 to calculate size
|
|
308
|
+
const bytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0))
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
size: bytes.length,
|
|
312
|
+
alias,
|
|
313
|
+
url: '', // Will be set after storage
|
|
314
|
+
format: StoredFileFormat.Original,
|
|
315
|
+
base64: base64Data,
|
|
316
|
+
bytes
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Convert file payload to base64
|
|
321
|
+
const payloadToBase64 = (payload: StoredFilePayload): string => {
|
|
322
|
+
if (payload.base64) {
|
|
323
|
+
return payload.base64
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (payload.bytes) {
|
|
327
|
+
return btoa(String.fromCharCode(...payload.bytes))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
throw new Error('No data available for base64 conversion')
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Scope-Based Authorization
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import { StoredFileMeta } from '@owlmeans/storage-common'
|
|
338
|
+
|
|
339
|
+
class FileAuthorizationManager {
|
|
340
|
+
canAccess(file: StoredFileMeta, userScopes: string[]): boolean {
|
|
341
|
+
return file.scopes.some(scope => userScopes.includes(scope))
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
canModify(file: StoredFileMeta, userScopes: string[], entityId?: string): boolean {
|
|
345
|
+
// Check if user has modify access
|
|
346
|
+
const hasModifyScope = userScopes.some(scope =>
|
|
347
|
+
scope.startsWith('write:') || scope.startsWith('modify:')
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
// Check entity-specific access
|
|
351
|
+
const hasEntityAccess = entityId && file.entityId === entityId &&
|
|
352
|
+
userScopes.includes(`entity:${entityId}`)
|
|
353
|
+
|
|
354
|
+
return hasModifyScope && (hasEntityAccess || !file.entityId)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
filterAccessibleFiles(files: StoredFileMeta[], userScopes: string[]): StoredFileMeta[] {
|
|
358
|
+
return files.filter(file => this.canAccess(file, userScopes))
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Usage
|
|
363
|
+
const authManager = new FileAuthorizationManager()
|
|
364
|
+
const userScopes = ['read:documents', 'entity:user-123']
|
|
365
|
+
|
|
366
|
+
const canRead = authManager.canAccess(fileMeta, userScopes)
|
|
367
|
+
const canWrite = authManager.canModify(fileMeta, ['write:documents', 'entity:user-123'], 'user-123')
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### File Status Management
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { StoredFile, StoredFileStatus } from '@owlmeans/storage-common'
|
|
374
|
+
|
|
375
|
+
class FileStatusManager {
|
|
376
|
+
updateStatus(file: StoredFile, newStatus: StoredFileStatus): StoredFile {
|
|
377
|
+
return {
|
|
378
|
+
...file,
|
|
379
|
+
status: newStatus
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
isAvailable(file: StoredFile): boolean {
|
|
384
|
+
return file.status === StoredFileStatus.Available
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
isProcessing(file: StoredFile): boolean {
|
|
388
|
+
return file.status === StoredFileStatus.Processing
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
hasError(file: StoredFile): boolean {
|
|
392
|
+
return file.status === StoredFileStatus.Error
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
getProcessingFiles(files: StoredFile[]): StoredFile[] {
|
|
396
|
+
return files.filter(file => this.isProcessing(file))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getAvailableFiles(files: StoredFile[]): StoredFile[] {
|
|
400
|
+
return files.filter(file => this.isAvailable(file))
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Usage
|
|
405
|
+
const statusManager = new FileStatusManager()
|
|
406
|
+
|
|
407
|
+
// Update file status after processing
|
|
408
|
+
const processedFile = statusManager.updateStatus(file, StoredFileStatus.Available)
|
|
409
|
+
|
|
410
|
+
// Check file availability
|
|
411
|
+
if (statusManager.isAvailable(file)) {
|
|
412
|
+
console.log('File is ready for use')
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### File Format Management
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import {
|
|
420
|
+
StoredFileWithData,
|
|
421
|
+
StoredFileFormat,
|
|
422
|
+
StoredFilePayload
|
|
423
|
+
} from '@owlmeans/storage-common'
|
|
424
|
+
|
|
425
|
+
class FileFormatManager {
|
|
426
|
+
addInstance(
|
|
427
|
+
file: StoredFileWithData,
|
|
428
|
+
format: StoredFileFormat,
|
|
429
|
+
payload: StoredFilePayload
|
|
430
|
+
): StoredFileWithData {
|
|
431
|
+
return {
|
|
432
|
+
...file,
|
|
433
|
+
instances: {
|
|
434
|
+
...file.instances,
|
|
435
|
+
[format]: {
|
|
436
|
+
...payload,
|
|
437
|
+
format
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
getInstanceByFormat(file: StoredFileWithData, format: StoredFileFormat): StoredFilePayload | null {
|
|
444
|
+
return file.instances[format] || null
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
hasFormat(file: StoredFileWithData, format: StoredFileFormat): boolean {
|
|
448
|
+
return format in file.instances
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
getAvailableFormats(file: StoredFileWithData): StoredFileFormat[] {
|
|
452
|
+
return Object.keys(file.instances)
|
|
453
|
+
.filter(key => file.instances[key].format)
|
|
454
|
+
.map(key => file.instances[key].format!)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Usage
|
|
459
|
+
const formatManager = new FileFormatManager()
|
|
460
|
+
|
|
461
|
+
// Add thumbnail instance
|
|
462
|
+
const thumbnailPayload: StoredFilePayload = {
|
|
463
|
+
size: 10240,
|
|
464
|
+
alias: 'file-thumb',
|
|
465
|
+
url: 'https://storage.example.com/thumbnails/file-thumb.jpg',
|
|
466
|
+
format: StoredFileFormat.Thumbnail,
|
|
467
|
+
bytes: thumbnailBytes
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const fileWithThumbnail = formatManager.addInstance(
|
|
471
|
+
originalFile,
|
|
472
|
+
StoredFileFormat.Thumbnail,
|
|
473
|
+
thumbnailPayload
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
// Get specific format
|
|
477
|
+
const thumbnail = formatManager.getInstanceByFormat(fileWithThumbnail, StoredFileFormat.Thumbnail)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Validation and Error Handling
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
import {
|
|
484
|
+
StoredFileMetaSchema,
|
|
485
|
+
StoredFileInstanceSchema,
|
|
486
|
+
StorageError
|
|
487
|
+
} from '@owlmeans/storage-common'
|
|
488
|
+
import Ajv from 'ajv'
|
|
489
|
+
|
|
490
|
+
const ajv = new Ajv()
|
|
491
|
+
const validateMeta = ajv.compile(StoredFileMetaSchema)
|
|
492
|
+
const validateInstance = ajv.compile(StoredFileInstanceSchema)
|
|
493
|
+
|
|
494
|
+
class FileValidator {
|
|
495
|
+
validateMetadata(meta: unknown): StoredFileMeta {
|
|
496
|
+
if (!validateMeta(meta)) {
|
|
497
|
+
throw new StorageError('Invalid file metadata', {
|
|
498
|
+
code: 'VALIDATION_ERROR',
|
|
499
|
+
details: validateMeta.errors
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
return meta as StoredFileMeta
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
validateInstance(instance: unknown): StoredFileInstance {
|
|
506
|
+
if (!validateInstance(instance)) {
|
|
507
|
+
throw new StorageError('Invalid file instance', {
|
|
508
|
+
code: 'VALIDATION_ERROR',
|
|
509
|
+
details: validateInstance.errors
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
return instance as StoredFileInstance
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
validateFile(file: unknown): StoredFile {
|
|
516
|
+
const meta = this.validateMetadata(file)
|
|
517
|
+
|
|
518
|
+
if (typeof file === 'object' && file !== null && 'instances' in file) {
|
|
519
|
+
const instances = (file as any).instances
|
|
520
|
+
|
|
521
|
+
if (typeof instances !== 'object') {
|
|
522
|
+
throw new StorageError('Invalid instances object')
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Validate each instance
|
|
526
|
+
Object.values(instances).forEach(instance => {
|
|
527
|
+
this.validateInstance(instance)
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
return file as StoredFile
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
throw new StorageError('Invalid file structure')
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Usage
|
|
538
|
+
const validator = new FileValidator()
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
const validFile = validator.validateFile(untrustedFileData)
|
|
542
|
+
console.log('File validation passed')
|
|
543
|
+
} catch (error) {
|
|
544
|
+
if (error instanceof StorageError) {
|
|
545
|
+
console.error('Storage validation error:', error.message)
|
|
546
|
+
console.error('Details:', error.details)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Integration with Other Packages
|
|
552
|
+
|
|
553
|
+
### Authentication Integration
|
|
554
|
+
```typescript
|
|
555
|
+
import { ScopeValueSchema } from '@owlmeans/auth'
|
|
556
|
+
import { StoredFileMeta } from '@owlmeans/storage-common'
|
|
557
|
+
|
|
558
|
+
// File scopes use authentication scope validation
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Error System Integration
|
|
562
|
+
```typescript
|
|
563
|
+
import { OwlMeansError } from '@owlmeans/error'
|
|
564
|
+
import { StorageError } from '@owlmeans/storage-common'
|
|
565
|
+
|
|
566
|
+
// Storage errors extend the OwlMeans error system
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Resource Integration
|
|
570
|
+
```typescript
|
|
571
|
+
import { StoredFile } from '@owlmeans/storage-common'
|
|
572
|
+
import { Resource } from '@owlmeans/resource'
|
|
573
|
+
|
|
574
|
+
// Storage resources can use these types for consistent file management
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
## Best Practices
|
|
578
|
+
|
|
579
|
+
1. **Scope Management**: Use specific scopes for fine-grained access control
|
|
580
|
+
2. **Status Tracking**: Always update file status during processing
|
|
581
|
+
3. **Instance Management**: Use appropriate instances for different use cases
|
|
582
|
+
4. **Validation**: Always validate file data using provided schemas
|
|
583
|
+
5. **Error Handling**: Use storage-specific error types for better debugging
|
|
584
|
+
6. **Format Support**: Provide multiple formats for better user experience
|
|
585
|
+
7. **Security**: Validate all file metadata and content before storage
|
|
586
|
+
|
|
587
|
+
## Dependencies
|
|
588
|
+
|
|
589
|
+
This package depends on:
|
|
590
|
+
- `@owlmeans/auth` - Authentication and authorization utilities
|
|
591
|
+
- `@owlmeans/error` - Error handling system
|
|
592
|
+
- `ajv` - JSON Schema validation (peer dependency)
|
|
593
|
+
|
|
594
|
+
## Related Packages
|
|
595
|
+
|
|
596
|
+
- [`@owlmeans/storage-resource`](../storage-resource) - Storage resource implementations
|
|
597
|
+
- [`@owlmeans/static-resource`](../static-resource) - Static resource management
|
|
598
|
+
- [`@owlmeans/image-resource`](../image-resource) - Image-specific storage
|
|
599
|
+
- [`@owlmeans/auth`](../auth) - Authentication and authorization
|
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare enum StoredFileStatus {
|
|
2
|
+
Uploaded = "uploaded",
|
|
3
|
+
ProcessingReady = "processing-ready",
|
|
4
|
+
Processed = "processed",
|
|
5
|
+
Cached = "cached",
|
|
6
|
+
Unknown = "unknown"
|
|
7
|
+
}
|
|
8
|
+
export declare enum StoredFileFormat {
|
|
9
|
+
Bytes = "bytes",
|
|
10
|
+
Base64 = "base64"
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=consts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,oBAAY,gBAAgB;IAC1B,QAAQ,aAAa;IACrB,eAAe,qBAAqB;IACpC,SAAS,cAAc;IACvB,MAAM,WAAW;IACjB,OAAO,YAAY;CACpB;AAED,oBAAY,gBAAgB;IAC1B,KAAK,UAAU;IACf,MAAM,WAAW;CAClB"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export var StoredFileStatus;
|
|
2
|
+
(function (StoredFileStatus) {
|
|
3
|
+
StoredFileStatus["Uploaded"] = "uploaded";
|
|
4
|
+
StoredFileStatus["ProcessingReady"] = "processing-ready";
|
|
5
|
+
StoredFileStatus["Processed"] = "processed";
|
|
6
|
+
StoredFileStatus["Cached"] = "cached";
|
|
7
|
+
StoredFileStatus["Unknown"] = "unknown";
|
|
8
|
+
})(StoredFileStatus || (StoredFileStatus = {}));
|
|
9
|
+
export var StoredFileFormat;
|
|
10
|
+
(function (StoredFileFormat) {
|
|
11
|
+
StoredFileFormat["Bytes"] = "bytes";
|
|
12
|
+
StoredFileFormat["Base64"] = "base64";
|
|
13
|
+
})(StoredFileFormat || (StoredFileFormat = {}));
|
|
14
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,gBAMX;AAND,WAAY,gBAAgB;IAC1B,yCAAqB,CAAA;IACrB,wDAAoC,CAAA;IACpC,2CAAuB,CAAA;IACvB,qCAAiB,CAAA;IACjB,uCAAmB,CAAA;AACrB,CAAC,EANW,gBAAgB,KAAhB,gBAAgB,QAM3B;AAED,MAAM,CAAN,IAAY,gBAGX;AAHD,WAAY,gBAAgB;IAC1B,mCAAe,CAAA;IACf,qCAAiB,CAAA;AACnB,CAAC,EAHW,gBAAgB,KAAhB,gBAAgB,QAG3B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ResilientError } from '@owlmeans/error';
|
|
2
|
+
export declare class StoredFileError extends ResilientError {
|
|
3
|
+
static typeName: string;
|
|
4
|
+
constructor(message?: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class OrphanFileError extends StoredFileError {
|
|
7
|
+
static typeName: string;
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class FilePropertyError extends StoredFileError {
|
|
11
|
+
static typeName: string;
|
|
12
|
+
constructor(message?: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class FileStreamError extends FilePropertyError {
|
|
15
|
+
static typeName: string;
|
|
16
|
+
constructor(message?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class StorageApiError extends StoredFileError {
|
|
19
|
+
static typeName: string;
|
|
20
|
+
constructor(message?: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class FileTypeError extends StoredFileError {
|
|
23
|
+
static typeName: string;
|
|
24
|
+
constructor(message?: string);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,eAAgB,SAAQ,cAAc;IACjD,OAAuB,QAAQ,EAAE,MAAM,CAAoB;gBAE/C,OAAO,GAAE,MAAgB;CAGtC;AAED,qBAAa,eAAgB,SAAQ,eAAe;IAClD,OAAuB,QAAQ,EAAE,MAAM,CAAsC;gBAEjE,OAAO,GAAE,MAAgB;CAItC;AAED,qBAAa,iBAAkB,SAAQ,eAAe;IACpD,OAAuB,QAAQ,EAAE,MAAM,CAAwC;gBAEnE,OAAO,GAAE,MAAgB;CAItC;AAED,qBAAa,eAAgB,SAAQ,iBAAiB;IACpD,OAAuB,QAAQ,EAAE,MAAM,CAA0C;gBAErE,OAAO,GAAE,MAAgB;CAItC;AAED,qBAAa,eAAgB,SAAQ,eAAe;IAClD,OAAuB,QAAQ,EAAE,MAAM,CAAmC;gBAE9D,OAAO,GAAE,MAAgB;CAItC;AAED,qBAAa,aAAc,SAAQ,eAAe;IAChD,OAAuB,QAAQ,EAAE,MAAM,CAAoC;gBAE/D,OAAO,GAAE,MAAgB;CAItC"}
|
package/build/errors.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ResilientError } from '@owlmeans/error';
|
|
2
|
+
export class StoredFileError extends ResilientError {
|
|
3
|
+
static typeName = 'StoredFileError';
|
|
4
|
+
constructor(message = 'error') {
|
|
5
|
+
super(StoredFileError.typeName, `stored-file:${message}`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class OrphanFileError extends StoredFileError {
|
|
9
|
+
static typeName = `${StoredFileError.typeName}Orphan`;
|
|
10
|
+
constructor(message = 'error') {
|
|
11
|
+
super(`orphan:${message}`);
|
|
12
|
+
this.type = OrphanFileError.typeName;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class FilePropertyError extends StoredFileError {
|
|
16
|
+
static typeName = `${StoredFileError.typeName}Property`;
|
|
17
|
+
constructor(message = 'error') {
|
|
18
|
+
super(`property:${message}`);
|
|
19
|
+
this.type = FilePropertyError.typeName;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class FileStreamError extends FilePropertyError {
|
|
23
|
+
static typeName = `${StoredFileError.typeName}FileStream`;
|
|
24
|
+
constructor(message = 'error') {
|
|
25
|
+
super(`creation:${message}`);
|
|
26
|
+
this.type = FileStreamError.typeName;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class StorageApiError extends StoredFileError {
|
|
30
|
+
static typeName = `${StoredFileError.typeName}Api`;
|
|
31
|
+
constructor(message = 'error') {
|
|
32
|
+
super(`api:${message}`);
|
|
33
|
+
this.type = StorageApiError.typeName;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class FileTypeError extends StoredFileError {
|
|
37
|
+
static typeName = `${StoredFileError.typeName}Type`;
|
|
38
|
+
constructor(message = 'error') {
|
|
39
|
+
super(`type:${message}`);
|
|
40
|
+
this.type = FileTypeError.typeName;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
ResilientError.registerErrorClass(StoredFileError);
|
|
44
|
+
ResilientError.registerErrorClass(OrphanFileError);
|
|
45
|
+
ResilientError.registerErrorClass(FilePropertyError);
|
|
46
|
+
ResilientError.registerErrorClass(FileStreamError);
|
|
47
|
+
ResilientError.registerErrorClass(StorageApiError);
|
|
48
|
+
ResilientError.registerErrorClass(FileTypeError);
|
|
49
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,OAAO,eAAgB,SAAQ,cAAc;IAC1C,MAAM,CAAU,QAAQ,GAAW,iBAAiB,CAAA;IAE3D,YAAY,UAAkB,OAAO;QACnC,KAAK,CAAC,eAAe,CAAC,QAAQ,EAAE,eAAe,OAAO,EAAE,CAAC,CAAA;IAC3D,CAAC;;AAGH,MAAM,OAAO,eAAgB,SAAQ,eAAe;IAC3C,MAAM,CAAU,QAAQ,GAAW,GAAG,eAAe,CAAC,QAAQ,QAAQ,CAAA;IAE7E,YAAY,UAAkB,OAAO;QACnC,KAAK,CAAC,UAAU,OAAO,EAAE,CAAC,CAAA;QAC1B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;;AAGH,MAAM,OAAO,iBAAkB,SAAQ,eAAe;IAC7C,MAAM,CAAU,QAAQ,GAAW,GAAG,eAAe,CAAC,QAAQ,UAAU,CAAA;IAE/E,YAAY,UAAkB,OAAO;QACnC,KAAK,CAAC,YAAY,OAAO,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC,QAAQ,CAAA;IACxC,CAAC;;AAGH,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IAC7C,MAAM,CAAU,QAAQ,GAAW,GAAG,eAAe,CAAC,QAAQ,YAAY,CAAA;IAEjF,YAAY,UAAkB,OAAO;QACnC,KAAK,CAAC,YAAY,OAAO,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;;AAGH,MAAM,OAAO,eAAgB,SAAQ,eAAe;IAC3C,MAAM,CAAU,QAAQ,GAAW,GAAG,eAAe,CAAC,QAAQ,KAAK,CAAA;IAE1E,YAAY,UAAkB,OAAO;QACnC,KAAK,CAAC,OAAO,OAAO,EAAE,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;;AAGH,MAAM,OAAO,aAAc,SAAQ,eAAe;IACzC,MAAM,CAAU,QAAQ,GAAW,GAAG,eAAe,CAAC,QAAQ,MAAM,CAAA;IAE3E,YAAY,UAAkB,OAAO;QACnC,KAAK,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAA;IACpC,CAAC;;AAGH,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA;AAClD,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA;AAClD,cAAc,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAA;AACpD,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA;AAClD,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA;AAClD,cAAc,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
|
package/build/model.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { JSONSchemaType } from 'ajv';
|
|
2
|
+
import type { StoredFileInstance, StoredFileMeta } from './types.js';
|
|
3
|
+
export declare const StoredFileMetaSchema: JSONSchemaType<StoredFileMeta>;
|
|
4
|
+
export declare const StoredFileInstanceSchema: JSONSchemaType<StoredFileInstance>;
|
|
5
|
+
export declare const StoredFilePayloadSchema: JSONSchemaType<StoredFileInstance>;
|
|
6
|
+
export declare const StoredFileSchema: JSONSchemaType<StoredFileMeta>;
|
|
7
|
+
export declare const StoredFileWithDataSchema: JSONSchemaType<StoredFileMeta>;
|
|
8
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAIpE,eAAO,MAAM,oBAAoB,EAAE,cAAc,CAAC,cAAc,CAc/D,CAAA;AAED,eAAO,MAAM,wBAAwB,EAAE,cAAc,CAAC,kBAAkB,CASvE,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,kBAAkB,CAUhC,CAAA;AAEvC,eAAO,MAAM,gBAAgB,EAAE,cAAc,CAAC,cAAc,CAQzB,CAAA;AAEnC,eAAO,MAAM,wBAAwB,EAAE,cAAc,CAAC,cAAc,CASjC,CAAA"}
|
package/build/model.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { StoredFileFormat, StoredFileStatus } from './consts.js';
|
|
2
|
+
import { EntityValueSchema, IdValueSchema, ScopeValueSchema } from '@owlmeans/auth';
|
|
3
|
+
export const StoredFileMetaSchema = {
|
|
4
|
+
type: 'object',
|
|
5
|
+
properties: {
|
|
6
|
+
entityId: { ...EntityValueSchema, nullable: true },
|
|
7
|
+
sourceName: { type: 'string', minLength: 1, maxLength: 255, nullable: true },
|
|
8
|
+
name: { type: 'string', minLength: 1, maxLength: 128, nullable: true },
|
|
9
|
+
title: { type: 'string', minLength: 1, maxLength: 255, nullable: true },
|
|
10
|
+
scopes: { type: 'array', items: { ...ScopeValueSchema } },
|
|
11
|
+
mimeType: { type: 'string', minLength: 1, maxLength: 32 },
|
|
12
|
+
alias: { ...IdValueSchema },
|
|
13
|
+
status: { type: 'string', enum: Object.values(StoredFileStatus) }
|
|
14
|
+
},
|
|
15
|
+
required: ['scopes', 'mimeType', 'alias', 'status'],
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
};
|
|
18
|
+
export const StoredFileInstanceSchema = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
size: { type: 'number', default: 0 },
|
|
22
|
+
alias: { ...IdValueSchema },
|
|
23
|
+
url: { type: 'string', format: 'uri' }
|
|
24
|
+
},
|
|
25
|
+
required: ['size', 'alias', 'url'],
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
};
|
|
28
|
+
export const StoredFilePayloadSchema = {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
...StoredFileInstanceSchema.properties,
|
|
32
|
+
format: { type: 'string', enum: Object.values(StoredFileFormat), nullable: true },
|
|
33
|
+
bytes: { type: 'array', items: { type: 'uint8' }, nullable: true },
|
|
34
|
+
base64: { type: 'string', format: 'byte', nullable: true },
|
|
35
|
+
},
|
|
36
|
+
required: [...StoredFileInstanceSchema.required],
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
};
|
|
39
|
+
export const StoredFileSchema = {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
...StoredFileMetaSchema.properties,
|
|
43
|
+
instances: { type: 'object', additionalProperties: { ...StoredFileInstanceSchema } },
|
|
44
|
+
},
|
|
45
|
+
required: [...StoredFileMetaSchema.required, 'instances'],
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
};
|
|
48
|
+
export const StoredFileWithDataSchema = {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
...StoredFileMetaSchema.properties,
|
|
52
|
+
format: { type: 'string', enum: Object.values(StoredFileFormat), nullable: true },
|
|
53
|
+
instances: { type: 'object', additionalProperties: { ...StoredFilePayloadSchema } },
|
|
54
|
+
},
|
|
55
|
+
required: [...StoredFileMetaSchema.required, 'instances'],
|
|
56
|
+
additionalProperties: false,
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { StoredFileFormat, StoredFileStatus } from './consts.js';
|
|
2
|
+
export interface StoredFileMeta {
|
|
3
|
+
entityId?: string;
|
|
4
|
+
sourceName?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
scopes: string[];
|
|
8
|
+
mimeType: string;
|
|
9
|
+
alias: string;
|
|
10
|
+
status: StoredFileStatus;
|
|
11
|
+
}
|
|
12
|
+
export interface StoredFileInstance {
|
|
13
|
+
size: number;
|
|
14
|
+
alias: string;
|
|
15
|
+
url: string;
|
|
16
|
+
}
|
|
17
|
+
export interface StoredFilePayload extends StoredFileInstance {
|
|
18
|
+
format?: StoredFileFormat;
|
|
19
|
+
bytes?: Uint8Array;
|
|
20
|
+
base64?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface StoredFile extends StoredFileMeta {
|
|
23
|
+
instances: {
|
|
24
|
+
[key: string]: StoredFileInstance;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface StoredFileWithData extends StoredFileMeta {
|
|
28
|
+
format?: StoredFileFormat;
|
|
29
|
+
instances: {
|
|
30
|
+
[key: string]: StoredFilePayload;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAErE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,SAAS,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAA;KAAE,CAAA;CACjD;AAED,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACxD,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,SAAS,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAA;KAAE,CAAA;CAChD"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/storage-common",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 387 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"main": "build/index.js",
|
|
11
|
+
"module": "build/index.js",
|
|
12
|
+
"types": "build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.js",
|
|
16
|
+
"require": "./build/index.js",
|
|
17
|
+
"default": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"ajv": "*"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@owlmeans/auth": "^0.1.0",
|
|
27
|
+
"@owlmeans/error": "^0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"nodemon": "^3.1.7",
|
|
31
|
+
"typescript": "^5.6.3"
|
|
32
|
+
},
|
|
33
|
+
"private": false,
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/consts.ts
ADDED
package/src/errors.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ResilientError } from '@owlmeans/error'
|
|
2
|
+
|
|
3
|
+
export class StoredFileError extends ResilientError {
|
|
4
|
+
public static override typeName: string = 'StoredFileError'
|
|
5
|
+
|
|
6
|
+
constructor(message: string = 'error') {
|
|
7
|
+
super(StoredFileError.typeName, `stored-file:${message}`)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class OrphanFileError extends StoredFileError {
|
|
12
|
+
public static override typeName: string = `${StoredFileError.typeName}Orphan`
|
|
13
|
+
|
|
14
|
+
constructor(message: string = 'error') {
|
|
15
|
+
super(`orphan:${message}`)
|
|
16
|
+
this.type = OrphanFileError.typeName
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class FilePropertyError extends StoredFileError {
|
|
21
|
+
public static override typeName: string = `${StoredFileError.typeName}Property`
|
|
22
|
+
|
|
23
|
+
constructor(message: string = 'error') {
|
|
24
|
+
super(`property:${message}`)
|
|
25
|
+
this.type = FilePropertyError.typeName
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class FileStreamError extends FilePropertyError {
|
|
30
|
+
public static override typeName: string = `${StoredFileError.typeName}FileStream`
|
|
31
|
+
|
|
32
|
+
constructor(message: string = 'error') {
|
|
33
|
+
super(`creation:${message}`)
|
|
34
|
+
this.type = FileStreamError.typeName
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class StorageApiError extends StoredFileError {
|
|
39
|
+
public static override typeName: string = `${StoredFileError.typeName}Api`
|
|
40
|
+
|
|
41
|
+
constructor(message: string = 'error') {
|
|
42
|
+
super(`api:${message}`)
|
|
43
|
+
this.type = StorageApiError.typeName
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class FileTypeError extends StoredFileError {
|
|
48
|
+
public static override typeName: string = `${StoredFileError.typeName}Type`
|
|
49
|
+
|
|
50
|
+
constructor(message: string = 'error') {
|
|
51
|
+
super(`type:${message}`)
|
|
52
|
+
this.type = FileTypeError.typeName
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
ResilientError.registerErrorClass(StoredFileError)
|
|
57
|
+
ResilientError.registerErrorClass(OrphanFileError)
|
|
58
|
+
ResilientError.registerErrorClass(FilePropertyError)
|
|
59
|
+
ResilientError.registerErrorClass(FileStreamError)
|
|
60
|
+
ResilientError.registerErrorClass(StorageApiError)
|
|
61
|
+
ResilientError.registerErrorClass(FileTypeError)
|
package/src/index.ts
ADDED
package/src/model.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { JSONSchemaType } from 'ajv'
|
|
2
|
+
import type { StoredFileInstance, StoredFileMeta } from './types.js'
|
|
3
|
+
import { StoredFileFormat, StoredFileStatus } from './consts.js'
|
|
4
|
+
import { EntityValueSchema, IdValueSchema, ScopeValueSchema } from '@owlmeans/auth'
|
|
5
|
+
|
|
6
|
+
export const StoredFileMetaSchema: JSONSchemaType<StoredFileMeta> = {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
entityId: { ...EntityValueSchema, nullable: true },
|
|
10
|
+
sourceName: { type: 'string', minLength: 1, maxLength: 255, nullable: true },
|
|
11
|
+
name: { type: 'string', minLength: 1, maxLength: 128, nullable: true },
|
|
12
|
+
title: { type: 'string', minLength: 1, maxLength: 255, nullable: true },
|
|
13
|
+
scopes: { type: 'array', items: { ...ScopeValueSchema } },
|
|
14
|
+
mimeType: { type: 'string', minLength: 1, maxLength: 32 },
|
|
15
|
+
alias: { ...IdValueSchema },
|
|
16
|
+
status: { type: 'string', enum: Object.values(StoredFileStatus) }
|
|
17
|
+
},
|
|
18
|
+
required: ['scopes', 'mimeType', 'alias', 'status'],
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const StoredFileInstanceSchema: JSONSchemaType<StoredFileInstance> = {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
size: { type: 'number', default: 0 },
|
|
26
|
+
alias: { ...IdValueSchema },
|
|
27
|
+
url: { type: 'string', format: 'uri' }
|
|
28
|
+
},
|
|
29
|
+
required: ['size', 'alias', 'url'],
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const StoredFilePayloadSchema: JSONSchemaType<StoredFileInstance> = {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
...StoredFileInstanceSchema.properties,
|
|
37
|
+
format: { type: 'string', enum: Object.values(StoredFileFormat), nullable: true },
|
|
38
|
+
bytes: { type: 'array', items: { type: 'uint8' }, nullable: true },
|
|
39
|
+
base64: { type: 'string', format: 'byte', nullable: true },
|
|
40
|
+
},
|
|
41
|
+
required: [...StoredFileInstanceSchema.required],
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
} as JSONSchemaType<StoredFileInstance>
|
|
44
|
+
|
|
45
|
+
export const StoredFileSchema: JSONSchemaType<StoredFileMeta> = {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
...StoredFileMetaSchema.properties,
|
|
49
|
+
instances: { type: 'object', additionalProperties: { ...StoredFileInstanceSchema } },
|
|
50
|
+
},
|
|
51
|
+
required: [...StoredFileMetaSchema.required, 'instances'],
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
} as JSONSchemaType<StoredFileMeta>
|
|
54
|
+
|
|
55
|
+
export const StoredFileWithDataSchema: JSONSchemaType<StoredFileMeta> = {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
...StoredFileMetaSchema.properties,
|
|
59
|
+
format: { type: 'string', enum: Object.values(StoredFileFormat), nullable: true },
|
|
60
|
+
instances: { type: 'object', additionalProperties: { ...StoredFilePayloadSchema } },
|
|
61
|
+
},
|
|
62
|
+
required: [...StoredFileMetaSchema.required, 'instances'],
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
} as JSONSchemaType<StoredFileMeta>
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { StoredFileFormat, StoredFileStatus } from './consts.js'
|
|
2
|
+
|
|
3
|
+
export interface StoredFileMeta {
|
|
4
|
+
entityId?: string
|
|
5
|
+
sourceName?: string
|
|
6
|
+
name?: string
|
|
7
|
+
title?: string
|
|
8
|
+
scopes: string[]
|
|
9
|
+
mimeType: string
|
|
10
|
+
alias: string
|
|
11
|
+
status: StoredFileStatus
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StoredFileInstance {
|
|
15
|
+
size: number
|
|
16
|
+
alias: string
|
|
17
|
+
url: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface StoredFilePayload extends StoredFileInstance {
|
|
21
|
+
format?: StoredFileFormat
|
|
22
|
+
bytes?: Uint8Array
|
|
23
|
+
base64?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StoredFile extends StoredFileMeta {
|
|
27
|
+
instances: { [key: string]: StoredFileInstance }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface StoredFileWithData extends StoredFileMeta {
|
|
31
|
+
format?: StoredFileFormat
|
|
32
|
+
instances: { [key: string]: StoredFilePayload }
|
|
33
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
],
|
|
5
|
+
"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",
|
|
9
|
+
},
|
|
10
|
+
"exclude": [
|
|
11
|
+
"./dist/**/*",
|
|
12
|
+
"./build/**/*",
|
|
13
|
+
"./*.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/consts.ts","./src/errors.ts","./src/index.ts","./src/model.ts","./src/types.ts"],"version":"5.6.3"}
|