@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,437 @@
1
+ /**
2
+ * Cloudflare Workflows Service
3
+ * @description Service for orchestrating long-running, retryable operations
4
+ */
5
+
6
+ import type {
7
+ WorkflowDefinition,
8
+ WorkflowExecution,
9
+ WorkflowStep,
10
+ WorkflowInstanceState,
11
+ MediaProcessingWorkflow,
12
+ AIGenerationWorkflow,
13
+ BatchOperationWorkflow,
14
+ } from '../../domain/workflows.entity';
15
+
16
+ export interface WorkflowServiceConfig {
17
+ KV?: KVNamespace;
18
+ D1?: D1Database;
19
+ maxExecutionTime?: number; // seconds
20
+ defaultRetries?: number;
21
+ }
22
+
23
+ export class WorkflowService {
24
+ private kv?: KVNamespace;
25
+ private d1?: D1Database;
26
+ private maxExecutionTime: number;
27
+ private defaultRetries: number;
28
+
29
+ constructor(config: WorkflowServiceConfig = {}) {
30
+ this.kv = config.KV;
31
+ this.d1 = config.D1;
32
+ this.maxExecutionTime = config.maxExecutionTime || 300; // 5 minutes default
33
+ this.defaultRetries = config.defaultRetries || 3;
34
+ }
35
+
36
+ /**
37
+ * Create a new workflow definition
38
+ */
39
+ async createWorkflow(definition: WorkflowDefinition): Promise<void> {
40
+ const key = `workflow:${definition.id}`;
41
+ if (this.kv) {
42
+ await this.kv.put(key, JSON.stringify(definition));
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Get workflow definition
48
+ */
49
+ async getWorkflow(workflowId: string): Promise<WorkflowDefinition | null> {
50
+ if (this.kv) {
51
+ const data = await this.kv.get(`workflow:${workflowId}`);
52
+ return data ? JSON.parse(data) : null;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Start a workflow execution
59
+ */
60
+ async startExecution(
61
+ workflowId: string,
62
+ inputs: Record<string, unknown>
63
+ ): Promise<WorkflowExecution> {
64
+ const workflow = await this.getWorkflow(workflowId);
65
+ if (!workflow) {
66
+ throw new Error(`Workflow ${workflowId} not found`);
67
+ }
68
+
69
+ const execution: WorkflowExecution = {
70
+ id: this.generateId(),
71
+ workflowId,
72
+ status: 'pending',
73
+ completedSteps: [],
74
+ failedSteps: [],
75
+ inputs,
76
+ startedAt: Date.now(),
77
+ retryCount: 0,
78
+ };
79
+
80
+ await this.saveExecution(execution);
81
+ await this.executeWorkflow(execution, workflow);
82
+
83
+ return execution;
84
+ }
85
+
86
+ /**
87
+ * Execute workflow steps
88
+ */
89
+ private async executeWorkflow(
90
+ execution: WorkflowExecution,
91
+ workflow: WorkflowDefinition
92
+ ): Promise<void> {
93
+ execution.status = 'running';
94
+ await this.saveExecution(execution);
95
+
96
+ const results: Record<string, unknown> = {};
97
+ const stepStatus: Record<string, 'completed' | 'failed'> = {};
98
+
99
+ // Execute steps in order, respecting dependencies
100
+ for (const step of workflow.steps) {
101
+ // Check if dependencies are met
102
+ if (step.dependencies) {
103
+ const depsMet = step.dependencies.every(
104
+ (dep) => stepStatus[dep] === 'completed'
105
+ );
106
+ if (!depsMet) {
107
+ continue; // Skip for now, will retry later
108
+ }
109
+ }
110
+
111
+ try {
112
+ // Get step state for retries
113
+ const state = await this.getStepState(execution.id, step.id);
114
+ const stepInputs = state?.data || { ...results, ...step.inputs };
115
+
116
+ // Execute step
117
+ const stepResult = await this.executeStep(step, stepInputs);
118
+
119
+ // Store result
120
+ results[step.id] = stepResult;
121
+ stepStatus[step.id] = 'completed';
122
+ execution.completedSteps.push(step.id);
123
+
124
+ // Save step state for idempotency
125
+ await this.saveStepState(execution.id, step.id, stepResult);
126
+
127
+ } catch (error) {
128
+ stepStatus[step.id] = 'failed';
129
+ execution.failedSteps.push(step.id);
130
+ execution.error = error instanceof Error ? error.message : String(error);
131
+
132
+ // Check retry policy
133
+ const retryPolicy = step.retryPolicy || workflow.retryConfig;
134
+ if (retryPolicy && execution.retryCount < this.defaultRetries) {
135
+ execution.retryCount++;
136
+ execution.status = 'retrying';
137
+ await this.saveExecution(execution);
138
+
139
+ // Exponential backoff
140
+ const delay = Math.min(
141
+ retryPolicy.initialDelay * Math.pow(retryPolicy.backoffMultiplier, execution.retryCount),
142
+ retryPolicy.maxDelay
143
+ );
144
+ await this.sleep(delay);
145
+
146
+ // Retry this step
147
+ continue;
148
+ }
149
+
150
+ // Max retries exceeded, mark as failed
151
+ execution.status = 'failed';
152
+ execution.completedAt = Date.now();
153
+ await this.saveExecution(execution);
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ // All steps completed
159
+ execution.status = 'completed';
160
+ execution.outputs = results;
161
+ execution.completedAt = Date.now();
162
+ await this.saveExecution(execution);
163
+ }
164
+
165
+ /**
166
+ * Execute a single step
167
+ */
168
+ private async executeStep(
169
+ step: WorkflowStep,
170
+ inputs: Record<string, unknown>
171
+ ): Promise<unknown> {
172
+ // This would call the actual handler function
173
+ // For now, simulate execution
174
+ const handler = this.getHandler(step.handler);
175
+ if (!handler) {
176
+ throw new Error(`Handler ${step.handler} not found`);
177
+ }
178
+
179
+ return handler(inputs);
180
+ }
181
+
182
+ /**
183
+ * Resume a workflow from a specific step
184
+ */
185
+ async resumeExecution(
186
+ executionId: string,
187
+ fromStep?: string
188
+ ): Promise<WorkflowExecution> {
189
+ const execution = await this.getExecution(executionId);
190
+ if (!execution) {
191
+ throw new Error(`Execution ${executionId} not found`);
192
+ }
193
+
194
+ const workflow = await this.getWorkflow(execution.workflowId);
195
+ if (!workflow) {
196
+ throw new Error(`Workflow ${execution.workflowId} not found`);
197
+ }
198
+
199
+ // If fromStep is specified, reset to that step
200
+ if (fromStep) {
201
+ const stepIndex = workflow.steps.findIndex((s) => s.id === fromStep);
202
+ if (stepIndex === -1) {
203
+ throw new Error(`Step ${fromStep} not found`);
204
+ }
205
+
206
+ // Reset completed steps after the resume point
207
+ execution.completedSteps = workflow.steps
208
+ .slice(0, stepIndex)
209
+ .map((s) => s.id);
210
+ execution.failedSteps = [];
211
+ execution.status = 'pending';
212
+ }
213
+
214
+ await this.executeWorkflow(execution, workflow);
215
+ return execution;
216
+ }
217
+
218
+ /**
219
+ * Get execution status
220
+ */
221
+ async getExecution(executionId: string): Promise<WorkflowExecution | null> {
222
+ if (this.kv) {
223
+ const data = await this.kv.get(`execution:${executionId}`);
224
+ return data ? JSON.parse(data) : null;
225
+ }
226
+ return null;
227
+ }
228
+
229
+ /**
230
+ * List executions for a workflow
231
+ */
232
+ async listExecutions(workflowId: string): Promise<WorkflowExecution[]> {
233
+ // This would typically use D1 for proper querying
234
+ // For now, return empty array
235
+ return [];
236
+ }
237
+
238
+ /**
239
+ * Save execution state
240
+ */
241
+ private async saveExecution(execution: WorkflowExecution): Promise<void> {
242
+ if (this.kv) {
243
+ await this.kv.put(
244
+ `execution:${execution.id}`,
245
+ JSON.stringify(execution),
246
+ { expirationTtl: 86400 } // 24 hours
247
+ );
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Save step state for idempotency
253
+ */
254
+ private async saveStepState(
255
+ executionId: string,
256
+ stepId: string,
257
+ data: Record<string, unknown>
258
+ ): Promise<void> {
259
+ if (this.kv) {
260
+ const state: WorkflowInstanceState = {
261
+ executionId,
262
+ stepId,
263
+ data,
264
+ timestamp: Date.now(),
265
+ };
266
+ await this.kv.put(
267
+ `step:${executionId}:${stepId}`,
268
+ JSON.stringify(state)
269
+ );
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Get step state
275
+ */
276
+ private async getStepState(
277
+ executionId: string,
278
+ stepId: string
279
+ ): Promise<WorkflowInstanceState | null> {
280
+ if (this.kv) {
281
+ const data = await this.kv.get(`step:${executionId}:${stepId}`);
282
+ return data ? JSON.parse(data) : null;
283
+ }
284
+ return null;
285
+ }
286
+
287
+ /**
288
+ * Get handler function (mock implementation)
289
+ */
290
+ private getHandler(name: string): ((inputs: Record<string, unknown>) => Promise<unknown>) | null {
291
+ const handlers: Record<string, (inputs: Record<string, unknown>) => Promise<unknown>> = {
292
+ 'media-process': async (inputs) => ({ processed: true, urls: [] }),
293
+ 'ai-generate': async (inputs) => ({ result: 'generated text', tokens: 100 }),
294
+ 'batch-operation': async (inputs) => ({ successful: 10, failed: 0 }),
295
+ };
296
+ return handlers[name] || null;
297
+ }
298
+
299
+ private generateId(): string {
300
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
301
+ }
302
+
303
+ private sleep(ms: number): Promise<void> {
304
+ return new Promise((resolve) => setTimeout(resolve, ms));
305
+ }
306
+ }
307
+
308
+ // Predefined workflow templates
309
+ export const WORKFLOW_TEMPLATES: Record<string, Partial<WorkflowDefinition>> = {
310
+ 'media-processing': {
311
+ name: 'Media Processing Workflow',
312
+ description: 'Process media files with various operations',
313
+ version: '1.0.0',
314
+ steps: [
315
+ {
316
+ id: 'download',
317
+ name: 'Download Media',
318
+ handler: 'media-download',
319
+ },
320
+ {
321
+ id: 'transcode',
322
+ name: 'Transcode Media',
323
+ handler: 'media-transcode',
324
+ dependencies: ['download'],
325
+ retryPolicy: {
326
+ maxAttempts: 3,
327
+ backoffMultiplier: 2,
328
+ initialDelay: 1000,
329
+ maxDelay: 10000,
330
+ },
331
+ },
332
+ {
333
+ id: 'optimize',
334
+ name: 'Optimize Media',
335
+ handler: 'media-optimize',
336
+ dependencies: ['transcode'],
337
+ },
338
+ {
339
+ id: 'upload',
340
+ name: 'Upload to R2',
341
+ handler: 'r2-upload',
342
+ dependencies: ['optimize'],
343
+ },
344
+ ],
345
+ },
346
+
347
+ 'ai-generation': {
348
+ name: 'AI Content Generation',
349
+ description: 'Generate content using AI with emotion control',
350
+ version: '1.0.0',
351
+ steps: [
352
+ {
353
+ id: 'generate-script',
354
+ name: 'Generate Script with Workers AI',
355
+ handler: 'workers-ai-generate',
356
+ timeout: 30,
357
+ },
358
+ {
359
+ id: 'validate-content',
360
+ name: 'Validate Generated Content',
361
+ handler: 'content-validate',
362
+ dependencies: ['generate-script'],
363
+ },
364
+ {
365
+ id: 'save-to-database',
366
+ name: 'Save to Database',
367
+ handler: 'd1-insert',
368
+ dependencies: ['validate-content'],
369
+ },
370
+ ],
371
+ },
372
+
373
+ 'batch-operations': {
374
+ name: 'Batch Operations',
375
+ description: 'Execute operations in batches with parallelism',
376
+ version: '1.0.0',
377
+ steps: [
378
+ {
379
+ id: 'fetch-items',
380
+ name: 'Fetch Items to Process',
381
+ handler: 'batch-fetch',
382
+ },
383
+ {
384
+ id: 'process-batches',
385
+ name: 'Process in Batches',
386
+ handler: 'batch-process',
387
+ dependencies: ['fetch-items'],
388
+ },
389
+ {
390
+ id: 'aggregate-results',
391
+ name: 'Aggregate Results',
392
+ handler: 'batch-aggregate',
393
+ dependencies: ['process-batches'],
394
+ },
395
+ ],
396
+ },
397
+
398
+ // Voice cloning inspired workflow
399
+ 'voice-content-creation': {
400
+ name: 'Voice Content Creation',
401
+ description: 'Create AI-generated audio content with cloned voice',
402
+ version: '1.0.0',
403
+ steps: [
404
+ {
405
+ id: 'clone-voice',
406
+ name: 'Clone Voice from Sample',
407
+ handler: 'fishaudio-clone',
408
+ timeout: 60,
409
+ retryPolicy: {
410
+ maxAttempts: 3,
411
+ backoffMultiplier: 2,
412
+ initialDelay: 2000,
413
+ maxDelay: 15000,
414
+ },
415
+ },
416
+ {
417
+ id: 'generate-script',
418
+ name: 'Generate Script with Emotion',
419
+ handler: 'workers-ai-script',
420
+ timeout: 30,
421
+ },
422
+ {
423
+ id: 'generate-tts',
424
+ name: 'Generate TTS with Cloned Voice',
425
+ handler: 'fishaudio-tts',
426
+ dependencies: ['clone-voice', 'generate-script'],
427
+ timeout: 60,
428
+ },
429
+ {
430
+ id: 'combine-audio',
431
+ name: 'Combine Audio Tracks',
432
+ handler: 'audio-combine',
433
+ dependencies: ['generate-tts'],
434
+ },
435
+ ],
436
+ },
437
+ };