@od-oneapp/storage 2026.1.1301
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 +854 -0
- package/dist/client-next.d.mts +61 -0
- package/dist/client-next.d.mts.map +1 -0
- package/dist/client-next.mjs +111 -0
- package/dist/client-next.mjs.map +1 -0
- package/dist/client-utils-Dx6W25iz.d.mts +43 -0
- package/dist/client-utils-Dx6W25iz.d.mts.map +1 -0
- package/dist/client.d.mts +28 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +183 -0
- package/dist/client.mjs.map +1 -0
- package/dist/env-BVHLmQdh.mjs +128 -0
- package/dist/env-BVHLmQdh.mjs.map +1 -0
- package/dist/env.mjs +3 -0
- package/dist/health-check-D7LnnDec.mjs +746 -0
- package/dist/health-check-D7LnnDec.mjs.map +1 -0
- package/dist/health-check-im_huJ59.d.mts +116 -0
- package/dist/health-check-im_huJ59.d.mts.map +1 -0
- package/dist/index.d.mts +60 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/keys.d.mts +37 -0
- package/dist/keys.d.mts.map +1 -0
- package/dist/keys.mjs +253 -0
- package/dist/keys.mjs.map +1 -0
- package/dist/server-edge.d.mts +28 -0
- package/dist/server-edge.d.mts.map +1 -0
- package/dist/server-edge.mjs +88 -0
- package/dist/server-edge.mjs.map +1 -0
- package/dist/server-next.d.mts +183 -0
- package/dist/server-next.d.mts.map +1 -0
- package/dist/server-next.mjs +1353 -0
- package/dist/server-next.mjs.map +1 -0
- package/dist/server.d.mts +70 -0
- package/dist/server.d.mts.map +1 -0
- package/dist/server.mjs +384 -0
- package/dist/server.mjs.map +1 -0
- package/dist/types.d.mts +321 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +3 -0
- package/dist/validation.d.mts +101 -0
- package/dist/validation.d.mts.map +1 -0
- package/dist/validation.mjs +590 -0
- package/dist/validation.mjs.map +1 -0
- package/dist/vercel-blob-07Sx0Akn.d.mts +31 -0
- package/dist/vercel-blob-07Sx0Akn.d.mts.map +1 -0
- package/dist/vercel-blob-DA8HaYuw.mjs +158 -0
- package/dist/vercel-blob-DA8HaYuw.mjs.map +1 -0
- package/package.json +111 -0
- package/src/actions/blob-upload.ts +171 -0
- package/src/actions/index.ts +23 -0
- package/src/actions/mediaActions.ts +1071 -0
- package/src/actions/productMediaActions.ts +538 -0
- package/src/auth-helpers.ts +386 -0
- package/src/capabilities.ts +225 -0
- package/src/client-next.ts +184 -0
- package/src/client-utils.ts +292 -0
- package/src/client.ts +102 -0
- package/src/constants.ts +88 -0
- package/src/health-check.ts +81 -0
- package/src/multi-storage.ts +230 -0
- package/src/multipart.ts +497 -0
- package/src/retry-utils.test.ts +118 -0
- package/src/retry-utils.ts +59 -0
- package/src/server-edge.ts +129 -0
- package/src/server-next.ts +14 -0
- package/src/server.ts +666 -0
- package/src/validation.test.ts +312 -0
- package/src/validation.ts +827 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Health check utilities for storage providers
|
|
3
|
+
*
|
|
4
|
+
* Provides health check functionality to verify storage provider availability
|
|
5
|
+
* and performance. Useful for monitoring and alerting.
|
|
6
|
+
*
|
|
7
|
+
* @module @repo/storage/health-check
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { StorageProvider } from '../types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Health check result with status and metrics
|
|
14
|
+
*/
|
|
15
|
+
export interface HealthCheckResult {
|
|
16
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
17
|
+
latencyMs: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
details?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check the health of a storage provider
|
|
24
|
+
*
|
|
25
|
+
* @param provider - The storage provider to check
|
|
26
|
+
* @returns Health check result with status and latency
|
|
27
|
+
*/
|
|
28
|
+
export async function checkProviderHealth(provider: StorageProvider): Promise<HealthCheckResult> {
|
|
29
|
+
const startTime = Date.now();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Perform a lightweight operation to check provider health
|
|
33
|
+
// Using list with limit 1 as a health check probe
|
|
34
|
+
await provider.list({ limit: 1 });
|
|
35
|
+
|
|
36
|
+
const latencyMs = Date.now() - startTime;
|
|
37
|
+
|
|
38
|
+
// Define health thresholds
|
|
39
|
+
const HEALTHY_THRESHOLD_MS = 1000; // < 1s is healthy
|
|
40
|
+
const DEGRADED_THRESHOLD_MS = 3000; // 1-3s is degraded
|
|
41
|
+
|
|
42
|
+
let status: 'healthy' | 'degraded' | 'unhealthy';
|
|
43
|
+
if (latencyMs < HEALTHY_THRESHOLD_MS) {
|
|
44
|
+
status = 'healthy';
|
|
45
|
+
} else if (latencyMs < DEGRADED_THRESHOLD_MS) {
|
|
46
|
+
status = 'degraded';
|
|
47
|
+
} else {
|
|
48
|
+
status = 'unhealthy';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
status,
|
|
53
|
+
latencyMs,
|
|
54
|
+
details: {
|
|
55
|
+
provider: provider.constructor.name,
|
|
56
|
+
threshold_healthy_ms: HEALTHY_THRESHOLD_MS,
|
|
57
|
+
threshold_degraded_ms: DEGRADED_THRESHOLD_MS,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const latencyMs = Date.now() - startTime;
|
|
62
|
+
return {
|
|
63
|
+
status: 'unhealthy',
|
|
64
|
+
latencyMs,
|
|
65
|
+
error: error instanceof Error ? error.message : String(error),
|
|
66
|
+
details: {
|
|
67
|
+
provider: provider.constructor.name,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Perform a comprehensive health check on storage system
|
|
75
|
+
*
|
|
76
|
+
* @param provider - The storage provider to check
|
|
77
|
+
* @returns Detailed health check result
|
|
78
|
+
*/
|
|
79
|
+
export async function storageHealthCheck(provider: StorageProvider): Promise<HealthCheckResult> {
|
|
80
|
+
return checkProviderHealth(provider);
|
|
81
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Multi-storage provider manager
|
|
3
|
+
*
|
|
4
|
+
* Manages multiple storage providers with routing and fallback capabilities.
|
|
5
|
+
* Allows using different providers for different use cases or as backups.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Provider routing based on key patterns
|
|
9
|
+
* - Fallback to default provider
|
|
10
|
+
* - Unified API across multiple providers
|
|
11
|
+
*
|
|
12
|
+
* @module @repo/storage/multi-storage
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { CloudflareImagesProvider } from '../providers/cloudflare-images';
|
|
16
|
+
import { CloudflareR2Provider } from '../providers/cloudflare-r2';
|
|
17
|
+
import { VercelBlobProvider } from '../providers/vercel-blob';
|
|
18
|
+
import {
|
|
19
|
+
type ListOptions,
|
|
20
|
+
type MultiStorageConfig,
|
|
21
|
+
type StorageConfig,
|
|
22
|
+
type StorageObject,
|
|
23
|
+
type StorageProvider,
|
|
24
|
+
type UploadOptions,
|
|
25
|
+
} from '../types';
|
|
26
|
+
|
|
27
|
+
export class MultiStorageManager {
|
|
28
|
+
private providers: Map<string, StorageProvider> = new Map();
|
|
29
|
+
private defaultProvider: string;
|
|
30
|
+
private routing: MultiStorageConfig['routing'];
|
|
31
|
+
|
|
32
|
+
constructor(config: MultiStorageConfig) {
|
|
33
|
+
// Initialize all providers
|
|
34
|
+
for (const [name, providerConfig] of Object.entries(config.providers)) {
|
|
35
|
+
this.providers.set(name, this.createProvider(providerConfig));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Set default provider
|
|
39
|
+
const firstProvider = Object.keys(config.providers)[0];
|
|
40
|
+
this.defaultProvider = config.defaultProvider ?? firstProvider ?? '';
|
|
41
|
+
if (!this.defaultProvider) {
|
|
42
|
+
throw new Error('No storage providers configured');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.routing = config.routing;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private createProvider(config: StorageConfig): StorageProvider {
|
|
49
|
+
switch (config.provider) {
|
|
50
|
+
case 'multi':
|
|
51
|
+
throw new Error('Multi provider cannot be nested');
|
|
52
|
+
|
|
53
|
+
case 'cloudflare-r2':
|
|
54
|
+
if (!config.cloudflareR2) {
|
|
55
|
+
throw new Error('Cloudflare R2 configuration is required');
|
|
56
|
+
}
|
|
57
|
+
// Handle array of R2 configs
|
|
58
|
+
if (Array.isArray(config.cloudflareR2)) {
|
|
59
|
+
if (config.cloudflareR2.length === 0) {
|
|
60
|
+
throw new Error('No R2 configurations provided');
|
|
61
|
+
}
|
|
62
|
+
const firstR2Config = config.cloudflareR2[0];
|
|
63
|
+
if (!firstR2Config) {
|
|
64
|
+
throw new Error('First R2 configuration is undefined');
|
|
65
|
+
}
|
|
66
|
+
// Use first one for single provider (backward compatibility)
|
|
67
|
+
return new CloudflareR2Provider(firstR2Config);
|
|
68
|
+
}
|
|
69
|
+
return new CloudflareR2Provider(config.cloudflareR2);
|
|
70
|
+
|
|
71
|
+
case 'cloudflare-images':
|
|
72
|
+
if (!config.cloudflareImages) {
|
|
73
|
+
throw new Error('Cloudflare Images configuration is required');
|
|
74
|
+
}
|
|
75
|
+
return new CloudflareImagesProvider(config.cloudflareImages);
|
|
76
|
+
|
|
77
|
+
case 'vercel-blob':
|
|
78
|
+
if (!config.vercelBlob?.token) {
|
|
79
|
+
throw new Error('Vercel Blob token is required');
|
|
80
|
+
}
|
|
81
|
+
return new VercelBlobProvider(config.vercelBlob.token);
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Unknown storage provider: ${config.provider}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private getProviderForKey(key: string): { provider: StorageProvider; providerName: string } {
|
|
89
|
+
// Check routing rules
|
|
90
|
+
if (this.routing) {
|
|
91
|
+
// Check file type routing
|
|
92
|
+
const extension = key.split('.').pop()?.toLowerCase();
|
|
93
|
+
|
|
94
|
+
// Image routing
|
|
95
|
+
if (this.routing.images && this.isImageFile(extension)) {
|
|
96
|
+
const provider = this.providers.get(this.routing.images);
|
|
97
|
+
if (provider) {
|
|
98
|
+
return { provider, providerName: this.routing.images };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Document routing
|
|
103
|
+
if (this.routing.documents && this.isDocumentFile(extension)) {
|
|
104
|
+
const provider = this.providers.get(this.routing.documents);
|
|
105
|
+
if (provider) {
|
|
106
|
+
return { provider, providerName: this.routing.documents };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Custom routing rules
|
|
111
|
+
for (const [pattern, providerName] of Object.entries(this.routing)) {
|
|
112
|
+
if (pattern !== 'images' && pattern !== 'documents' && providerName) {
|
|
113
|
+
// Simple pattern matching (could be enhanced with regex)
|
|
114
|
+
if (key.includes(pattern)) {
|
|
115
|
+
const provider = this.providers.get(providerName);
|
|
116
|
+
if (provider) {
|
|
117
|
+
return { provider, providerName };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Fall back to default provider
|
|
125
|
+
const provider = this.providers.get(this.defaultProvider);
|
|
126
|
+
if (!provider) {
|
|
127
|
+
throw new Error(`Default provider '${this.defaultProvider}' not found`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { provider, providerName: this.defaultProvider };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private isImageFile(extension?: string): boolean {
|
|
134
|
+
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg', 'ico'];
|
|
135
|
+
return extension ? imageExtensions.includes(extension) : false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private isDocumentFile(extension?: string): boolean {
|
|
139
|
+
const documentExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv'];
|
|
140
|
+
return extension ? documentExtensions.includes(extension) : false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Get a specific provider by name
|
|
144
|
+
getProvider(name: string): StorageProvider | undefined {
|
|
145
|
+
return this.providers.get(name);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Storage operations that route to appropriate provider
|
|
149
|
+
async delete(key: string): Promise<void> {
|
|
150
|
+
const { provider } = this.getProviderForKey(key);
|
|
151
|
+
return provider.delete(key);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async download(key: string): Promise<Blob> {
|
|
155
|
+
const { provider } = this.getProviderForKey(key);
|
|
156
|
+
return provider.download(key);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async exists(key: string): Promise<boolean> {
|
|
160
|
+
const { provider } = this.getProviderForKey(key);
|
|
161
|
+
return provider.exists(key);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async getMetadata(key: string): Promise<StorageObject> {
|
|
165
|
+
const { provider } = this.getProviderForKey(key);
|
|
166
|
+
return provider.getMetadata(key);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async getUrl(key: string, options?: { expiresIn?: number }): Promise<string> {
|
|
170
|
+
const { provider } = this.getProviderForKey(key);
|
|
171
|
+
return provider.getUrl(key, options);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async list(options?: ListOptions & { provider?: string }): Promise<StorageObject[]> {
|
|
175
|
+
// If specific provider requested, use it
|
|
176
|
+
if (options?.provider) {
|
|
177
|
+
const provider = this.providers.get(options.provider);
|
|
178
|
+
if (!provider) {
|
|
179
|
+
throw new Error(`Provider '${options.provider}' not found`);
|
|
180
|
+
}
|
|
181
|
+
return provider.list(options);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Otherwise, list from all providers
|
|
185
|
+
const allResults: StorageObject[] = [];
|
|
186
|
+
for (const provider of this.providers.values()) {
|
|
187
|
+
const results = await provider.list(options);
|
|
188
|
+
allResults.push(...results);
|
|
189
|
+
}
|
|
190
|
+
return allResults;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async upload(
|
|
194
|
+
key: string,
|
|
195
|
+
data: ArrayBuffer | Blob | Buffer | File | ReadableStream,
|
|
196
|
+
options?: UploadOptions & { provider?: string },
|
|
197
|
+
): Promise<StorageObject> {
|
|
198
|
+
let provider: StorageProvider;
|
|
199
|
+
|
|
200
|
+
// If specific provider requested, use it
|
|
201
|
+
if (options?.provider) {
|
|
202
|
+
const requestedProvider = this.providers.get(options.provider);
|
|
203
|
+
if (!requestedProvider) {
|
|
204
|
+
throw new Error(`Provider '${options.provider}' not found`);
|
|
205
|
+
}
|
|
206
|
+
provider = requestedProvider;
|
|
207
|
+
} else {
|
|
208
|
+
// Otherwise, use routing logic
|
|
209
|
+
const { provider: routedProvider } = this.getProviderForKey(key);
|
|
210
|
+
provider = routedProvider;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return provider.upload(key, data, options);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Extended methods for Cloudflare Images
|
|
217
|
+
async getCloudflareImagesProvider(): Promise<InstanceType<typeof CloudflareImagesProvider> | undefined> {
|
|
218
|
+
for (const provider of this.providers.values()) {
|
|
219
|
+
if (provider instanceof CloudflareImagesProvider) {
|
|
220
|
+
return provider;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Get all provider names
|
|
227
|
+
getProviderNames(): string[] {
|
|
228
|
+
return Array.from(this.providers.keys());
|
|
229
|
+
}
|
|
230
|
+
}
|