@umituz/web-cloudflare 1.0.1

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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +621 -0
  3. package/package.json +87 -0
  4. package/src/config/patterns.ts +469 -0
  5. package/src/config/types.ts +648 -0
  6. package/src/domain/entities/analytics.entity.ts +47 -0
  7. package/src/domain/entities/d1.entity.ts +37 -0
  8. package/src/domain/entities/image.entity.ts +48 -0
  9. package/src/domain/entities/index.ts +11 -0
  10. package/src/domain/entities/kv.entity.ts +34 -0
  11. package/src/domain/entities/r2.entity.ts +55 -0
  12. package/src/domain/entities/worker.entity.ts +35 -0
  13. package/src/domain/index.ts +7 -0
  14. package/src/domain/interfaces/index.ts +6 -0
  15. package/src/domain/interfaces/services.interface.ts +82 -0
  16. package/src/index.ts +53 -0
  17. package/src/infrastructure/constants/index.ts +13 -0
  18. package/src/infrastructure/domain/ai-gateway.entity.ts +169 -0
  19. package/src/infrastructure/domain/workflows.entity.ts +108 -0
  20. package/src/infrastructure/middleware/index.ts +405 -0
  21. package/src/infrastructure/router/index.ts +549 -0
  22. package/src/infrastructure/services/ai-gateway/index.ts +416 -0
  23. package/src/infrastructure/services/analytics/analytics.service.ts +189 -0
  24. package/src/infrastructure/services/analytics/index.ts +7 -0
  25. package/src/infrastructure/services/d1/d1.service.ts +191 -0
  26. package/src/infrastructure/services/d1/index.ts +7 -0
  27. package/src/infrastructure/services/images/images.service.ts +227 -0
  28. package/src/infrastructure/services/images/index.ts +7 -0
  29. package/src/infrastructure/services/kv/index.ts +7 -0
  30. package/src/infrastructure/services/kv/kv.service.ts +116 -0
  31. package/src/infrastructure/services/r2/index.ts +7 -0
  32. package/src/infrastructure/services/r2/r2.service.ts +164 -0
  33. package/src/infrastructure/services/workers/index.ts +7 -0
  34. package/src/infrastructure/services/workers/workers.service.ts +164 -0
  35. package/src/infrastructure/services/workflows/index.ts +437 -0
  36. package/src/infrastructure/utils/helpers.ts +732 -0
  37. package/src/infrastructure/utils/index.ts +6 -0
  38. package/src/infrastructure/utils/utils.util.ts +150 -0
  39. package/src/presentation/hooks/cloudflare.hooks.ts +314 -0
  40. package/src/presentation/hooks/index.ts +6 -0
  41. package/src/worker.example.ts +41 -0
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Cloudflare AI Gateway Service
3
+ * @description AI Gateway for routing, caching, and analytics
4
+ */
5
+
6
+ import type {
7
+ AIGatewayConfig,
8
+ AIRequest,
9
+ AIResponse,
10
+ AIProvider,
11
+ AIAnalytics,
12
+ } from '../../domain/ai-gateway.entity';
13
+
14
+ export class AIGatewayService {
15
+ private config: AIGatewayConfig;
16
+ private kv?: KVNamespace;
17
+ private analytics: Map<string, number>;
18
+
19
+ constructor(config: AIGatewayConfig, KV?: KVNamespace) {
20
+ this.config = config;
21
+ this.kv = KV;
22
+ this.analytics = new Map();
23
+ }
24
+
25
+ /**
26
+ * Route AI request to appropriate provider
27
+ */
28
+ async route(request: AIRequest): Promise<AIResponse> {
29
+ const startTime = Date.now();
30
+
31
+ // Check cache first
32
+ if (this.config.cacheEnabled && request.cacheKey) {
33
+ const cached = await this.getFromCache(request.cacheKey);
34
+ if (cached) {
35
+ return {
36
+ ...cached,
37
+ cached: true,
38
+ timestamp: Date.now(),
39
+ };
40
+ }
41
+ }
42
+
43
+ // Select provider (load balancing or fallback)
44
+ const provider = this.selectProvider(request.provider);
45
+ if (!provider) {
46
+ throw new Error(`Provider ${request.provider} not found`);
47
+ }
48
+
49
+ try {
50
+ // Make request to provider
51
+ const response = await this.makeRequest(provider, request);
52
+
53
+ // Cache response
54
+ if (this.config.cacheEnabled && request.cacheKey) {
55
+ await this.saveToCache(request.cacheKey, response);
56
+ }
57
+
58
+ // Track analytics
59
+ if (this.config.analytics) {
60
+ this.trackAnalytics(provider, response, Date.now() - startTime);
61
+ }
62
+
63
+ return response;
64
+
65
+ } catch (error) {
66
+ // Try fallback provider
67
+ if (provider.fallbackProvider) {
68
+ const fallback = this.config.providers.find(
69
+ (p) => p.id === provider.fallbackProvider
70
+ );
71
+ if (fallback) {
72
+ return this.makeRequest(fallback, request);
73
+ }
74
+ }
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Select provider based on weight or specific provider
81
+ */
82
+ private selectProvider(providerId?: string): AIProvider | null {
83
+ if (providerId) {
84
+ return this.config.providers.find((p) => p.id === providerId) || null;
85
+ }
86
+
87
+ // Load balancing based on weights
88
+ const totalWeight = this.config.providers.reduce(
89
+ (sum, p) => sum + (p.weight || 1),
90
+ 0
91
+ );
92
+ let random = Math.random() * totalWeight;
93
+
94
+ for (const provider of this.config.providers) {
95
+ random -= provider.weight || 1;
96
+ if (random <= 0) {
97
+ return provider;
98
+ }
99
+ }
100
+
101
+ return this.config.providers[0] || null;
102
+ }
103
+
104
+ /**
105
+ * Make request to AI provider
106
+ */
107
+ private async makeRequest(
108
+ provider: AIProvider,
109
+ request: AIRequest
110
+ ): Promise<AIResponse> {
111
+ const url = `${provider.baseURL}/${request.model}`;
112
+
113
+ const response = await fetch(url, {
114
+ method: 'POST',
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ 'Authorization': `Bearer ${provider.apiKey}`,
118
+ },
119
+ body: JSON.stringify({
120
+ prompt: request.prompt,
121
+ ...request.parameters,
122
+ stream: request.stream,
123
+ }),
124
+ });
125
+
126
+ if (!response.ok) {
127
+ throw new Error(`Provider error: ${response.statusText}`);
128
+ }
129
+
130
+ const data = await response.json();
131
+
132
+ return {
133
+ id: data.id || this.generateId(),
134
+ provider: provider.id,
135
+ model: request.model,
136
+ content: data.content || data.text || data.output,
137
+ usage: data.usage || {
138
+ promptTokens: 0,
139
+ completionTokens: 0,
140
+ totalTokens: 0,
141
+ },
142
+ cached: false,
143
+ timestamp: Date.now(),
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Get response from cache
149
+ */
150
+ private async getFromCache(key: string): Promise<AIResponse | null> {
151
+ if (this.kv) {
152
+ const data = await this.kv.get(`ai_cache:${key}`);
153
+ return data ? JSON.parse(data) : null;
154
+ }
155
+ return null;
156
+ }
157
+
158
+ /**
159
+ * Save response to cache
160
+ */
161
+ private async saveToCache(key: string, response: AIResponse): Promise<void> {
162
+ if (this.kv && this.config.cacheTTL) {
163
+ await this.kv.put(
164
+ `ai_cache:${key}`,
165
+ JSON.stringify(response),
166
+ { expirationTtl: this.config.cacheTTL }
167
+ );
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Track analytics
173
+ */
174
+ private trackAnalytics(
175
+ provider: AIProvider,
176
+ response: AIResponse,
177
+ duration: number
178
+ ): void {
179
+ const key = `provider:${provider.id}`;
180
+ const currentCount = this.analytics.get(key) || 0;
181
+ this.analytics.set(key, currentCount + 1);
182
+ }
183
+
184
+ /**
185
+ * Get analytics
186
+ */
187
+ async getAnalytics(): Promise<AIAnalytics> {
188
+ return {
189
+ totalRequests: Array.from(this.analytics.values()).reduce((a, b) => a + b, 0),
190
+ totalTokens: 0,
191
+ cacheHitRate: 0,
192
+ averageResponseTime: 0,
193
+ providerUsage: Object.fromEntries(this.analytics),
194
+ errorRate: 0,
195
+ };
196
+ }
197
+
198
+ private generateId(): string {
199
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Workers AI Service
205
+ * @description Direct integration with Cloudflare Workers AI
206
+ */
207
+
208
+ import type {
209
+ WorkersAIRequest,
210
+ WorkersAIResponse,
211
+ WorkersAIInputs,
212
+ ScriptGenerationRequest,
213
+ EmotionControl,
214
+ } from '../../domain/ai-gateway.entity';
215
+
216
+ export class WorkersAIService {
217
+ private env: {
218
+ AI?: AiTextGeneration;
219
+ bindings?: {
220
+ AI?: Ai;
221
+ };
222
+ };
223
+
224
+ constructor(env: { AI?: any; bindings?: any }) {
225
+ this.env = env;
226
+ }
227
+
228
+ /**
229
+ * Run text generation model
230
+ */
231
+ async runTextGeneration(
232
+ model: string,
233
+ inputs: WorkersAIInputs['text_generation']
234
+ ): Promise<WorkersAIResponse> {
235
+ try {
236
+ // @ts-ignore - Workers AI runtime binding
237
+ const ai = this.env.bindings?.AI || this.env.AI;
238
+
239
+ if (!ai) {
240
+ throw new Error('Workers AI binding not configured');
241
+ }
242
+
243
+ const response = await ai.run(model, inputs);
244
+
245
+ return {
246
+ success: true,
247
+ data: {
248
+ output: response.response || response.output || response.text,
249
+ },
250
+ model,
251
+ };
252
+ } catch (error) {
253
+ return {
254
+ success: false,
255
+ error: error instanceof Error ? error.message : String(error),
256
+ model,
257
+ };
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Generate script with emotion control (from voice cloning app)
263
+ */
264
+ async generateScript(request: ScriptGenerationRequest): Promise<WorkersAIResponse> {
265
+ const { topic, emotion, duration, style = 'casual', keywords = [] } = request;
266
+
267
+ // Build emotion-enhanced prompt
268
+ const emotionPrompt = this.buildEmotionPrompt(emotion);
269
+ const keywordText = keywords.length > 0 ? `\nKeywords: ${keywords.join(', ')}` : '';
270
+ const durationEstimate = Math.ceil(duration / 150); // ~150 words per minute
271
+
272
+ const prompt = `Write a ${style} ${durationEstimate}-minute script about: ${topic}
273
+
274
+ ${emotionPrompt}
275
+
276
+ Requirements:
277
+ - Duration: approximately ${duration} seconds
278
+ - Tone: ${style}
279
+ - Target: Engaging, clear, and natural
280
+ - Format: Conversational speech${keywordText}
281
+
282
+ Generate the script:`;
283
+
284
+ return this.runTextGeneration('@cf/meta/llama-3.1-8b-instruct', {
285
+ prompt,
286
+ max_tokens: durationEstimate * 50,
287
+ temperature: 0.8 + (emotion.intensity * 0.2),
288
+ top_p: 0.9,
289
+ });
290
+ }
291
+
292
+ /**
293
+ * Build emotion prompt for script generation
294
+ */
295
+ private buildEmotionPrompt(emotion: EmotionControl): string {
296
+ const intensityText =
297
+ emotion.intensity > 0.7 ? 'strongly ' :
298
+ emotion.intensity > 0.4 ? 'moderately ' :
299
+ 'subtly ';
300
+
301
+ const emotionInstructions: Record<EmotionControl['emotion'], string> = {
302
+ neutral: 'Maintain a balanced, professional tone throughout',
303
+ happy: `Inject ${intensityText}positive, energetic language and enthusiastic expressions`,
304
+ sad: `Use ${intensityText}reflective, thoughtful language with a gentle tone`,
305
+ angry: `Incorporate ${intensityText}passionate, firm language with strong conviction`,
306
+ excited: `Use ${intensityText}dynamic, upbeat language with high energy expressions`,
307
+ calm: `Maintain ${intensityText}serene, peaceful language with a steady rhythm`,
308
+ surprised: `Include ${intensityText}wonder, discovery, and unexpected insights`,
309
+ };
310
+
311
+ return emotionInstructions[emotion.emotion] || emotionInstructions.neutral;
312
+ }
313
+
314
+ /**
315
+ * Run image generation model
316
+ */
317
+ async runImageGeneration(
318
+ model: string,
319
+ inputs: WorkersAIInputs['image_generation']
320
+ ): Promise<WorkersAIResponse> {
321
+ try {
322
+ // @ts-ignore - Workers AI runtime binding
323
+ const ai = this.env.bindings?.AI || this.env.AI;
324
+
325
+ if (!ai) {
326
+ throw new Error('Workers AI binding not configured');
327
+ }
328
+
329
+ const response = await ai.run(model, inputs);
330
+
331
+ return {
332
+ success: true,
333
+ data: {
334
+ image: response.image || response.output,
335
+ },
336
+ model,
337
+ };
338
+ } catch (error) {
339
+ return {
340
+ success: false,
341
+ error: error instanceof Error ? error.message : String(error),
342
+ model,
343
+ };
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Generate embedding
349
+ */
350
+ async generateEmbedding(text: string): Promise<WorkersAIResponse> {
351
+ try {
352
+ // @ts-ignore - Workers AI runtime binding
353
+ const ai = this.env.bindings?.AI || this.env.AI;
354
+
355
+ if (!ai) {
356
+ throw new Error('Workers AI binding not configured');
357
+ }
358
+
359
+ const model = '@cf/openai/clip-vit-base-patch32';
360
+ const response = await ai.run(model, { text });
361
+
362
+ return {
363
+ success: true,
364
+ data: {
365
+ embedding: response.embedding || response.output,
366
+ },
367
+ model,
368
+ };
369
+ } catch (error) {
370
+ return {
371
+ success: false,
372
+ error: error instanceof Error ? error.message : String(error),
373
+ model: '@cf/openai/clip-vit-base-patch32',
374
+ };
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Translate text
380
+ */
381
+ async translate(
382
+ text: string,
383
+ sourceLang: string,
384
+ targetLang: string
385
+ ): Promise<WorkersAIResponse> {
386
+ try {
387
+ // @ts-ignore - Workers AI runtime binding
388
+ const ai = this.env.bindings?.AI || this.env.AI;
389
+
390
+ if (!ai) {
391
+ throw new Error('Workers AI binding not configured');
392
+ }
393
+
394
+ // Note: Translation model might vary
395
+ const response = await ai.run('@cf/meta/m2m100-1.2b', {
396
+ text,
397
+ source_lang: sourceLang,
398
+ target_lang: targetLang,
399
+ });
400
+
401
+ return {
402
+ success: true,
403
+ data: {
404
+ output: response.translated_text || response.output || response.text,
405
+ },
406
+ model: '@cf/meta/m2m100-1.2b',
407
+ };
408
+ } catch (error) {
409
+ return {
410
+ success: false,
411
+ error: error instanceof Error ? error.message : String(error),
412
+ model: '@cf/meta/m2m100-1.2b',
413
+ };
414
+ }
415
+ }
416
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Analytics Service
3
+ * @description Cloudflare Web Analytics operations
4
+ */
5
+
6
+ import type { AnalyticsEvent, AnalyticsPageviewEvent, AnalyticsCustomEvent, AnalyticsData } from "../../../domain/entities/analytics.entity";
7
+ import type { IAnalyticsService } from "../../../domain/interfaces/services.interface";
8
+
9
+ export interface AnalyticsClientOptions {
10
+ readonly siteId: string;
11
+ readonly scriptUrl?: string;
12
+ }
13
+
14
+ class AnalyticsService implements IAnalyticsService {
15
+ private siteId: string | null = null;
16
+ private scriptUrl: string | null = null;
17
+ private eventQueue: AnalyticsEvent[] = [];
18
+
19
+ initialize(options: AnalyticsClientOptions): void {
20
+ this.siteId = options.siteId;
21
+ this.scriptUrl = options.scriptUrl ?? null;
22
+ }
23
+
24
+ private ensureInitialized(): void {
25
+ if (!this.siteId) {
26
+ throw new Error("AnalyticsService not initialized");
27
+ }
28
+ }
29
+
30
+ async trackEvent(event: AnalyticsEvent): Promise<void> {
31
+ this.ensureInitialized();
32
+
33
+ this.eventQueue.push(event);
34
+
35
+ // In a browser environment, send to Cloudflare Analytics
36
+ if (typeof window !== "undefined" && (window as any)._cfAnalytics) {
37
+ (window as any)._cfAnalytics.track(event);
38
+ }
39
+ }
40
+
41
+ async trackPageview(url: string, title: string, referrer?: string): Promise<void> {
42
+ const event: AnalyticsPageviewEvent = {
43
+ timestamp: Date.now(),
44
+ url,
45
+ eventType: "pageview",
46
+ title,
47
+ referrer,
48
+ };
49
+
50
+ await this.trackEvent(event);
51
+ }
52
+
53
+ async trackCustom(eventName: string, data?: Record<string, unknown>): Promise<void> {
54
+ if (typeof window === "undefined") return;
55
+
56
+ const event: AnalyticsCustomEvent = {
57
+ timestamp: Date.now(),
58
+ url: window.location.href,
59
+ eventType: "custom",
60
+ eventName,
61
+ eventData: data,
62
+ };
63
+
64
+ await this.trackEvent(event);
65
+ }
66
+
67
+ async trackOutboundLink(url: string, linkType?: string): Promise<void> {
68
+ if (typeof window === "undefined") return;
69
+
70
+ const event: AnalyticsCustomEvent = {
71
+ timestamp: Date.now(),
72
+ url: window.location.href,
73
+ eventType: "custom",
74
+ eventName: "outbound-link",
75
+ eventData: {
76
+ targetUrl: url,
77
+ linkType,
78
+ },
79
+ };
80
+
81
+ await this.trackEvent(event);
82
+ }
83
+
84
+ async trackTiming(name: string, value: number, label?: string): Promise<void> {
85
+ if (typeof window === "undefined") return;
86
+
87
+ const event: AnalyticsEvent = {
88
+ timestamp: Date.now(),
89
+ url: window.location.href,
90
+ eventType: "timing",
91
+ eventData: {
92
+ name,
93
+ value,
94
+ label,
95
+ },
96
+ };
97
+
98
+ await this.trackEvent(event);
99
+ }
100
+
101
+ async getAnalytics(): Promise<AnalyticsData> {
102
+ this.ensureInitialized();
103
+
104
+ // In a real implementation, this would fetch from Cloudflare Analytics API
105
+ // For now, return queued events
106
+
107
+ return {
108
+ siteId: this.siteId!,
109
+ events: this.eventQueue,
110
+ metrics: {
111
+ pageviews: this.eventQueue.filter((e) => e.eventType === "pageview").length,
112
+ uniqueVisitors: new Set(this.eventQueue.map((e) => e.url)).size,
113
+ },
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Get analytics script tag
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 {
134
+ this.eventQueue = [];
135
+ }
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
+ }
188
+
189
+ export const analyticsService = new AnalyticsService();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Cloudflare Analytics Service
3
+ * Subpath: @umituz/web-cloudflare/analytics
4
+ */
5
+
6
+ export { AnalyticsService, analyticsService } from "./analytics.service";
7
+ export type { AnalyticsClientOptions } from "./analytics.service";