@objectstack/service-storage 4.0.2 → 4.0.4
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +15 -0
- package/README.md +466 -0
- package/package.json +5 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/service-storage@4.0.
|
|
2
|
+
> @objectstack/service-storage@4.0.4 build /home/runner/work/framework/framework/packages/services/service-storage
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
13
|
[32mESM[39m [1mdist/index.js [22m[32m4.04 KB[39m
|
|
14
14
|
[32mESM[39m [1mdist/index.js.map [22m[32m10.95 KB[39m
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 67ms
|
|
16
16
|
[32mCJS[39m [1mdist/index.cjs [22m[32m5.36 KB[39m
|
|
17
17
|
[32mCJS[39m [1mdist/index.cjs.map [22m[32m11.56 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 69ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 14628ms
|
|
21
21
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m4.20 KB[39m
|
|
22
22
|
[32mDTS[39m [1mdist/index.d.cts [22m[32m4.20 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @objectstack/service-storage
|
|
2
2
|
|
|
3
|
+
## 4.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [326b66b]
|
|
8
|
+
- @objectstack/spec@4.0.4
|
|
9
|
+
- @objectstack/core@4.0.4
|
|
10
|
+
|
|
11
|
+
## 4.0.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- @objectstack/spec@4.0.3
|
|
16
|
+
- @objectstack/core@4.0.3
|
|
17
|
+
|
|
3
18
|
## 4.0.2
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/README.md
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# @objectstack/service-storage
|
|
2
|
+
|
|
3
|
+
Storage Service for ObjectStack — implements `IStorageService` with local filesystem and S3 adapter skeleton.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple Adapters**: Local filesystem (development) and S3-compatible storage (production)
|
|
8
|
+
- **File Upload**: Upload files with automatic path management
|
|
9
|
+
- **File Download**: Retrieve files with streaming support
|
|
10
|
+
- **URL Generation**: Generate signed URLs for secure access
|
|
11
|
+
- **Metadata**: Store and retrieve file metadata
|
|
12
|
+
- **Directory Operations**: Create, list, and delete directories
|
|
13
|
+
- **Multipart Upload**: Support for large file uploads
|
|
14
|
+
- **Type-Safe**: Full TypeScript support
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @objectstack/service-storage
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For S3 adapter:
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Basic Usage
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { defineStack } from '@objectstack/spec';
|
|
31
|
+
import { ServiceStorage } from '@objectstack/service-storage';
|
|
32
|
+
|
|
33
|
+
const stack = defineStack({
|
|
34
|
+
services: [
|
|
35
|
+
ServiceStorage.configure({
|
|
36
|
+
adapter: 'local', // or 's3'
|
|
37
|
+
basePath: './uploads',
|
|
38
|
+
}),
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
### Local Filesystem Adapter (Development)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
ServiceStorage.configure({
|
|
49
|
+
adapter: 'local',
|
|
50
|
+
basePath: './uploads',
|
|
51
|
+
baseUrl: 'http://localhost:3000/uploads',
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### S3 Adapter (Production)
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
ServiceStorage.configure({
|
|
59
|
+
adapter: 's3',
|
|
60
|
+
s3: {
|
|
61
|
+
bucket: 'my-bucket',
|
|
62
|
+
region: 'us-east-1',
|
|
63
|
+
credentials: {
|
|
64
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
65
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
baseUrl: 'https://my-bucket.s3.amazonaws.com',
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### S3-Compatible Services (Cloudflare R2, DigitalOcean Spaces, MinIO)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
ServiceStorage.configure({
|
|
76
|
+
adapter: 's3',
|
|
77
|
+
s3: {
|
|
78
|
+
bucket: 'my-bucket',
|
|
79
|
+
region: 'auto',
|
|
80
|
+
endpoint: 'https://r2.cloudflarestorage.com/account-id',
|
|
81
|
+
credentials: {
|
|
82
|
+
accessKeyId: process.env.R2_ACCESS_KEY_ID,
|
|
83
|
+
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Service API
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// Get storage service
|
|
93
|
+
const storage = kernel.getService<IStorageService>('storage');
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### File Upload
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Upload a file from buffer
|
|
100
|
+
await storage.upload({
|
|
101
|
+
path: 'documents/contract.pdf',
|
|
102
|
+
data: fileBuffer,
|
|
103
|
+
contentType: 'application/pdf',
|
|
104
|
+
metadata: {
|
|
105
|
+
userId: 'user:123',
|
|
106
|
+
category: 'contracts',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Upload from stream
|
|
111
|
+
await storage.uploadStream({
|
|
112
|
+
path: 'videos/demo.mp4',
|
|
113
|
+
stream: fileStream,
|
|
114
|
+
contentType: 'video/mp4',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Upload with automatic path generation
|
|
118
|
+
const path = await storage.uploadAuto({
|
|
119
|
+
data: fileBuffer,
|
|
120
|
+
fileName: 'profile.jpg',
|
|
121
|
+
folder: 'avatars',
|
|
122
|
+
contentType: 'image/jpeg',
|
|
123
|
+
});
|
|
124
|
+
// Returns: 'avatars/2024/01/15/abc123-profile.jpg'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### File Download
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Download file as buffer
|
|
131
|
+
const file = await storage.download('documents/contract.pdf');
|
|
132
|
+
console.log(file.data); // Buffer
|
|
133
|
+
console.log(file.contentType); // 'application/pdf'
|
|
134
|
+
console.log(file.size); // File size in bytes
|
|
135
|
+
|
|
136
|
+
// Download as stream
|
|
137
|
+
const stream = await storage.downloadStream('videos/demo.mp4');
|
|
138
|
+
stream.pipe(res); // Pipe to HTTP response
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### File Management
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Check if file exists
|
|
145
|
+
const exists = await storage.exists('documents/contract.pdf');
|
|
146
|
+
|
|
147
|
+
// Get file metadata
|
|
148
|
+
const metadata = await storage.getMetadata('documents/contract.pdf');
|
|
149
|
+
// {
|
|
150
|
+
// size: 1024000,
|
|
151
|
+
// contentType: 'application/pdf',
|
|
152
|
+
// lastModified: Date,
|
|
153
|
+
// metadata: { userId: 'user:123', category: 'contracts' }
|
|
154
|
+
// }
|
|
155
|
+
|
|
156
|
+
// Delete file
|
|
157
|
+
await storage.delete('documents/contract.pdf');
|
|
158
|
+
|
|
159
|
+
// Copy file
|
|
160
|
+
await storage.copy({
|
|
161
|
+
from: 'documents/contract.pdf',
|
|
162
|
+
to: 'archive/2024/contract.pdf',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Move file
|
|
166
|
+
await storage.move({
|
|
167
|
+
from: 'temp/upload.pdf',
|
|
168
|
+
to: 'documents/contract.pdf',
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Directory Operations
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// List files in directory
|
|
176
|
+
const files = await storage.list('documents', {
|
|
177
|
+
recursive: false,
|
|
178
|
+
limit: 100,
|
|
179
|
+
});
|
|
180
|
+
// Returns: ['contract.pdf', 'invoice.pdf', 'report.docx']
|
|
181
|
+
|
|
182
|
+
// List with metadata
|
|
183
|
+
const files = await storage.listDetailed('documents');
|
|
184
|
+
// Returns: [
|
|
185
|
+
// { path: 'contract.pdf', size: 1024000, lastModified: Date },
|
|
186
|
+
// { path: 'invoice.pdf', size: 512000, lastModified: Date },
|
|
187
|
+
// ]
|
|
188
|
+
|
|
189
|
+
// Delete directory and all contents
|
|
190
|
+
await storage.deleteDirectory('temp');
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### URL Generation
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Generate public URL (for public files)
|
|
197
|
+
const url = storage.getUrl('public/logo.png');
|
|
198
|
+
// 'https://my-bucket.s3.amazonaws.com/public/logo.png'
|
|
199
|
+
|
|
200
|
+
// Generate signed URL (for private files, expires in 1 hour)
|
|
201
|
+
const signedUrl = await storage.getSignedUrl('documents/contract.pdf', {
|
|
202
|
+
expiresIn: 3600,
|
|
203
|
+
operation: 'read', // or 'write'
|
|
204
|
+
});
|
|
205
|
+
// 'https://my-bucket.s3.amazonaws.com/documents/contract.pdf?X-Amz-Signature=...'
|
|
206
|
+
|
|
207
|
+
// Generate upload URL (for direct client uploads)
|
|
208
|
+
const uploadUrl = await storage.getUploadUrl('uploads/temp.pdf', {
|
|
209
|
+
expiresIn: 900, // 15 minutes
|
|
210
|
+
contentType: 'application/pdf',
|
|
211
|
+
maxSize: 10485760, // 10MB
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Advanced Features
|
|
216
|
+
|
|
217
|
+
### Multipart Upload (Large Files)
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// Initialize multipart upload
|
|
221
|
+
const uploadId = await storage.initMultipartUpload({
|
|
222
|
+
path: 'large-files/video.mp4',
|
|
223
|
+
contentType: 'video/mp4',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Upload parts (can be done in parallel)
|
|
227
|
+
const parts = [];
|
|
228
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
229
|
+
const part = await storage.uploadPart({
|
|
230
|
+
uploadId,
|
|
231
|
+
partNumber: i + 1,
|
|
232
|
+
data: chunks[i],
|
|
233
|
+
});
|
|
234
|
+
parts.push(part);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Complete multipart upload
|
|
238
|
+
await storage.completeMultipartUpload({
|
|
239
|
+
uploadId,
|
|
240
|
+
parts,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Or abort if failed
|
|
244
|
+
await storage.abortMultipartUpload(uploadId);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Direct Browser Upload
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Server: Generate presigned POST URL
|
|
251
|
+
const presignedPost = await storage.getPresignedPost({
|
|
252
|
+
path: 'uploads/${filename}',
|
|
253
|
+
conditions: [
|
|
254
|
+
['content-length-range', 0, 10485760], // Max 10MB
|
|
255
|
+
['starts-with', '$Content-Type', 'image/'], // Only images
|
|
256
|
+
],
|
|
257
|
+
expiresIn: 900, // 15 minutes
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Client: Upload directly to S3 from browser
|
|
261
|
+
const formData = new FormData();
|
|
262
|
+
Object.entries(presignedPost.fields).forEach(([key, value]) => {
|
|
263
|
+
formData.append(key, value);
|
|
264
|
+
});
|
|
265
|
+
formData.append('file', file);
|
|
266
|
+
|
|
267
|
+
await fetch(presignedPost.url, {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
body: formData,
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Image Processing Integration
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// Upload original image
|
|
277
|
+
await storage.upload({
|
|
278
|
+
path: 'images/original/photo.jpg',
|
|
279
|
+
data: imageBuffer,
|
|
280
|
+
contentType: 'image/jpeg',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Generate and upload thumbnails
|
|
284
|
+
const thumbnail = await resizeImage(imageBuffer, { width: 200, height: 200 });
|
|
285
|
+
await storage.upload({
|
|
286
|
+
path: 'images/thumbnails/photo.jpg',
|
|
287
|
+
data: thumbnail,
|
|
288
|
+
contentType: 'image/jpeg',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const medium = await resizeImage(imageBuffer, { width: 800, height: 800 });
|
|
292
|
+
await storage.upload({
|
|
293
|
+
path: 'images/medium/photo.jpg',
|
|
294
|
+
data: medium,
|
|
295
|
+
contentType: 'image/jpeg',
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### File Attachments for Records
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Attach file to a record
|
|
303
|
+
await storage.upload({
|
|
304
|
+
path: `attachments/opportunity/${opportunityId}/proposal.pdf`,
|
|
305
|
+
data: fileBuffer,
|
|
306
|
+
contentType: 'application/pdf',
|
|
307
|
+
metadata: {
|
|
308
|
+
objectType: 'opportunity',
|
|
309
|
+
recordId: opportunityId,
|
|
310
|
+
uploadedBy: 'user:123',
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// List attachments for a record
|
|
315
|
+
const attachments = await storage.list(`attachments/opportunity/${opportunityId}`);
|
|
316
|
+
|
|
317
|
+
// Delete all attachments when record is deleted
|
|
318
|
+
await storage.deleteDirectory(`attachments/opportunity/${opportunityId}`);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## REST API Endpoints
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
POST /api/v1/storage/upload # Upload file
|
|
325
|
+
GET /api/v1/storage/download/:path # Download file
|
|
326
|
+
DELETE /api/v1/storage/:path # Delete file
|
|
327
|
+
GET /api/v1/storage/list # List files
|
|
328
|
+
POST /api/v1/storage/signed-url # Generate signed URL
|
|
329
|
+
POST /api/v1/storage/upload-url # Generate upload URL
|
|
330
|
+
GET /api/v1/storage/metadata/:path # Get file metadata
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Client Integration
|
|
334
|
+
|
|
335
|
+
### React Component Example
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { useStorage } from '@objectstack/client-react';
|
|
339
|
+
|
|
340
|
+
function FileUploader() {
|
|
341
|
+
const { upload, uploading, progress } = useStorage();
|
|
342
|
+
|
|
343
|
+
const handleUpload = async (file: File) => {
|
|
344
|
+
const path = await upload({
|
|
345
|
+
file,
|
|
346
|
+
folder: 'documents',
|
|
347
|
+
onProgress: (percent) => console.log(`Upload: ${percent}%`),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
console.log('Uploaded to:', path);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<div>
|
|
355
|
+
<input type="file" onChange={(e) => handleUpload(e.target.files[0])} />
|
|
356
|
+
{uploading && <progress value={progress} max="100" />}
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Common Patterns
|
|
363
|
+
|
|
364
|
+
### User Avatar Upload
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
async function uploadAvatar(userId: string, imageFile: Buffer) {
|
|
368
|
+
// Upload original
|
|
369
|
+
const path = `avatars/${userId}/original.jpg`;
|
|
370
|
+
await storage.upload({
|
|
371
|
+
path,
|
|
372
|
+
data: imageFile,
|
|
373
|
+
contentType: 'image/jpeg',
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Generate thumbnail
|
|
377
|
+
const thumbnail = await resizeImage(imageFile, { width: 128, height: 128 });
|
|
378
|
+
await storage.upload({
|
|
379
|
+
path: `avatars/${userId}/thumbnail.jpg`,
|
|
380
|
+
data: thumbnail,
|
|
381
|
+
contentType: 'image/jpeg',
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
original: storage.getUrl(path),
|
|
386
|
+
thumbnail: storage.getUrl(`avatars/${userId}/thumbnail.jpg`),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Document Management
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
async function uploadDocument(doc: {
|
|
395
|
+
recordId: string;
|
|
396
|
+
file: Buffer;
|
|
397
|
+
fileName: string;
|
|
398
|
+
uploadedBy: string;
|
|
399
|
+
}) {
|
|
400
|
+
const path = `documents/${doc.recordId}/${Date.now()}-${doc.fileName}`;
|
|
401
|
+
|
|
402
|
+
await storage.upload({
|
|
403
|
+
path,
|
|
404
|
+
data: doc.file,
|
|
405
|
+
contentType: getMimeType(doc.fileName),
|
|
406
|
+
metadata: {
|
|
407
|
+
recordId: doc.recordId,
|
|
408
|
+
uploadedBy: doc.uploadedBy,
|
|
409
|
+
fileName: doc.fileName,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Create signed URL for secure download
|
|
414
|
+
const downloadUrl = await storage.getSignedUrl(path, { expiresIn: 86400 }); // 24 hours
|
|
415
|
+
|
|
416
|
+
return { path, downloadUrl };
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Best Practices
|
|
421
|
+
|
|
422
|
+
1. **Path Organization**: Use hierarchical paths (e.g., `object/recordId/filename`)
|
|
423
|
+
2. **Content Types**: Always specify correct `contentType`
|
|
424
|
+
3. **Security**: Use signed URLs for private files
|
|
425
|
+
4. **Cleanup**: Delete files when records are deleted
|
|
426
|
+
5. **Validation**: Validate file types and sizes before upload
|
|
427
|
+
6. **Metadata**: Store useful metadata with files
|
|
428
|
+
7. **Backups**: Implement backup strategy for S3 buckets
|
|
429
|
+
|
|
430
|
+
## Performance Considerations
|
|
431
|
+
|
|
432
|
+
- **Streaming**: Use streams for large files to reduce memory usage
|
|
433
|
+
- **CDN**: Put CloudFront or similar CDN in front of S3
|
|
434
|
+
- **Compression**: Compress files before upload when appropriate
|
|
435
|
+
- **Caching**: Cache file URLs and metadata
|
|
436
|
+
- **Multipart**: Use multipart upload for files > 5MB
|
|
437
|
+
|
|
438
|
+
## Contract Implementation
|
|
439
|
+
|
|
440
|
+
Implements `IStorageService` from `@objectstack/spec/contracts`:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
interface IStorageService {
|
|
444
|
+
upload(options: UploadOptions): Promise<void>;
|
|
445
|
+
uploadStream(options: UploadStreamOptions): Promise<void>;
|
|
446
|
+
download(path: string): Promise<FileData>;
|
|
447
|
+
downloadStream(path: string): Promise<ReadableStream>;
|
|
448
|
+
delete(path: string): Promise<void>;
|
|
449
|
+
exists(path: string): Promise<boolean>;
|
|
450
|
+
getMetadata(path: string): Promise<FileMetadata>;
|
|
451
|
+
list(path: string, options?: ListOptions): Promise<string[]>;
|
|
452
|
+
getUrl(path: string): string;
|
|
453
|
+
getSignedUrl(path: string, options?: SignedUrlOptions): Promise<string>;
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## License
|
|
458
|
+
|
|
459
|
+
Apache-2.0
|
|
460
|
+
|
|
461
|
+
## See Also
|
|
462
|
+
|
|
463
|
+
- [AWS S3 Documentation](https://docs.aws.amazon.com/s3/)
|
|
464
|
+
- [Cloudflare R2 Documentation](https://developers.cloudflare.com/r2/)
|
|
465
|
+
- [@objectstack/spec/contracts](../../spec/src/contracts/)
|
|
466
|
+
- [File Upload Guide](/content/docs/guides/storage/)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/service-storage",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Storage Service for ObjectStack — implements IStorageService with local filesystem and S3 adapter skeleton",
|
|
6
6
|
"type": "module",
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@objectstack/core": "4.0.
|
|
18
|
-
"@objectstack/spec": "4.0.
|
|
17
|
+
"@objectstack/core": "4.0.4",
|
|
18
|
+
"@objectstack/spec": "4.0.4"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@types/node": "^25.
|
|
21
|
+
"@types/node": "^25.6.0",
|
|
22
22
|
"typescript": "^6.0.2",
|
|
23
|
-
"vitest": "^4.1.
|
|
23
|
+
"vitest": "^4.1.4"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup --config ../../../tsup.config.ts",
|