@umituz/web-cloudflare 1.4.6 → 1.4.7
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/package.json +1 -1
- package/src/config/patterns.ts +2 -2
- package/src/domains/ai-gateway/services/index.ts +18 -8
- package/src/domains/analytics/services/analytics.service.ts +16 -92
- package/src/domains/analytics/types/service.interface.ts +1 -1
- package/src/domains/d1/services/d1.service.ts +19 -22
- package/src/domains/images/services/images.service.ts +58 -21
- package/src/domains/kv/services/kv.service.ts +10 -5
- package/src/domains/middleware/entities/index.ts +4 -4
- package/src/domains/middleware/services/auth.service.ts +6 -2
- package/src/domains/middleware/services/cache.service.ts +7 -3
- package/src/domains/middleware/services/cors.service.ts +8 -5
- package/src/domains/middleware/services/rate-limit.service.ts +7 -3
- package/src/domains/middleware/types/service.interface.ts +17 -11
- package/src/domains/r2/services/r2.service.ts +21 -13
- package/src/domains/workers/entities/index.ts +17 -1
- package/src/domains/workers/examples/worker.example.ts +10 -8
- package/src/domains/workers/services/workers.service.ts +6 -4
- package/src/domains/workflows/entities/index.ts +14 -1
- package/src/domains/workflows/services/workflows.service.ts +43 -10
- package/src/domains/wrangler/services/wrangler.service.ts +150 -443
- package/src/index.ts +44 -12
- package/src/infrastructure/middleware/index.ts +18 -1
- package/src/infrastructure/utils/helpers.ts +29 -7
- package/src/presentation/hooks/cloudflare.hooks.ts +7 -309
- package/src/presentation/hooks/index.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-cloudflare",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "Comprehensive Cloudflare Workers integration with config-based patterns, middleware, router, workflows, and AI (Patch-only versioning: only z in x.y.z increments)",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/config/patterns.ts
CHANGED
|
@@ -246,7 +246,7 @@ export const minimalConfig: Partial<WorkerConfig> = {
|
|
|
246
246
|
export function mergeConfigs<T extends Record<string, any>>(
|
|
247
247
|
base: T,
|
|
248
248
|
...overrides: Array<Partial<Record<string, any>>>
|
|
249
|
-
):
|
|
249
|
+
): Record<string, any> {
|
|
250
250
|
return overrides.reduce((acc, override) => {
|
|
251
251
|
return deepMerge(acc, override);
|
|
252
252
|
}, base);
|
|
@@ -255,7 +255,7 @@ export function mergeConfigs<T extends Record<string, any>>(
|
|
|
255
255
|
function deepMerge<T extends Record<string, any>>(
|
|
256
256
|
target: T,
|
|
257
257
|
source: Partial<Record<string, any>>
|
|
258
|
-
):
|
|
258
|
+
): Record<string, any> {
|
|
259
259
|
const output = { ...target };
|
|
260
260
|
|
|
261
261
|
if (isObject(target) && isObject(source)) {
|
|
@@ -127,17 +127,27 @@ export class AIGatewayService {
|
|
|
127
127
|
throw new Error(`Provider error: ${response.statusText}`);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
const data = await response.json()
|
|
130
|
+
const data = await response.json() as {
|
|
131
|
+
id?: string;
|
|
132
|
+
content?: string;
|
|
133
|
+
text?: string;
|
|
134
|
+
output?: string;
|
|
135
|
+
usage?: {
|
|
136
|
+
promptTokens?: number;
|
|
137
|
+
completionTokens?: number;
|
|
138
|
+
totalTokens?: number;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
131
141
|
|
|
132
142
|
return {
|
|
133
143
|
id: data.id || this.generateId(),
|
|
134
144
|
provider: provider.id,
|
|
135
145
|
model: request.model,
|
|
136
|
-
content: data.content || data.text || data.output,
|
|
137
|
-
usage:
|
|
138
|
-
promptTokens: 0,
|
|
139
|
-
completionTokens: 0,
|
|
140
|
-
totalTokens: 0,
|
|
146
|
+
content: data.content || data.text || data.output || '',
|
|
147
|
+
usage: {
|
|
148
|
+
promptTokens: data.usage?.promptTokens || 0,
|
|
149
|
+
completionTokens: data.usage?.completionTokens || 0,
|
|
150
|
+
totalTokens: data.usage?.totalTokens || 0,
|
|
141
151
|
},
|
|
142
152
|
cached: false,
|
|
143
153
|
timestamp: Date.now(),
|
|
@@ -215,9 +225,9 @@ import type {
|
|
|
215
225
|
|
|
216
226
|
export class WorkersAIService {
|
|
217
227
|
private env: {
|
|
218
|
-
AI?:
|
|
228
|
+
AI?: any;
|
|
219
229
|
bindings?: {
|
|
220
|
-
AI?:
|
|
230
|
+
AI?: any;
|
|
221
231
|
};
|
|
222
232
|
};
|
|
223
233
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Analytics Service
|
|
3
|
-
* @description Cloudflare Web Analytics operations
|
|
3
|
+
* @description Cloudflare Web Analytics operations for Workers runtime
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { AnalyticsEvent, AnalyticsPageviewEvent, AnalyticsCustomEvent
|
|
6
|
+
import type { AnalyticsEvent, AnalyticsPageviewEvent, AnalyticsCustomEvent } from "../entities";
|
|
7
|
+
import type { AnalyticsData } from "../../../domain/entities/analytics.entity";
|
|
7
8
|
import type { IAnalyticsService } from "../../../domain/interfaces/services.interface";
|
|
8
9
|
|
|
9
10
|
export interface AnalyticsClientOptions {
|
|
@@ -29,13 +30,9 @@ class AnalyticsService implements IAnalyticsService {
|
|
|
29
30
|
|
|
30
31
|
async trackEvent(event: AnalyticsEvent): Promise<void> {
|
|
31
32
|
this.ensureInitialized();
|
|
32
|
-
|
|
33
33
|
this.eventQueue.push(event);
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
if (typeof window !== "undefined" && (window as any)._cfAnalytics) {
|
|
37
|
-
(window as any)._cfAnalytics.track(event);
|
|
38
|
-
}
|
|
34
|
+
// In Workers runtime, events are queued for batch processing
|
|
35
|
+
// The actual sending would be done via Cloudflare Analytics API
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
async trackPageview(url: string, title: string, referrer?: string): Promise<void> {
|
|
@@ -50,12 +47,10 @@ class AnalyticsService implements IAnalyticsService {
|
|
|
50
47
|
await this.trackEvent(event);
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
async trackCustom(eventName: string, data?: Record<string, unknown
|
|
54
|
-
if (typeof window === "undefined") return;
|
|
55
|
-
|
|
50
|
+
async trackCustom(eventName: string, data?: Record<string, unknown>, url?: string): Promise<void> {
|
|
56
51
|
const event: AnalyticsCustomEvent = {
|
|
57
52
|
timestamp: Date.now(),
|
|
58
|
-
url:
|
|
53
|
+
url: url || '/workers',
|
|
59
54
|
eventType: "custom",
|
|
60
55
|
eventName,
|
|
61
56
|
eventData: data,
|
|
@@ -65,11 +60,9 @@ class AnalyticsService implements IAnalyticsService {
|
|
|
65
60
|
}
|
|
66
61
|
|
|
67
62
|
async trackOutboundLink(url: string, linkType?: string): Promise<void> {
|
|
68
|
-
if (typeof window === "undefined") return;
|
|
69
|
-
|
|
70
63
|
const event: AnalyticsCustomEvent = {
|
|
71
64
|
timestamp: Date.now(),
|
|
72
|
-
url:
|
|
65
|
+
url: '/workers',
|
|
73
66
|
eventType: "custom",
|
|
74
67
|
eventName: "outbound-link",
|
|
75
68
|
eventData: {
|
|
@@ -82,11 +75,9 @@ class AnalyticsService implements IAnalyticsService {
|
|
|
82
75
|
}
|
|
83
76
|
|
|
84
77
|
async trackTiming(name: string, value: number, label?: string): Promise<void> {
|
|
85
|
-
if (typeof window === "undefined") return;
|
|
86
|
-
|
|
87
78
|
const event: AnalyticsEvent = {
|
|
88
79
|
timestamp: Date.now(),
|
|
89
|
-
url:
|
|
80
|
+
url: '/workers',
|
|
90
81
|
eventType: "timing",
|
|
91
82
|
eventData: {
|
|
92
83
|
name,
|
|
@@ -101,89 +92,22 @@ class AnalyticsService implements IAnalyticsService {
|
|
|
101
92
|
async getAnalytics(): Promise<AnalyticsData> {
|
|
102
93
|
this.ensureInitialized();
|
|
103
94
|
|
|
104
|
-
// In a real implementation, this would fetch from Cloudflare Analytics API
|
|
105
|
-
// For now, return queued events
|
|
106
|
-
|
|
107
95
|
return {
|
|
108
96
|
siteId: this.siteId!,
|
|
109
|
-
events: this.eventQueue,
|
|
97
|
+
events: [...this.eventQueue],
|
|
110
98
|
metrics: {
|
|
111
|
-
pageviews: this.eventQueue.filter(
|
|
112
|
-
uniqueVisitors:
|
|
99
|
+
pageviews: this.eventQueue.filter(e => e.eventType === "pageview").length,
|
|
100
|
+
uniqueVisitors: 0, // Would be calculated from stored data
|
|
113
101
|
},
|
|
114
102
|
};
|
|
115
103
|
}
|
|
116
104
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*/
|
|
120
|
-
getScriptTag(): string {
|
|
121
|
-
this.ensureInitialized();
|
|
122
|
-
|
|
123
|
-
const scriptUrl = this.scriptUrl || `https://static.cloudflareinsights.com/beacon.min.js`;
|
|
124
|
-
|
|
125
|
-
return `
|
|
126
|
-
<script defer src='${scriptUrl}' data-cf-beacon='{"token": "${this.siteId}"}'></script>
|
|
127
|
-
`.trim();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Clear queued events
|
|
132
|
-
*/
|
|
133
|
-
clearEvents(): void {
|
|
105
|
+
async flush(): Promise<void> {
|
|
106
|
+
// In Workers, this would send events to Cloudflare Analytics API
|
|
134
107
|
this.eventQueue = [];
|
|
135
108
|
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Get queued events
|
|
139
|
-
*/
|
|
140
|
-
getQueuedEvents(): readonly AnalyticsEvent[] {
|
|
141
|
-
return this.eventQueue;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* E-commerce helpers
|
|
146
|
-
*/
|
|
147
|
-
async trackPurchase(transactionId: string, revenue: number, items: readonly { id: string; name: string; price: number; quantity: number }[]): Promise<void> {
|
|
148
|
-
await this.trackCustom("purchase", {
|
|
149
|
-
transactionId,
|
|
150
|
-
revenue,
|
|
151
|
-
items,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async trackAddToCart(itemId: string, price: number, quantity: number): Promise<void> {
|
|
156
|
-
await this.trackCustom("add-to-cart", {
|
|
157
|
-
itemId,
|
|
158
|
-
price,
|
|
159
|
-
quantity,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async trackRemoveFromCart(itemId: string, quantity: number): Promise<void> {
|
|
164
|
-
await this.trackCustom("remove-from-cart", {
|
|
165
|
-
itemId,
|
|
166
|
-
quantity,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Engagement helpers
|
|
172
|
-
*/
|
|
173
|
-
async trackScrollDepth(depth: number): Promise<void> {
|
|
174
|
-
await this.trackCustom("scroll-depth", { depth });
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async trackTimeOnPage(seconds: number): Promise<void> {
|
|
178
|
-
await this.trackCustom("time-on-page", { seconds });
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async trackEngagement(action: string, target?: string): Promise<void> {
|
|
182
|
-
await this.trackCustom("engagement", {
|
|
183
|
-
action,
|
|
184
|
-
target,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
109
|
}
|
|
188
110
|
|
|
111
|
+
// Export class and singleton instance
|
|
112
|
+
export { AnalyticsService };
|
|
189
113
|
export const analyticsService = new AnalyticsService();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description Abstract interface for Analytics operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { AnalyticsEvent, AnalyticsData } from '
|
|
6
|
+
import type { AnalyticsEvent, AnalyticsData } from '../../../domain/entities/analytics.entity';
|
|
7
7
|
|
|
8
8
|
export interface IAnalyticsService {
|
|
9
9
|
trackEvent(event: AnalyticsEvent): Promise<void>;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description Cloudflare D1 database operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { D1QueryResult, D1BatchResult } from "
|
|
6
|
+
import type { D1QueryResult, D1BatchResult } from "../../../domain/entities/d1.entity";
|
|
7
7
|
import type { ID1Service } from "../../../domain/interfaces/services.interface";
|
|
8
8
|
|
|
9
9
|
export interface D1ExecOptions {
|
|
@@ -33,14 +33,10 @@ class D1Service implements ID1Service {
|
|
|
33
33
|
const result = params ? await stmt.bind(...params).all() : await stmt.all();
|
|
34
34
|
|
|
35
35
|
return {
|
|
36
|
-
|
|
37
|
-
success: result.success,
|
|
36
|
+
rows: result.results as T[],
|
|
38
37
|
meta: result.meta
|
|
39
38
|
? {
|
|
40
39
|
duration: result.meta.duration,
|
|
41
|
-
rows_read: result.meta.rows_read,
|
|
42
|
-
rows_written: result.meta.rows_written,
|
|
43
|
-
last_row_id: result.meta.last_row_id,
|
|
44
40
|
changes: result.meta.changes,
|
|
45
41
|
}
|
|
46
42
|
: undefined,
|
|
@@ -48,20 +44,20 @@ class D1Service implements ID1Service {
|
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
async batch(
|
|
51
|
-
|
|
47
|
+
sqlStatements: readonly { sql: string; params?: readonly unknown[]; binding?: string }[]
|
|
52
48
|
): Promise<D1BatchResult> {
|
|
53
49
|
// Group by binding
|
|
54
50
|
const grouped = new Map<string, D1PreparedStatement[]>();
|
|
55
51
|
|
|
56
|
-
for (const stmt of
|
|
57
|
-
const
|
|
58
|
-
const database = this.getDatabase(
|
|
52
|
+
for (const stmt of sqlStatements) {
|
|
53
|
+
const bindingName = stmt.binding || "default";
|
|
54
|
+
const database = this.getDatabase(bindingName);
|
|
59
55
|
|
|
60
|
-
if (!grouped.has(
|
|
61
|
-
grouped.set(
|
|
56
|
+
if (!grouped.has(bindingName)) {
|
|
57
|
+
grouped.set(bindingName, []);
|
|
62
58
|
}
|
|
63
59
|
|
|
64
|
-
grouped.get(
|
|
60
|
+
grouped.get(bindingName)!.push(database.prepare(stmt.sql));
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
// Note: D1 batch requires all statements to be from the same database
|
|
@@ -69,18 +65,17 @@ class D1Service implements ID1Service {
|
|
|
69
65
|
throw new Error("Batch operations must be from the same database binding");
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
const [
|
|
73
|
-
const database = this.getDatabase(
|
|
68
|
+
const [bindingName, preparedStatements] = Array.from(grouped.entries())[0];
|
|
69
|
+
const database = this.getDatabase(bindingName);
|
|
74
70
|
|
|
75
|
-
const results = await database.batch(
|
|
71
|
+
const results = await database.batch(preparedStatements as D1PreparedStatement[]);
|
|
76
72
|
|
|
77
73
|
return {
|
|
78
74
|
success: results.every((r) => r.success),
|
|
79
75
|
results: results.map((r) => ({
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
})),
|
|
76
|
+
rows: r.results,
|
|
77
|
+
meta: r.meta ? { duration: r.meta.duration, changes: r.meta.changes } : undefined,
|
|
78
|
+
})) as D1QueryResult[],
|
|
84
79
|
};
|
|
85
80
|
}
|
|
86
81
|
|
|
@@ -94,7 +89,7 @@ class D1Service implements ID1Service {
|
|
|
94
89
|
async findOne<T>(sql: string, params?: readonly unknown[], binding?: string): Promise<T | null> {
|
|
95
90
|
const result = await this.query<T>(sql, params, binding);
|
|
96
91
|
|
|
97
|
-
return (result.
|
|
92
|
+
return (result.rows[0] as T) ?? null;
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
async insert<T>(
|
|
@@ -165,7 +160,7 @@ class D1Service implements ID1Service {
|
|
|
165
160
|
|
|
166
161
|
const result = await this.query<{ name: string }>(sql, [table], binding);
|
|
167
162
|
|
|
168
|
-
return result.
|
|
163
|
+
return result.rows.length > 0;
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
/**
|
|
@@ -188,4 +183,6 @@ interface D1Transaction {
|
|
|
188
183
|
query<T>(sql: string, params?: readonly unknown[]): Promise<D1QueryResult<T>>;
|
|
189
184
|
}
|
|
190
185
|
|
|
186
|
+
// Export class and singleton instance
|
|
187
|
+
export { D1Service };
|
|
191
188
|
export const d1Service = new D1Service();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description Cloudflare Images operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { ImageUploadResult, ImageUploadOptions, ImageTransformation, SignedURL } from "
|
|
6
|
+
import type { ImageUploadResult, ImageUploadOptions, ImageTransformation, SignedURL } from "../../../domain/entities/image.entity";
|
|
7
7
|
import type { IImageService } from "../../../domain/interfaces/services.interface";
|
|
8
8
|
import { validationUtils, transformUtils } from "../../../infrastructure/utils";
|
|
9
9
|
import { MAX_IMAGE_SIZE, ALLOWED_IMAGE_TYPES } from "../../../infrastructure/constants";
|
|
@@ -59,14 +59,13 @@ class ImagesService implements IImageService {
|
|
|
59
59
|
const formData = new FormData();
|
|
60
60
|
formData.append("file", file);
|
|
61
61
|
|
|
62
|
-
if
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
formData.append("requireSignedURLs", options.requireSignedURLs.toString());
|
|
62
|
+
// Apply transformations if specified in options
|
|
63
|
+
if (options?.width || options?.height || options?.format || options?.quality) {
|
|
64
|
+
const variants: Array<{ width?: number; height?: number; format?: string; quality?: number }> = [];
|
|
65
|
+
if (options.width) variants.push({ width: options.width });
|
|
66
|
+
if (options.height) variants.push({ height: options.height });
|
|
67
|
+
if (options.format) variants.push({ format: options.format });
|
|
68
|
+
if (options.quality) variants.push({ quality: options.quality });
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
// Upload
|
|
@@ -81,19 +80,31 @@ class ImagesService implements IImageService {
|
|
|
81
80
|
throw new Error(`Upload failed: ${error}`);
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
const data = await response.json()
|
|
83
|
+
const data = await response.json() as {
|
|
84
|
+
result: {
|
|
85
|
+
id: string;
|
|
86
|
+
filename: string;
|
|
87
|
+
uploaded: string;
|
|
88
|
+
variants: string[];
|
|
89
|
+
requireSignedURLs: boolean;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
85
92
|
|
|
93
|
+
// Map API response to old entity type
|
|
86
94
|
return {
|
|
87
95
|
id: data.result.id,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
url: data.result.variants[0] || '',
|
|
97
|
+
variants: data.result.variants.map((v) => ({
|
|
98
|
+
width: 0,
|
|
99
|
+
height: 0,
|
|
100
|
+
format: (options?.format || 'jpeg') as 'jpeg' | 'png' | 'webp' | 'avif',
|
|
101
|
+
url: v,
|
|
102
|
+
})),
|
|
92
103
|
};
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
async getSignedURL(imageId: string, expiresIn = 3600): Promise<SignedURL> {
|
|
96
|
-
const
|
|
107
|
+
const expires = Date.now() + expiresIn * 1000;
|
|
97
108
|
|
|
98
109
|
const response = await fetch(`${this.getAPIBaseURL()}/${imageId}`, {
|
|
99
110
|
method: "POST",
|
|
@@ -102,7 +113,7 @@ class ImagesService implements IImageService {
|
|
|
102
113
|
"Content-Type": "application/json",
|
|
103
114
|
},
|
|
104
115
|
body: JSON.stringify({
|
|
105
|
-
expiry:
|
|
116
|
+
expiry: new Date(expires).toISOString(),
|
|
106
117
|
}),
|
|
107
118
|
});
|
|
108
119
|
|
|
@@ -111,11 +122,16 @@ class ImagesService implements IImageService {
|
|
|
111
122
|
throw new Error(`Failed to get signed URL: ${error}`);
|
|
112
123
|
}
|
|
113
124
|
|
|
114
|
-
const data = await response.json()
|
|
125
|
+
const data = await response.json() as {
|
|
126
|
+
result: {
|
|
127
|
+
signedURLs?: string[];
|
|
128
|
+
variants?: string[];
|
|
129
|
+
};
|
|
130
|
+
};
|
|
115
131
|
|
|
116
132
|
return {
|
|
117
|
-
url: data.result.signedURLs?.[0] || data.result.variants?.[0],
|
|
118
|
-
|
|
133
|
+
url: data.result.signedURLs?.[0] || data.result.variants?.[0] || '',
|
|
134
|
+
expires,
|
|
119
135
|
};
|
|
120
136
|
}
|
|
121
137
|
|
|
@@ -164,10 +180,29 @@ class ImagesService implements IImageService {
|
|
|
164
180
|
throw new Error(`List failed: ${error}`);
|
|
165
181
|
}
|
|
166
182
|
|
|
167
|
-
const data = await response.json()
|
|
183
|
+
const data = await response.json() as {
|
|
184
|
+
result: {
|
|
185
|
+
images: Array<{
|
|
186
|
+
id: string;
|
|
187
|
+
filename: string;
|
|
188
|
+
uploaded: string;
|
|
189
|
+
variants: string[];
|
|
190
|
+
}>;
|
|
191
|
+
totalCount: number;
|
|
192
|
+
};
|
|
193
|
+
};
|
|
168
194
|
|
|
169
195
|
return {
|
|
170
|
-
images: data.result.images
|
|
196
|
+
images: data.result.images.map((img) => ({
|
|
197
|
+
id: img.id,
|
|
198
|
+
url: img.variants[0] || '',
|
|
199
|
+
variants: img.variants.map((v) => ({
|
|
200
|
+
width: 0,
|
|
201
|
+
height: 0,
|
|
202
|
+
format: 'jpeg' as const,
|
|
203
|
+
url: v,
|
|
204
|
+
})),
|
|
205
|
+
})),
|
|
171
206
|
totalCount: data.result.totalCount,
|
|
172
207
|
};
|
|
173
208
|
}
|
|
@@ -224,4 +259,6 @@ class ImagesService implements IImageService {
|
|
|
224
259
|
}
|
|
225
260
|
}
|
|
226
261
|
|
|
262
|
+
// Export class and singleton instance
|
|
263
|
+
export { ImagesService };
|
|
227
264
|
export const imagesService = new ImagesService();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description Cloudflare KV key-value storage operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { KVEntry, KVListOptions, KVListResult } from "
|
|
6
|
+
import type { KVEntry, KVListOptions, KVListResult } from "../../../domain/entities/kv.entity";
|
|
7
7
|
import type { IKVService } from "../../../domain/interfaces/services.interface";
|
|
8
8
|
import { validationUtils, cacheUtils } from "../../../infrastructure/utils";
|
|
9
9
|
|
|
@@ -79,9 +79,12 @@ class KVService implements IKVService {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
return {
|
|
82
|
-
keys: list.keys
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
keys: list.keys.map((k) => ({
|
|
83
|
+
key: k.name,
|
|
84
|
+
value: '',
|
|
85
|
+
metadata: k.metadata as Record<string, unknown> | undefined,
|
|
86
|
+
})),
|
|
87
|
+
cursor: (list as any).cursor as string | undefined,
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
90
|
|
|
@@ -107,10 +110,12 @@ class KVService implements IKVService {
|
|
|
107
110
|
|
|
108
111
|
await Promise.all(
|
|
109
112
|
list.keys.map(async (key) => {
|
|
110
|
-
await this.delete(key.
|
|
113
|
+
await this.delete(key.key, binding);
|
|
111
114
|
})
|
|
112
115
|
);
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
// Export class and singleton instance
|
|
120
|
+
export { KVService };
|
|
116
121
|
export const kvService = new KVService();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* CORS configuration
|
|
8
8
|
*/
|
|
9
|
-
export interface
|
|
9
|
+
export interface MiddlewareCORSConfig {
|
|
10
10
|
enabled: boolean;
|
|
11
11
|
allowedOrigins: string[];
|
|
12
12
|
allowedMethods: string[];
|
|
@@ -19,7 +19,7 @@ export interface CORSConfig {
|
|
|
19
19
|
/**
|
|
20
20
|
* Cache configuration
|
|
21
21
|
*/
|
|
22
|
-
export interface
|
|
22
|
+
export interface MiddlewareCacheConfig {
|
|
23
23
|
enabled: boolean;
|
|
24
24
|
defaultTTL: number;
|
|
25
25
|
paths?: Record<string, number>;
|
|
@@ -31,7 +31,7 @@ export interface CacheConfig {
|
|
|
31
31
|
/**
|
|
32
32
|
* Rate limit configuration
|
|
33
33
|
*/
|
|
34
|
-
export interface
|
|
34
|
+
export interface MiddlewareRateLimitConfig {
|
|
35
35
|
enabled: boolean;
|
|
36
36
|
maxRequests: number;
|
|
37
37
|
window: number;
|
|
@@ -48,7 +48,7 @@ export interface RateLimitConfig {
|
|
|
48
48
|
/**
|
|
49
49
|
* Authentication configuration
|
|
50
50
|
*/
|
|
51
|
-
export interface
|
|
51
|
+
export interface MiddlewareAuthConfig {
|
|
52
52
|
enabled: boolean;
|
|
53
53
|
type: 'bearer' | 'apikey' | 'basic';
|
|
54
54
|
token?: string;
|
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
* @description Authentication middleware for Cloudflare Workers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { MiddlewareAuthConfig } from '../entities';
|
|
7
|
+
|
|
8
|
+
// Type aliases for backwards compatibility
|
|
9
|
+
export type { MiddlewareAuthConfig };
|
|
10
|
+
export type AuthConfig = MiddlewareAuthConfig;
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* Require authentication
|
|
10
14
|
*/
|
|
11
15
|
export async function requireAuth(
|
|
12
16
|
request: Request,
|
|
13
|
-
config:
|
|
17
|
+
config: MiddlewareAuthConfig
|
|
14
18
|
): Promise<Response | null> {
|
|
15
19
|
if (!config.enabled) {
|
|
16
20
|
return null;
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* @description Caching middleware for Cloudflare Workers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { MiddlewareCacheConfig } from '../entities';
|
|
7
|
+
|
|
8
|
+
// Type aliases for backwards compatibility
|
|
9
|
+
export type { MiddlewareCacheConfig };
|
|
10
|
+
export type CacheConfig = MiddlewareCacheConfig;
|
|
7
11
|
|
|
8
12
|
interface CacheEntry {
|
|
9
13
|
response: Response;
|
|
@@ -17,7 +21,7 @@ const cacheStore = new Map<string, CacheEntry>();
|
|
|
17
21
|
*/
|
|
18
22
|
export async function cache(
|
|
19
23
|
request: Request,
|
|
20
|
-
config:
|
|
24
|
+
config: MiddlewareCacheConfig
|
|
21
25
|
): Promise<Response | null> {
|
|
22
26
|
if (!config.enabled) {
|
|
23
27
|
return null;
|
|
@@ -46,7 +50,7 @@ export async function cache(
|
|
|
46
50
|
export function setCache(
|
|
47
51
|
request: Request,
|
|
48
52
|
response: Response,
|
|
49
|
-
config:
|
|
53
|
+
config: MiddlewareCacheConfig
|
|
50
54
|
): void {
|
|
51
55
|
if (!config.enabled) {
|
|
52
56
|
return;
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* @description Cross-Origin Resource Sharing middleware for Cloudflare Workers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { MiddlewareCORSConfig } from '../entities';
|
|
7
|
+
|
|
8
|
+
// Type alias for backwards compatibility
|
|
9
|
+
export type CORSConfig = MiddlewareCORSConfig;
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Add CORS headers to response
|
|
@@ -11,7 +14,7 @@ import type { CORSConfig } from '../entities';
|
|
|
11
14
|
export function addCorsHeaders(
|
|
12
15
|
request: Request,
|
|
13
16
|
response: Response,
|
|
14
|
-
config:
|
|
17
|
+
config: MiddlewareCORSConfig
|
|
15
18
|
): Response {
|
|
16
19
|
if (!config.enabled) {
|
|
17
20
|
return response;
|
|
@@ -23,7 +26,7 @@ export function addCorsHeaders(
|
|
|
23
26
|
// Check if origin is allowed
|
|
24
27
|
const allowedOrigin = config.allowedOrigins.includes('*')
|
|
25
28
|
? '*'
|
|
26
|
-
: config.allowedOrigins.includes(origin
|
|
29
|
+
: origin && config.allowedOrigins.includes(origin)
|
|
27
30
|
? origin
|
|
28
31
|
: config.allowedOrigins[0];
|
|
29
32
|
|
|
@@ -55,7 +58,7 @@ export function addCorsHeaders(
|
|
|
55
58
|
*/
|
|
56
59
|
export async function cors(
|
|
57
60
|
request: Request,
|
|
58
|
-
config:
|
|
61
|
+
config: MiddlewareCORSConfig
|
|
59
62
|
): Promise<Response | null> {
|
|
60
63
|
if (!config.enabled) {
|
|
61
64
|
return null;
|
|
@@ -68,7 +71,7 @@ export async function cors(
|
|
|
68
71
|
|
|
69
72
|
const allowedOrigin = config.allowedOrigins.includes('*')
|
|
70
73
|
? '*'
|
|
71
|
-
: config.allowedOrigins.includes(origin
|
|
74
|
+
: origin && config.allowedOrigins.includes(origin)
|
|
72
75
|
? origin
|
|
73
76
|
: config.allowedOrigins[0];
|
|
74
77
|
|