@trokky/trokky 3.1.0 → 3.3.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/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.d.ts +140 -0
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.d.ts.map +1 -0
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.js +853 -0
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.js.map +1 -0
- package/dist/adapters/cloudflare-d1/index.d.ts +5 -0
- package/dist/adapters/cloudflare-d1/index.d.ts.map +1 -0
- package/dist/adapters/cloudflare-d1/index.js +12 -0
- package/dist/adapters/cloudflare-d1/index.js.map +1 -0
- package/dist/adapters/cloudflare-d1/rate-limit-store.d.ts +46 -0
- package/dist/adapters/cloudflare-d1/rate-limit-store.d.ts.map +1 -0
- package/dist/adapters/cloudflare-d1/rate-limit-store.js +96 -0
- package/dist/adapters/cloudflare-d1/rate-limit-store.js.map +1 -0
- package/dist/adapters/cloudflare-d1/types.d.ts +69 -0
- package/dist/adapters/cloudflare-d1/types.d.ts.map +1 -0
- package/dist/adapters/cloudflare-d1/types.js +2 -0
- package/dist/adapters/cloudflare-d1/types.js.map +1 -0
- package/dist/adapters/cloudflare-r2/cloudflare-r2-adapter.d.ts +86 -0
- package/dist/adapters/cloudflare-r2/cloudflare-r2-adapter.d.ts.map +1 -0
- package/dist/adapters/cloudflare-r2/cloudflare-r2-adapter.js +493 -0
- package/dist/adapters/cloudflare-r2/cloudflare-r2-adapter.js.map +1 -0
- package/dist/adapters/cloudflare-r2/index.d.ts +3 -0
- package/dist/adapters/cloudflare-r2/index.d.ts.map +1 -0
- package/dist/adapters/cloudflare-r2/index.js +11 -0
- package/dist/adapters/cloudflare-r2/index.js.map +1 -0
- package/dist/adapters/cloudflare-r2/types.d.ts +43 -0
- package/dist/adapters/cloudflare-r2/types.d.ts.map +1 -0
- package/dist/adapters/cloudflare-r2/types.js +2 -0
- package/dist/adapters/cloudflare-r2/types.js.map +1 -0
- package/dist/adapters/filesystem-data/filesystem-data-adapter.d.ts +9 -0
- package/dist/adapters/filesystem-data/filesystem-data-adapter.d.ts.map +1 -1
- package/dist/adapters/filesystem-data/filesystem-data-adapter.js +28 -0
- package/dist/adapters/filesystem-data/filesystem-data-adapter.js.map +1 -1
- package/dist/adapters/postgres-data/postgres-data-adapter.d.ts +9 -0
- package/dist/adapters/postgres-data/postgres-data-adapter.d.ts.map +1 -1
- package/dist/adapters/postgres-data/postgres-data-adapter.js +59 -0
- package/dist/adapters/postgres-data/postgres-data-adapter.js.map +1 -1
- package/dist/core/core/engine.d.ts +10 -0
- package/dist/core/core/engine.d.ts.map +1 -1
- package/dist/core/core/engine.js +16 -2
- package/dist/core/core/engine.js.map +1 -1
- package/dist/core/media/image-processor.d.ts.map +1 -1
- package/dist/core/media/image-processor.js +5 -13
- package/dist/core/media/image-processor.js.map +1 -1
- package/dist/core/media/processors/sharp.d.ts.map +1 -1
- package/dist/core/media/processors/sharp.js +13 -2
- package/dist/core/media/processors/sharp.js.map +1 -1
- package/dist/core/media/processors/types.d.ts +1 -1
- package/dist/core/media/processors/types.d.ts.map +1 -1
- package/dist/core/security/rate-limit-store.d.ts +56 -0
- package/dist/core/security/rate-limit-store.d.ts.map +1 -0
- package/dist/core/security/rate-limit-store.js +55 -0
- package/dist/core/security/rate-limit-store.js.map +1 -0
- package/dist/core/security/rate-limiter.d.ts +39 -2
- package/dist/core/security/rate-limiter.d.ts.map +1 -1
- package/dist/core/security/rate-limiter.js +52 -20
- package/dist/core/security/rate-limiter.js.map +1 -1
- package/dist/core/services/media-service.d.ts +34 -0
- package/dist/core/services/media-service.d.ts.map +1 -1
- package/dist/core/services/media-service.js +172 -120
- package/dist/core/services/media-service.js.map +1 -1
- package/dist/core/services/user-service.d.ts +42 -0
- package/dist/core/services/user-service.d.ts.map +1 -1
- package/dist/core/services/user-service.js +114 -1
- package/dist/core/services/user-service.js.map +1 -1
- package/dist/core/types/storage-adapters.d.ts +18 -0
- package/dist/core/types/storage-adapters.d.ts.map +1 -1
- package/dist/core/utils/retry.d.ts +45 -0
- package/dist/core/utils/retry.d.ts.map +1 -0
- package/dist/core/utils/retry.js +47 -0
- package/dist/core/utils/retry.js.map +1 -0
- package/dist/i18n/config.d.ts +32 -0
- package/dist/i18n/config.d.ts.map +1 -1
- package/dist/i18n/locales/en/auth.json +16 -0
- package/dist/i18n/locales/fr/auth.json +16 -0
- package/dist/integrations/workers/adapter.d.ts +28 -0
- package/dist/integrations/workers/adapter.d.ts.map +1 -0
- package/dist/integrations/workers/adapter.js +111 -0
- package/dist/integrations/workers/adapter.js.map +1 -0
- package/dist/integrations/workers/index.d.ts +22 -0
- package/dist/integrations/workers/index.d.ts.map +1 -0
- package/dist/integrations/workers/index.js +60 -0
- package/dist/integrations/workers/index.js.map +1 -0
- package/dist/routes/auth/auth-flow-store.d.ts +17 -0
- package/dist/routes/auth/auth-flow-store.d.ts.map +1 -1
- package/dist/routes/auth/auth-flow-store.js +27 -0
- package/dist/routes/auth/auth-flow-store.js.map +1 -1
- package/dist/routes/auth/oauth.d.ts.map +1 -1
- package/dist/routes/auth/oauth.js +5 -12
- package/dist/routes/auth/oauth.js.map +1 -1
- package/dist/routes/auth/passkey.d.ts.map +1 -1
- package/dist/routes/auth/passkey.js +5 -8
- package/dist/routes/auth/passkey.js.map +1 -1
- package/dist/routes/handlers/auth.d.ts +14 -0
- package/dist/routes/handlers/auth.d.ts.map +1 -1
- package/dist/routes/handlers/auth.js +55 -0
- package/dist/routes/handlers/auth.js.map +1 -1
- package/dist/structure/validation/schemas.d.ts +60 -60
- package/package.json +20 -1
- package/dist/core/media/processors/cloudflare-images.d.ts +0 -83
- package/dist/core/media/processors/cloudflare-images.d.ts.map +0 -1
- package/dist/core/media/processors/cloudflare-images.js +0 -241
- package/dist/core/media/processors/cloudflare-images.js.map +0 -1
- package/dist/core/media/processors/cloudflare-transform-store.d.ts +0 -127
- package/dist/core/media/processors/cloudflare-transform-store.d.ts.map +0 -1
- package/dist/core/media/processors/cloudflare-transform-store.js +0 -237
- package/dist/core/media/processors/cloudflare-transform-store.js.map +0 -1
- package/dist/core/media/processors/cloudflare-transformations.d.ts +0 -81
- package/dist/core/media/processors/cloudflare-transformations.d.ts.map +0 -1
- package/dist/core/media/processors/cloudflare-transformations.js +0 -144
- package/dist/core/media/processors/cloudflare-transformations.js.map +0 -1
- package/dist/core/media/processors/workers-images.d.ts +0 -112
- package/dist/core/media/processors/workers-images.d.ts.map +0 -1
- package/dist/core/media/processors/workers-images.js +0 -257
- package/dist/core/media/processors/workers-images.js.map +0 -1
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloudflare Images Processor
|
|
3
|
-
*
|
|
4
|
-
* Integrates with Cloudflare Images for:
|
|
5
|
-
* - Automatic image optimization
|
|
6
|
-
* - Global CDN delivery
|
|
7
|
-
* - Real-time transformations
|
|
8
|
-
* - WebP/AVIF conversion
|
|
9
|
-
*
|
|
10
|
-
* Supports two modes:
|
|
11
|
-
* 1. Images API Binding (Workers) - Direct image manipulation in Workers
|
|
12
|
-
* 2. Upload API - Traditional HTTP API for external usage
|
|
13
|
-
*/
|
|
14
|
-
import { ImageProcessor } from './types.js';
|
|
15
|
-
export class CloudflareImagesProcessor extends ImageProcessor {
|
|
16
|
-
accountId;
|
|
17
|
-
apiToken;
|
|
18
|
-
deliveryUrl;
|
|
19
|
-
defaultVariant;
|
|
20
|
-
bindingEnabled;
|
|
21
|
-
bindingName;
|
|
22
|
-
env;
|
|
23
|
-
constructor(config) {
|
|
24
|
-
super(config);
|
|
25
|
-
const { accountId, apiToken, deliveryUrl, defaultVariant, binding } = config.options;
|
|
26
|
-
this.accountId = accountId;
|
|
27
|
-
this.apiToken = apiToken;
|
|
28
|
-
this.deliveryUrl = deliveryUrl || `https://imagedelivery.net/${accountId}`;
|
|
29
|
-
this.defaultVariant = defaultVariant || 'public';
|
|
30
|
-
this.bindingEnabled = binding?.enabled || false;
|
|
31
|
-
this.bindingName = binding?.bindingName || 'IMAGES';
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Set the Workers environment for binding mode
|
|
35
|
-
*/
|
|
36
|
-
setEnv(env) {
|
|
37
|
-
this.env = env;
|
|
38
|
-
}
|
|
39
|
-
async processImage(file, metadata) {
|
|
40
|
-
// Try binding mode first if enabled
|
|
41
|
-
if (this.bindingEnabled && this.env?.IMAGES) {
|
|
42
|
-
try {
|
|
43
|
-
return await this.processWithBinding(file, metadata);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
// In development, binding might not work - fall back to Upload API
|
|
47
|
-
if (error.message?.includes('wrangler dev') || error.message?.includes('proxy to')) {
|
|
48
|
-
console.warn('Cloudflare Images binding failed in development, falling back to Upload API:', error.message);
|
|
49
|
-
if (!this.apiToken) {
|
|
50
|
-
throw new Error('Cloudflare Images API token is required as fallback when binding fails in development');
|
|
51
|
-
}
|
|
52
|
-
return this.processWithUploadAPI(file, metadata);
|
|
53
|
-
}
|
|
54
|
-
throw error; // Re-throw other errors
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// Use Upload API mode
|
|
58
|
-
if (!this.apiToken) {
|
|
59
|
-
throw new Error('Cloudflare Images API token is required when not using binding mode');
|
|
60
|
-
}
|
|
61
|
-
return this.processWithUploadAPI(file, metadata);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Process image using Workers binding (edge-native)
|
|
65
|
-
*/
|
|
66
|
-
async processWithBinding(file, metadata) {
|
|
67
|
-
if (!this.env?.IMAGES) {
|
|
68
|
-
throw new Error('Cloudflare Images binding not available in environment');
|
|
69
|
-
}
|
|
70
|
-
const buffer = await file.arrayBuffer();
|
|
71
|
-
const variants = {};
|
|
72
|
-
// Process original
|
|
73
|
-
const original = {
|
|
74
|
-
url: this.getImageUrl(metadata.id, 'original'),
|
|
75
|
-
width: 0, // Will be set by Cloudflare
|
|
76
|
-
height: 0,
|
|
77
|
-
format: file.type.split('/')[1] || 'jpeg',
|
|
78
|
-
size: file.size,
|
|
79
|
-
buffer: Buffer.from(buffer)
|
|
80
|
-
};
|
|
81
|
-
// Process each variant using the binding
|
|
82
|
-
for (const variant of this.config.variants || []) {
|
|
83
|
-
const transformedBuffer = await this.env.IMAGES
|
|
84
|
-
.input(buffer)
|
|
85
|
-
.transform(this.mapVariantToCloudflareOptions(variant))
|
|
86
|
-
.output();
|
|
87
|
-
variants[variant.name] = {
|
|
88
|
-
url: this.getImageUrl(metadata.id, variant.name),
|
|
89
|
-
width: variant.width || 0,
|
|
90
|
-
height: variant.height || 0,
|
|
91
|
-
format: variant.format || 'webp',
|
|
92
|
-
size: transformedBuffer.byteLength,
|
|
93
|
-
buffer: Buffer.from(transformedBuffer)
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
return { original, variants };
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Process image using Upload API (traditional HTTP)
|
|
100
|
-
*/
|
|
101
|
-
async processWithUploadAPI(file, metadata) {
|
|
102
|
-
const formData = new FormData();
|
|
103
|
-
formData.append('file', file);
|
|
104
|
-
formData.append('id', metadata.id);
|
|
105
|
-
// Add metadata
|
|
106
|
-
const customMetadata = {
|
|
107
|
-
filename: metadata.filename,
|
|
108
|
-
path: metadata.path,
|
|
109
|
-
uploadedAt: new Date().toISOString()
|
|
110
|
-
};
|
|
111
|
-
formData.append('metadata', JSON.stringify(customMetadata));
|
|
112
|
-
// Configure variants if specified
|
|
113
|
-
if (this.config.variants && this.config.variants.length > 0) {
|
|
114
|
-
const variantConfig = this.createVariantConfig(this.config.variants);
|
|
115
|
-
formData.append('requireSignedURLs', 'false');
|
|
116
|
-
formData.append('variants', JSON.stringify(variantConfig));
|
|
117
|
-
}
|
|
118
|
-
const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/images/v1`, {
|
|
119
|
-
method: 'POST',
|
|
120
|
-
headers: {
|
|
121
|
-
'Authorization': `Bearer ${this.apiToken}`
|
|
122
|
-
},
|
|
123
|
-
body: formData
|
|
124
|
-
});
|
|
125
|
-
if (!response.ok) {
|
|
126
|
-
const error = await response.text();
|
|
127
|
-
throw new Error(`Failed to upload image to Cloudflare: ${error}`);
|
|
128
|
-
}
|
|
129
|
-
const result = await response.json();
|
|
130
|
-
if (!result.success) {
|
|
131
|
-
throw new Error(`Cloudflare Images API error: ${result.errors.join(', ')}`);
|
|
132
|
-
}
|
|
133
|
-
// Build processed image response
|
|
134
|
-
const variants = {};
|
|
135
|
-
for (const variant of this.config.variants || []) {
|
|
136
|
-
variants[variant.name] = {
|
|
137
|
-
url: this.getImageUrl(result.result.id, variant.name),
|
|
138
|
-
width: variant.width || 0,
|
|
139
|
-
height: variant.height || 0,
|
|
140
|
-
format: variant.format || 'webp',
|
|
141
|
-
size: 0 // Size not available from Upload API
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
original: {
|
|
146
|
-
url: this.getImageUrl(result.result.id, 'original'),
|
|
147
|
-
width: 0, // Dimensions not available from Upload API
|
|
148
|
-
height: 0,
|
|
149
|
-
format: file.type.split('/')[1] || 'jpeg',
|
|
150
|
-
size: file.size
|
|
151
|
-
},
|
|
152
|
-
variants
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Map Trokky variant config to Cloudflare transform options
|
|
157
|
-
*/
|
|
158
|
-
mapVariantToCloudflareOptions(variant) {
|
|
159
|
-
const options = {};
|
|
160
|
-
if (variant.width)
|
|
161
|
-
options.width = variant.width;
|
|
162
|
-
if (variant.height)
|
|
163
|
-
options.height = variant.height;
|
|
164
|
-
if (variant.quality)
|
|
165
|
-
options.quality = variant.quality;
|
|
166
|
-
if (variant.format)
|
|
167
|
-
options.format = variant.format;
|
|
168
|
-
// Map fit modes
|
|
169
|
-
if (variant.fit) {
|
|
170
|
-
const fitMap = {
|
|
171
|
-
'cover': 'cover',
|
|
172
|
-
'contain': 'contain',
|
|
173
|
-
'fill': 'crop',
|
|
174
|
-
'inside': 'scale-down',
|
|
175
|
-
'outside': 'pad'
|
|
176
|
-
};
|
|
177
|
-
options.fit = fitMap[variant.fit] || 'scale-down';
|
|
178
|
-
}
|
|
179
|
-
return options;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Create variant configuration for Upload API
|
|
183
|
-
*/
|
|
184
|
-
createVariantConfig(variants) {
|
|
185
|
-
const config = {};
|
|
186
|
-
for (const variant of variants) {
|
|
187
|
-
config[variant.name] = {
|
|
188
|
-
width: variant.width,
|
|
189
|
-
height: variant.height,
|
|
190
|
-
fit: variant.fit || 'scale-down',
|
|
191
|
-
metadata: {
|
|
192
|
-
quality: variant.quality,
|
|
193
|
-
format: variant.format
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
return config;
|
|
198
|
-
}
|
|
199
|
-
getImageUrl(imageId, variantName) {
|
|
200
|
-
const variant = variantName || this.defaultVariant;
|
|
201
|
-
return `${this.deliveryUrl}/${imageId}/${variant}`;
|
|
202
|
-
}
|
|
203
|
-
async deleteImage(imageId) {
|
|
204
|
-
if (!this.apiToken) {
|
|
205
|
-
throw new Error('Cloudflare Images API token is required for delete operations');
|
|
206
|
-
}
|
|
207
|
-
const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/images/v1/${imageId}`, {
|
|
208
|
-
method: 'DELETE',
|
|
209
|
-
headers: {
|
|
210
|
-
'Authorization': `Bearer ${this.apiToken}`
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
if (!response.ok && response.status !== 404) {
|
|
214
|
-
const error = await response.text();
|
|
215
|
-
throw new Error(`Failed to delete image from Cloudflare: ${error}`);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
async healthCheck() {
|
|
219
|
-
// Check binding availability
|
|
220
|
-
if (this.bindingEnabled && this.env?.IMAGES) {
|
|
221
|
-
return true;
|
|
222
|
-
}
|
|
223
|
-
// Check Upload API connectivity
|
|
224
|
-
if (this.apiToken) {
|
|
225
|
-
try {
|
|
226
|
-
const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/images/v1/stats`, {
|
|
227
|
-
method: 'GET',
|
|
228
|
-
headers: {
|
|
229
|
-
'Authorization': `Bearer ${this.apiToken}`
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
return response.ok;
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
//# sourceMappingURL=cloudflare-images.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare-images.js","sourceRoot":"","sources":["../../../../src/core/media/processors/cloudflare-images.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloudflare Transform & Store Processor
|
|
3
|
-
*
|
|
4
|
-
* Uses Cloudflare's Image Transformations API to transform images and stores
|
|
5
|
-
* the transformed variants back in R2. This gives you full control over serving
|
|
6
|
-
* while leveraging Cloudflare's transformation capabilities.
|
|
7
|
-
*
|
|
8
|
-
* Unlike the cloudflare-transformations processor which only generates URLs,
|
|
9
|
-
* this processor actually creates and stores the transformed images.
|
|
10
|
-
*
|
|
11
|
-
* Process:
|
|
12
|
-
* 1. Upload original to R2
|
|
13
|
-
* 2. Request transformation via Cloudflare API
|
|
14
|
-
* 3. Store transformed variant in R2
|
|
15
|
-
* 4. Generate R2 URLs for all variants
|
|
16
|
-
*
|
|
17
|
-
* Benefits:
|
|
18
|
-
* - Full control over image serving
|
|
19
|
-
* - Works in development without domain proxy
|
|
20
|
-
* - Transformed images are physically stored
|
|
21
|
-
* - Can serve directly from R2 or your own domain
|
|
22
|
-
*/
|
|
23
|
-
import { ImageProcessor } from './types.js';
|
|
24
|
-
import type { ImageProcessorConfig, ProcessedImage } from './types.js';
|
|
25
|
-
export interface CloudflareTransformStoreConfig extends ImageProcessorConfig {
|
|
26
|
-
type: 'cloudflare-transform-store';
|
|
27
|
-
options: {
|
|
28
|
-
/**
|
|
29
|
-
* Cloudflare account ID
|
|
30
|
-
*/
|
|
31
|
-
accountId: string;
|
|
32
|
-
/**
|
|
33
|
-
* Cloudflare API token with Images Read permission
|
|
34
|
-
* Used to fetch transformed images from Cloudflare's CDN
|
|
35
|
-
*/
|
|
36
|
-
apiToken?: string;
|
|
37
|
-
/**
|
|
38
|
-
* Base URL for transformation requests
|
|
39
|
-
* This should be a domain proxied through Cloudflare with transformations enabled
|
|
40
|
-
* Example: 'https://trokky.dev' or 'https://transform.yourdomain.com'
|
|
41
|
-
*/
|
|
42
|
-
transformUrl: string;
|
|
43
|
-
/**
|
|
44
|
-
* R2 bucket binding or configuration
|
|
45
|
-
* Used to store transformed images back to R2
|
|
46
|
-
*/
|
|
47
|
-
r2Bucket?: any;
|
|
48
|
-
/**
|
|
49
|
-
* Media path prefix for stored variants
|
|
50
|
-
* Example: '/media' will store variants at /media/[id]-[variant]
|
|
51
|
-
*/
|
|
52
|
-
mediaPath?: string;
|
|
53
|
-
/**
|
|
54
|
-
* Whether to store variants with a naming pattern
|
|
55
|
-
* true: /media/[id]-thumbnail, /media/[id]-medium
|
|
56
|
-
* false: /media/[id]/thumbnail, /media/[id]/medium
|
|
57
|
-
*/
|
|
58
|
-
flatStructure?: boolean;
|
|
59
|
-
/**
|
|
60
|
-
* Default format for transformations
|
|
61
|
-
*/
|
|
62
|
-
defaultFormat?: 'webp' | 'avif' | 'jpeg' | 'png';
|
|
63
|
-
/**
|
|
64
|
-
* Default quality (1-100)
|
|
65
|
-
*/
|
|
66
|
-
defaultQuality?: number;
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Cloudflare Transform & Store processor
|
|
71
|
-
*
|
|
72
|
-
* This processor transforms images using Cloudflare and stores them in R2
|
|
73
|
-
*/
|
|
74
|
-
export declare class CloudflareTransformStoreProcessor extends ImageProcessor {
|
|
75
|
-
private accountId;
|
|
76
|
-
private apiToken?;
|
|
77
|
-
private transformUrl;
|
|
78
|
-
private r2Bucket?;
|
|
79
|
-
private mediaPath;
|
|
80
|
-
private flatStructure;
|
|
81
|
-
private defaultFormat;
|
|
82
|
-
private defaultQuality;
|
|
83
|
-
private env?;
|
|
84
|
-
constructor(config: CloudflareTransformStoreConfig);
|
|
85
|
-
/**
|
|
86
|
-
* Set the R2 bucket from Worker environment
|
|
87
|
-
*/
|
|
88
|
-
setEnv(env: any): void;
|
|
89
|
-
processImage(file: File, metadata: {
|
|
90
|
-
id: string;
|
|
91
|
-
filename: string;
|
|
92
|
-
path: string;
|
|
93
|
-
}): Promise<ProcessedImage>;
|
|
94
|
-
/**
|
|
95
|
-
* Transform image using Cloudflare and fetch the result
|
|
96
|
-
*/
|
|
97
|
-
private transformAndFetch;
|
|
98
|
-
/**
|
|
99
|
-
* Build URL parameters for Cloudflare transformation
|
|
100
|
-
*/
|
|
101
|
-
private buildTransformParams;
|
|
102
|
-
/**
|
|
103
|
-
* Get the storage key for a variant
|
|
104
|
-
*/
|
|
105
|
-
private getVariantKey;
|
|
106
|
-
/**
|
|
107
|
-
* Store a variant in R2
|
|
108
|
-
*/
|
|
109
|
-
private storeVariant;
|
|
110
|
-
/**
|
|
111
|
-
* Get content type from file extension
|
|
112
|
-
*/
|
|
113
|
-
private getContentType;
|
|
114
|
-
/**
|
|
115
|
-
* Get the URL for a specific variant
|
|
116
|
-
*/
|
|
117
|
-
getImageUrl(imageId: string, variantName?: string): string;
|
|
118
|
-
/**
|
|
119
|
-
* Delete image and all variants
|
|
120
|
-
*/
|
|
121
|
-
deleteImage(imageId: string): Promise<void>;
|
|
122
|
-
/**
|
|
123
|
-
* Health check
|
|
124
|
-
*/
|
|
125
|
-
healthCheck(): Promise<boolean>;
|
|
126
|
-
}
|
|
127
|
-
//# sourceMappingURL=cloudflare-transform-store.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare-transform-store.d.ts","sourceRoot":"","sources":["../../../../src/core/media/processors/cloudflare-transform-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,oBAAoB,EAAgB,cAAc,EAAyB,MAAM,YAAY,CAAA;AAK3G,MAAM,WAAW,8BAA+B,SAAQ,oBAAoB;IAC1E,IAAI,EAAE,4BAA4B,CAAA;IAClC,OAAO,EAAE;QACP;;WAEG;QACH,SAAS,EAAE,MAAM,CAAA;QAEjB;;;WAGG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAA;QAEjB;;;;WAIG;QACH,YAAY,EAAE,MAAM,CAAA;QAEpB;;;WAGG;QACH,QAAQ,CAAC,EAAE,GAAG,CAAA;QAEd;;;WAGG;QACH,SAAS,CAAC,EAAE,MAAM,CAAA;QAElB;;;;WAIG;QACH,aAAa,CAAC,EAAE,OAAO,CAAA;QAEvB;;WAEG;QACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;QAEhD;;WAEG;QACH,cAAc,CAAC,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAED;;;;GAIG;AACH,qBAAa,iCAAkC,SAAQ,cAAc;IACnE,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,CAAK;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,GAAG,CAAC,CAAK;gBAEL,MAAM,EAAE,8BAA8B;IAwBlD;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAOhB,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GACvD,OAAO,CAAC,cAAc,CAAC;IA4C1B;;OAEG;YACW,iBAAiB;IA8B/B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAsC5B;;OAEG;IACH,OAAO,CAAC,aAAa;IAQrB;;OAEG;YACW,YAAY;IAmB1B;;OAEG;IACH,OAAO,CAAC,cAAc;IActB;;OAEG;IACI,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM;IASjE;;OAEG;IACU,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAexD;;OAEG;IACU,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAG7C"}
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloudflare Transform & Store Processor
|
|
3
|
-
*
|
|
4
|
-
* Uses Cloudflare's Image Transformations API to transform images and stores
|
|
5
|
-
* the transformed variants back in R2. This gives you full control over serving
|
|
6
|
-
* while leveraging Cloudflare's transformation capabilities.
|
|
7
|
-
*
|
|
8
|
-
* Unlike the cloudflare-transformations processor which only generates URLs,
|
|
9
|
-
* this processor actually creates and stores the transformed images.
|
|
10
|
-
*
|
|
11
|
-
* Process:
|
|
12
|
-
* 1. Upload original to R2
|
|
13
|
-
* 2. Request transformation via Cloudflare API
|
|
14
|
-
* 3. Store transformed variant in R2
|
|
15
|
-
* 4. Generate R2 URLs for all variants
|
|
16
|
-
*
|
|
17
|
-
* Benefits:
|
|
18
|
-
* - Full control over image serving
|
|
19
|
-
* - Works in development without domain proxy
|
|
20
|
-
* - Transformed images are physically stored
|
|
21
|
-
* - Can serve directly from R2 or your own domain
|
|
22
|
-
*/
|
|
23
|
-
import { ImageProcessor } from './types.js';
|
|
24
|
-
import { createLogger } from '../../utils/logger.js';
|
|
25
|
-
const logger = createLogger('media', 'CloudflareTransformStore');
|
|
26
|
-
/**
|
|
27
|
-
* Cloudflare Transform & Store processor
|
|
28
|
-
*
|
|
29
|
-
* This processor transforms images using Cloudflare and stores them in R2
|
|
30
|
-
*/
|
|
31
|
-
export class CloudflareTransformStoreProcessor extends ImageProcessor {
|
|
32
|
-
accountId;
|
|
33
|
-
apiToken;
|
|
34
|
-
transformUrl;
|
|
35
|
-
r2Bucket;
|
|
36
|
-
mediaPath;
|
|
37
|
-
flatStructure;
|
|
38
|
-
defaultFormat;
|
|
39
|
-
defaultQuality;
|
|
40
|
-
env;
|
|
41
|
-
constructor(config) {
|
|
42
|
-
super(config);
|
|
43
|
-
const { accountId, apiToken, transformUrl, r2Bucket, mediaPath, flatStructure, defaultFormat, defaultQuality } = config.options;
|
|
44
|
-
this.accountId = accountId;
|
|
45
|
-
this.apiToken = apiToken;
|
|
46
|
-
this.transformUrl = transformUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
47
|
-
this.r2Bucket = r2Bucket;
|
|
48
|
-
this.mediaPath = mediaPath || '/media';
|
|
49
|
-
this.flatStructure = flatStructure !== false; // Default to true
|
|
50
|
-
this.defaultFormat = defaultFormat || 'webp';
|
|
51
|
-
this.defaultQuality = defaultQuality || 85;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Set the R2 bucket from Worker environment
|
|
55
|
-
*/
|
|
56
|
-
setEnv(env) {
|
|
57
|
-
this.env = env;
|
|
58
|
-
if (env?.R2) {
|
|
59
|
-
this.r2Bucket = env.R2;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
async processImage(file, metadata) {
|
|
63
|
-
const buffer = await file.arrayBuffer();
|
|
64
|
-
const variants = {};
|
|
65
|
-
// Store original
|
|
66
|
-
const original = {
|
|
67
|
-
url: `${this.mediaPath}/${metadata.id}`,
|
|
68
|
-
width: 0, // Will be determined by first transformation request
|
|
69
|
-
height: 0,
|
|
70
|
-
format: file.type.split('/')[1] || 'jpeg',
|
|
71
|
-
size: file.size,
|
|
72
|
-
buffer: Buffer.from(buffer)
|
|
73
|
-
};
|
|
74
|
-
// Process and store each variant
|
|
75
|
-
for (const variant of this.config.variants || []) {
|
|
76
|
-
try {
|
|
77
|
-
const transformedData = await this.transformAndFetch(metadata.id, buffer, variant);
|
|
78
|
-
// Store the transformed variant in R2
|
|
79
|
-
const variantKey = this.getVariantKey(metadata.id, variant.name);
|
|
80
|
-
await this.storeVariant(variantKey, transformedData.buffer);
|
|
81
|
-
variants[variant.name] = {
|
|
82
|
-
url: `${this.mediaPath}/${variantKey}`,
|
|
83
|
-
width: variant.width || 0,
|
|
84
|
-
height: variant.height || 0,
|
|
85
|
-
format: variant.format || this.defaultFormat,
|
|
86
|
-
size: transformedData.buffer.byteLength,
|
|
87
|
-
buffer: transformedData.buffer
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
logger.error('Failed to transform variant', { variantName: variant.name, error });
|
|
92
|
-
// Continue with other variants even if one fails
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return { original, variants };
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Transform image using Cloudflare and fetch the result
|
|
99
|
-
*/
|
|
100
|
-
async transformAndFetch(imageId, imageBuffer, variant) {
|
|
101
|
-
// First, we need the original image to be accessible via a URL
|
|
102
|
-
// In development, this is tricky - we'll use the transformation URL directly
|
|
103
|
-
// Build transformation parameters
|
|
104
|
-
const params = this.buildTransformParams(variant);
|
|
105
|
-
const transformEndpoint = `${this.transformUrl}${this.mediaPath}/${imageId}?${params}`;
|
|
106
|
-
try {
|
|
107
|
-
// Fetch the transformed image from Cloudflare's CDN
|
|
108
|
-
// This assumes the original is already uploaded and accessible
|
|
109
|
-
const response = await fetch(transformEndpoint);
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
throw new Error(`Transformation failed: ${response.status} ${response.statusText}`);
|
|
112
|
-
}
|
|
113
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
114
|
-
return { buffer: Buffer.from(arrayBuffer) };
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
// Fallback: If we can't fetch from CDN, return original with a warning
|
|
118
|
-
logger.warn('Could not fetch transformation from CDN, using original', error);
|
|
119
|
-
return { buffer: Buffer.from(imageBuffer) };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Build URL parameters for Cloudflare transformation
|
|
124
|
-
*/
|
|
125
|
-
buildTransformParams(variant) {
|
|
126
|
-
const params = new URLSearchParams();
|
|
127
|
-
// Dimensions
|
|
128
|
-
if (variant.width) {
|
|
129
|
-
params.set('width', variant.width.toString());
|
|
130
|
-
}
|
|
131
|
-
if (variant.height) {
|
|
132
|
-
params.set('height', variant.height.toString());
|
|
133
|
-
}
|
|
134
|
-
// Fit mode
|
|
135
|
-
if (variant.fit) {
|
|
136
|
-
const fitMap = {
|
|
137
|
-
'cover': 'crop',
|
|
138
|
-
'contain': 'scale-down',
|
|
139
|
-
'fill': 'crop',
|
|
140
|
-
'inside': 'scale-down',
|
|
141
|
-
'outside': 'crop'
|
|
142
|
-
};
|
|
143
|
-
params.set('fit', fitMap[variant.fit] || variant.fit);
|
|
144
|
-
}
|
|
145
|
-
// Format
|
|
146
|
-
const format = variant.format || this.defaultFormat;
|
|
147
|
-
if (format !== 'jpeg') {
|
|
148
|
-
params.set('format', format);
|
|
149
|
-
}
|
|
150
|
-
// Quality
|
|
151
|
-
const quality = variant.quality || this.defaultQuality;
|
|
152
|
-
if (quality !== 85) {
|
|
153
|
-
params.set('quality', quality.toString());
|
|
154
|
-
}
|
|
155
|
-
return params.toString();
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Get the storage key for a variant
|
|
159
|
-
*/
|
|
160
|
-
getVariantKey(imageId, variantName) {
|
|
161
|
-
if (this.flatStructure) {
|
|
162
|
-
return `${imageId}-${variantName}`;
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
return `${imageId}/${variantName}`;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Store a variant in R2
|
|
170
|
-
*/
|
|
171
|
-
async storeVariant(key, buffer) {
|
|
172
|
-
if (!this.r2Bucket) {
|
|
173
|
-
logger.warn('R2 bucket not available, cannot store variant', { key });
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
try {
|
|
177
|
-
// Store in R2 using the bucket binding
|
|
178
|
-
await this.r2Bucket.put(key, buffer, {
|
|
179
|
-
httpMetadata: {
|
|
180
|
-
contentType: this.getContentType(key)
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
logger.error('Failed to store variant in R2', error);
|
|
186
|
-
throw error;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Get content type from file extension
|
|
191
|
-
*/
|
|
192
|
-
getContentType(filename) {
|
|
193
|
-
const ext = filename.split('.').pop()?.toLowerCase();
|
|
194
|
-
const types = {
|
|
195
|
-
'jpg': 'image/jpeg',
|
|
196
|
-
'jpeg': 'image/jpeg',
|
|
197
|
-
'png': 'image/png',
|
|
198
|
-
'webp': 'image/webp',
|
|
199
|
-
'avif': 'image/avif',
|
|
200
|
-
'gif': 'image/gif',
|
|
201
|
-
'svg': 'image/svg+xml'
|
|
202
|
-
};
|
|
203
|
-
return types[ext || ''] || 'application/octet-stream';
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Get the URL for a specific variant
|
|
207
|
-
*/
|
|
208
|
-
getImageUrl(imageId, variantName) {
|
|
209
|
-
if (!variantName || variantName === 'original') {
|
|
210
|
-
return `${this.mediaPath}/${imageId}`;
|
|
211
|
-
}
|
|
212
|
-
const variantKey = this.getVariantKey(imageId, variantName);
|
|
213
|
-
return `${this.mediaPath}/${variantKey}`;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Delete image and all variants
|
|
217
|
-
*/
|
|
218
|
-
async deleteImage(imageId) {
|
|
219
|
-
if (!this.r2Bucket) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
// Delete original
|
|
223
|
-
await this.r2Bucket.delete(`${this.mediaPath}/${imageId}`);
|
|
224
|
-
// Delete all variants
|
|
225
|
-
for (const variant of this.config.variants || []) {
|
|
226
|
-
const variantKey = this.getVariantKey(imageId, variant.name);
|
|
227
|
-
await this.r2Bucket.delete(`${this.mediaPath}/${variantKey}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Health check
|
|
232
|
-
*/
|
|
233
|
-
async healthCheck() {
|
|
234
|
-
return !!this.r2Bucket;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
//# sourceMappingURL=cloudflare-transform-store.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare-transform-store.js","sourceRoot":"","sources":["../../../../src/core/media/processors/cloudflare-transform-store.ts"],"names":[],"mappings":"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"}
|