@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.
Files changed (69) hide show
  1. package/README.md +854 -0
  2. package/dist/client-next.d.mts +61 -0
  3. package/dist/client-next.d.mts.map +1 -0
  4. package/dist/client-next.mjs +111 -0
  5. package/dist/client-next.mjs.map +1 -0
  6. package/dist/client-utils-Dx6W25iz.d.mts +43 -0
  7. package/dist/client-utils-Dx6W25iz.d.mts.map +1 -0
  8. package/dist/client.d.mts +28 -0
  9. package/dist/client.d.mts.map +1 -0
  10. package/dist/client.mjs +183 -0
  11. package/dist/client.mjs.map +1 -0
  12. package/dist/env-BVHLmQdh.mjs +128 -0
  13. package/dist/env-BVHLmQdh.mjs.map +1 -0
  14. package/dist/env.mjs +3 -0
  15. package/dist/health-check-D7LnnDec.mjs +746 -0
  16. package/dist/health-check-D7LnnDec.mjs.map +1 -0
  17. package/dist/health-check-im_huJ59.d.mts +116 -0
  18. package/dist/health-check-im_huJ59.d.mts.map +1 -0
  19. package/dist/index.d.mts +60 -0
  20. package/dist/index.d.mts.map +1 -0
  21. package/dist/index.mjs +3 -0
  22. package/dist/keys.d.mts +37 -0
  23. package/dist/keys.d.mts.map +1 -0
  24. package/dist/keys.mjs +253 -0
  25. package/dist/keys.mjs.map +1 -0
  26. package/dist/server-edge.d.mts +28 -0
  27. package/dist/server-edge.d.mts.map +1 -0
  28. package/dist/server-edge.mjs +88 -0
  29. package/dist/server-edge.mjs.map +1 -0
  30. package/dist/server-next.d.mts +183 -0
  31. package/dist/server-next.d.mts.map +1 -0
  32. package/dist/server-next.mjs +1353 -0
  33. package/dist/server-next.mjs.map +1 -0
  34. package/dist/server.d.mts +70 -0
  35. package/dist/server.d.mts.map +1 -0
  36. package/dist/server.mjs +384 -0
  37. package/dist/server.mjs.map +1 -0
  38. package/dist/types.d.mts +321 -0
  39. package/dist/types.d.mts.map +1 -0
  40. package/dist/types.mjs +3 -0
  41. package/dist/validation.d.mts +101 -0
  42. package/dist/validation.d.mts.map +1 -0
  43. package/dist/validation.mjs +590 -0
  44. package/dist/validation.mjs.map +1 -0
  45. package/dist/vercel-blob-07Sx0Akn.d.mts +31 -0
  46. package/dist/vercel-blob-07Sx0Akn.d.mts.map +1 -0
  47. package/dist/vercel-blob-DA8HaYuw.mjs +158 -0
  48. package/dist/vercel-blob-DA8HaYuw.mjs.map +1 -0
  49. package/package.json +111 -0
  50. package/src/actions/blob-upload.ts +171 -0
  51. package/src/actions/index.ts +23 -0
  52. package/src/actions/mediaActions.ts +1071 -0
  53. package/src/actions/productMediaActions.ts +538 -0
  54. package/src/auth-helpers.ts +386 -0
  55. package/src/capabilities.ts +225 -0
  56. package/src/client-next.ts +184 -0
  57. package/src/client-utils.ts +292 -0
  58. package/src/client.ts +102 -0
  59. package/src/constants.ts +88 -0
  60. package/src/health-check.ts +81 -0
  61. package/src/multi-storage.ts +230 -0
  62. package/src/multipart.ts +497 -0
  63. package/src/retry-utils.test.ts +118 -0
  64. package/src/retry-utils.ts +59 -0
  65. package/src/server-edge.ts +129 -0
  66. package/src/server-next.ts +14 -0
  67. package/src/server.ts +666 -0
  68. package/src/validation.test.ts +312 -0
  69. 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
+ }