@umituz/web-cloudflare 1.4.6 → 1.4.8
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 +11 -8
- package/src/config/types.ts +9 -1
- package/src/domains/ai-gateway/services/index.ts +52 -25
- 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 +14 -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 +25 -13
- package/src/domains/workers/entities/index.ts +17 -1
- package/src/domains/workers/examples/worker.example.ts +11 -8
- package/src/domains/workers/services/workers.service.ts +9 -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 +23 -18
- package/src/infrastructure/router/index.ts +2 -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.8",
|
|
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,24 +255,27 @@ 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
|
-
):
|
|
259
|
-
const output = { ...target };
|
|
258
|
+
): Record<string, any> {
|
|
259
|
+
const output: Record<string, unknown> = { ...target };
|
|
260
260
|
|
|
261
261
|
if (isObject(target) && isObject(source)) {
|
|
262
262
|
Object.keys(source).forEach((key) => {
|
|
263
263
|
const sourceValue = source[key];
|
|
264
|
-
const targetValue = target
|
|
264
|
+
const targetValue = (target as Record<string, unknown>)[key];
|
|
265
265
|
|
|
266
266
|
if (isObject(sourceValue)) {
|
|
267
267
|
if (!(key in target)) {
|
|
268
|
-
|
|
268
|
+
output[key] = sourceValue;
|
|
269
269
|
} else if (isObject(targetValue)) {
|
|
270
|
-
|
|
270
|
+
output[key] = deepMerge(
|
|
271
|
+
targetValue as Record<string, any>,
|
|
272
|
+
sourceValue
|
|
273
|
+
);
|
|
271
274
|
} else {
|
|
272
|
-
|
|
275
|
+
output[key] = sourceValue;
|
|
273
276
|
}
|
|
274
277
|
} else {
|
|
275
|
-
|
|
278
|
+
output[key] = sourceValue;
|
|
276
279
|
}
|
|
277
280
|
});
|
|
278
281
|
}
|
package/src/config/types.ts
CHANGED
|
@@ -629,7 +629,7 @@ export interface EnvConfig {
|
|
|
629
629
|
/**
|
|
630
630
|
* AI bindings
|
|
631
631
|
*/
|
|
632
|
-
AI?:
|
|
632
|
+
AI?: WorkersAIBinding;
|
|
633
633
|
|
|
634
634
|
/**
|
|
635
635
|
* Custom environment variables
|
|
@@ -637,6 +637,14 @@ export interface EnvConfig {
|
|
|
637
637
|
vars?: Record<string, string>;
|
|
638
638
|
}
|
|
639
639
|
|
|
640
|
+
/**
|
|
641
|
+
* Workers AI Binding
|
|
642
|
+
* @description Cloudflare Workers AI runtime binding
|
|
643
|
+
*/
|
|
644
|
+
export interface WorkersAIBinding {
|
|
645
|
+
run: <T = unknown>(model: string, inputs: Record<string, unknown>) => Promise<T>;
|
|
646
|
+
}
|
|
647
|
+
|
|
640
648
|
// ============================================================
|
|
641
649
|
// Configuration Merging Types
|
|
642
650
|
// ============================================================
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
AIProvider,
|
|
11
11
|
AIAnalytics,
|
|
12
12
|
} from '../entities';
|
|
13
|
+
import type { WorkersAIBinding } from '../../../config/types';
|
|
13
14
|
|
|
14
15
|
export class AIGatewayService {
|
|
15
16
|
private config: AIGatewayConfig;
|
|
@@ -127,17 +128,27 @@ export class AIGatewayService {
|
|
|
127
128
|
throw new Error(`Provider error: ${response.statusText}`);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
const data = await response.json()
|
|
131
|
+
const data = await response.json() as {
|
|
132
|
+
id?: string;
|
|
133
|
+
content?: string;
|
|
134
|
+
text?: string;
|
|
135
|
+
output?: string;
|
|
136
|
+
usage?: {
|
|
137
|
+
promptTokens?: number;
|
|
138
|
+
completionTokens?: number;
|
|
139
|
+
totalTokens?: number;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
131
142
|
|
|
132
143
|
return {
|
|
133
144
|
id: data.id || this.generateId(),
|
|
134
145
|
provider: provider.id,
|
|
135
146
|
model: request.model,
|
|
136
|
-
content: data.content || data.text || data.output,
|
|
137
|
-
usage:
|
|
138
|
-
promptTokens: 0,
|
|
139
|
-
completionTokens: 0,
|
|
140
|
-
totalTokens: 0,
|
|
147
|
+
content: data.content || data.text || data.output || '',
|
|
148
|
+
usage: {
|
|
149
|
+
promptTokens: data.usage?.promptTokens || 0,
|
|
150
|
+
completionTokens: data.usage?.completionTokens || 0,
|
|
151
|
+
totalTokens: data.usage?.totalTokens || 0,
|
|
141
152
|
},
|
|
142
153
|
cached: false,
|
|
143
154
|
timestamp: Date.now(),
|
|
@@ -215,16 +226,23 @@ import type {
|
|
|
215
226
|
|
|
216
227
|
export class WorkersAIService {
|
|
217
228
|
private env: {
|
|
218
|
-
AI?:
|
|
229
|
+
AI?: WorkersAIBinding;
|
|
219
230
|
bindings?: {
|
|
220
|
-
AI?:
|
|
231
|
+
AI?: WorkersAIBinding;
|
|
221
232
|
};
|
|
222
233
|
};
|
|
223
234
|
|
|
224
|
-
constructor(env: { AI?:
|
|
235
|
+
constructor(env: { AI?: WorkersAIBinding; bindings?: { AI?: WorkersAIBinding } }) {
|
|
225
236
|
this.env = env;
|
|
226
237
|
}
|
|
227
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Workers AI Binding type
|
|
241
|
+
*/
|
|
242
|
+
private getAI(): WorkersAIBinding | null {
|
|
243
|
+
return this.env.bindings?.AI || this.env.AI || null;
|
|
244
|
+
}
|
|
245
|
+
|
|
228
246
|
/**
|
|
229
247
|
* Run text generation model
|
|
230
248
|
*/
|
|
@@ -233,19 +251,24 @@ export class WorkersAIService {
|
|
|
233
251
|
inputs: WorkersAIInputs['text_generation']
|
|
234
252
|
): Promise<WorkersAIResponse> {
|
|
235
253
|
try {
|
|
236
|
-
|
|
237
|
-
const ai = this.env.bindings?.AI || this.env.AI;
|
|
254
|
+
const ai = this.getAI();
|
|
238
255
|
|
|
239
256
|
if (!ai) {
|
|
240
257
|
throw new Error('Workers AI binding not configured');
|
|
241
258
|
}
|
|
242
259
|
|
|
243
|
-
|
|
260
|
+
if (!inputs) {
|
|
261
|
+
throw new Error('Inputs are required for text generation');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const response = await ai.run(model, inputs as Record<string, unknown>);
|
|
244
265
|
|
|
245
266
|
return {
|
|
246
267
|
success: true,
|
|
247
268
|
data: {
|
|
248
|
-
output: response.response
|
|
269
|
+
output: ((response as Record<string, unknown>).response as string | string[] | undefined) ||
|
|
270
|
+
((response as Record<string, unknown>).output as string | string[] | undefined) ||
|
|
271
|
+
((response as Record<string, unknown>).text as string | string[] | undefined),
|
|
249
272
|
},
|
|
250
273
|
model,
|
|
251
274
|
};
|
|
@@ -319,19 +342,23 @@ Generate the script:`;
|
|
|
319
342
|
inputs: WorkersAIInputs['image_generation']
|
|
320
343
|
): Promise<WorkersAIResponse> {
|
|
321
344
|
try {
|
|
322
|
-
|
|
323
|
-
const ai = this.env.bindings?.AI || this.env.AI;
|
|
345
|
+
const ai = this.getAI();
|
|
324
346
|
|
|
325
347
|
if (!ai) {
|
|
326
348
|
throw new Error('Workers AI binding not configured');
|
|
327
349
|
}
|
|
328
350
|
|
|
329
|
-
|
|
351
|
+
if (!inputs) {
|
|
352
|
+
throw new Error('Inputs are required for image generation');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const response = await ai.run(model, inputs as Record<string, unknown>);
|
|
330
356
|
|
|
331
357
|
return {
|
|
332
358
|
success: true,
|
|
333
359
|
data: {
|
|
334
|
-
image: response.image ||
|
|
360
|
+
image: (response as Record<string, unknown>).image as string | undefined ||
|
|
361
|
+
(response as Record<string, unknown>).output as string | undefined,
|
|
335
362
|
},
|
|
336
363
|
model,
|
|
337
364
|
};
|
|
@@ -349,20 +376,19 @@ Generate the script:`;
|
|
|
349
376
|
*/
|
|
350
377
|
async generateEmbedding(text: string): Promise<WorkersAIResponse> {
|
|
351
378
|
try {
|
|
352
|
-
|
|
353
|
-
const ai = this.env.bindings?.AI || this.env.AI;
|
|
379
|
+
const ai = this.getAI();
|
|
354
380
|
|
|
355
381
|
if (!ai) {
|
|
356
382
|
throw new Error('Workers AI binding not configured');
|
|
357
383
|
}
|
|
358
384
|
|
|
359
385
|
const model = '@cf/openai/clip-vit-base-patch32';
|
|
360
|
-
const response = await ai.run(model, { text })
|
|
386
|
+
const response = await ai.run(model, { text }) as Record<string, unknown>;
|
|
361
387
|
|
|
362
388
|
return {
|
|
363
389
|
success: true,
|
|
364
390
|
data: {
|
|
365
|
-
embedding: response.embedding || response.output,
|
|
391
|
+
embedding: response.embedding as number[] | undefined || response.output as number[] | undefined,
|
|
366
392
|
},
|
|
367
393
|
model,
|
|
368
394
|
};
|
|
@@ -384,8 +410,7 @@ Generate the script:`;
|
|
|
384
410
|
targetLang: string
|
|
385
411
|
): Promise<WorkersAIResponse> {
|
|
386
412
|
try {
|
|
387
|
-
|
|
388
|
-
const ai = this.env.bindings?.AI || this.env.AI;
|
|
413
|
+
const ai = this.getAI();
|
|
389
414
|
|
|
390
415
|
if (!ai) {
|
|
391
416
|
throw new Error('Workers AI binding not configured');
|
|
@@ -396,12 +421,14 @@ Generate the script:`;
|
|
|
396
421
|
text,
|
|
397
422
|
source_lang: sourceLang,
|
|
398
423
|
target_lang: targetLang,
|
|
399
|
-
})
|
|
424
|
+
}) as Record<string, unknown>;
|
|
400
425
|
|
|
401
426
|
return {
|
|
402
427
|
success: true,
|
|
403
428
|
data: {
|
|
404
|
-
output: response.translated_text
|
|
429
|
+
output: response.translated_text as string | string[] | undefined ||
|
|
430
|
+
response.output as string | string[] | undefined ||
|
|
431
|
+
response.text as string | string[] | undefined,
|
|
405
432
|
},
|
|
406
433
|
model: '@cf/meta/m2m100-1.2b',
|
|
407
434
|
};
|
|
@@ -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();
|