@umituz/web-cloudflare 1.4.5 → 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.
Files changed (29) hide show
  1. package/package.json +2 -2
  2. package/src/config/patterns.ts +2 -2
  3. package/src/domains/ai-gateway/services/index.ts +18 -8
  4. package/src/domains/analytics/services/analytics.service.ts +16 -92
  5. package/src/domains/analytics/types/service.interface.ts +1 -1
  6. package/src/domains/d1/services/d1.service.ts +19 -22
  7. package/src/domains/images/services/images.service.ts +58 -21
  8. package/src/domains/kv/services/kv.service.ts +10 -5
  9. package/src/domains/middleware/entities/index.ts +106 -0
  10. package/src/domains/middleware/index.ts +14 -0
  11. package/src/{infrastructure/middleware/auth.ts → domains/middleware/services/auth.service.ts} +7 -11
  12. package/src/{infrastructure/middleware/cache.ts → domains/middleware/services/cache.service.ts} +13 -11
  13. package/src/{infrastructure/middleware/cors.ts → domains/middleware/services/cors.service.ts} +9 -14
  14. package/src/domains/middleware/services/index.ts +9 -0
  15. package/src/{infrastructure/middleware/rate-limit.ts → domains/middleware/services/rate-limit.service.ts} +8 -16
  16. package/src/domains/middleware/types/index.ts +5 -0
  17. package/src/domains/middleware/types/service.interface.ts +128 -0
  18. package/src/domains/r2/services/r2.service.ts +21 -13
  19. package/src/domains/workers/entities/index.ts +17 -1
  20. package/src/domains/workers/examples/worker.example.ts +10 -8
  21. package/src/domains/workers/services/workers.service.ts +6 -4
  22. package/src/domains/workflows/entities/index.ts +14 -1
  23. package/src/domains/workflows/services/workflows.service.ts +43 -10
  24. package/src/domains/wrangler/services/wrangler.service.ts +150 -443
  25. package/src/index.ts +45 -13
  26. package/src/infrastructure/middleware/index.ts +23 -8
  27. package/src/infrastructure/utils/helpers.ts +29 -7
  28. package/src/presentation/hooks/cloudflare.hooks.ts +7 -309
  29. package/src/presentation/hooks/index.ts +4 -1
@@ -1,25 +1,27 @@
1
1
  /**
2
- * Cache Middleware
2
+ * Cache Service
3
3
  * @description Caching middleware for Cloudflare Workers
4
4
  */
5
5
 
6
- export interface CacheConfig {
7
- enabled: boolean;
8
- defaultTTL: number;
9
- paths?: Record<string, number>;
10
- prefix?: string;
11
- bypassPaths?: string[];
12
- respectHeaders?: boolean;
6
+ import type { MiddlewareCacheConfig } from '../entities';
7
+
8
+ // Type aliases for backwards compatibility
9
+ export type { MiddlewareCacheConfig };
10
+ export type CacheConfig = MiddlewareCacheConfig;
11
+
12
+ interface CacheEntry {
13
+ response: Response;
14
+ expires: number;
13
15
  }
14
16
 
15
- const cacheStore = new Map<string, { response: Response; expires: number }>();
17
+ const cacheStore = new Map<string, CacheEntry>();
16
18
 
17
19
  /**
18
20
  * Cache middleware
19
21
  */
20
22
  export async function cache(
21
23
  request: Request,
22
- config: CacheConfig
24
+ config: MiddlewareCacheConfig
23
25
  ): Promise<Response | null> {
24
26
  if (!config.enabled) {
25
27
  return null;
@@ -48,7 +50,7 @@ export async function cache(
48
50
  export function setCache(
49
51
  request: Request,
50
52
  response: Response,
51
- config: CacheConfig
53
+ config: MiddlewareCacheConfig
52
54
  ): void {
53
55
  if (!config.enabled) {
54
56
  return;
@@ -1,17 +1,12 @@
1
1
  /**
2
- * CORS Middleware
2
+ * CORS Service
3
3
  * @description Cross-Origin Resource Sharing middleware for Cloudflare Workers
4
4
  */
5
5
 
6
- export interface CORSConfig {
7
- enabled: boolean;
8
- allowedOrigins: string[];
9
- allowedMethods: string[];
10
- allowedHeaders: string[];
11
- exposedHeaders?: string[];
12
- allowCredentials?: boolean;
13
- maxAge?: number;
14
- }
6
+ import type { MiddlewareCORSConfig } from '../entities';
7
+
8
+ // Type alias for backwards compatibility
9
+ export type CORSConfig = MiddlewareCORSConfig;
15
10
 
16
11
  /**
17
12
  * Add CORS headers to response
@@ -19,7 +14,7 @@ export interface CORSConfig {
19
14
  export function addCorsHeaders(
20
15
  request: Request,
21
16
  response: Response,
22
- config: CORSConfig
17
+ config: MiddlewareCORSConfig
23
18
  ): Response {
24
19
  if (!config.enabled) {
25
20
  return response;
@@ -31,7 +26,7 @@ export function addCorsHeaders(
31
26
  // Check if origin is allowed
32
27
  const allowedOrigin = config.allowedOrigins.includes('*')
33
28
  ? '*'
34
- : config.allowedOrigins.includes(origin || '')
29
+ : origin && config.allowedOrigins.includes(origin)
35
30
  ? origin
36
31
  : config.allowedOrigins[0];
37
32
 
@@ -63,7 +58,7 @@ export function addCorsHeaders(
63
58
  */
64
59
  export async function cors(
65
60
  request: Request,
66
- config: CORSConfig
61
+ config: MiddlewareCORSConfig
67
62
  ): Promise<Response | null> {
68
63
  if (!config.enabled) {
69
64
  return null;
@@ -76,7 +71,7 @@ export async function cors(
76
71
 
77
72
  const allowedOrigin = config.allowedOrigins.includes('*')
78
73
  ? '*'
79
- : config.allowedOrigins.includes(origin || '')
74
+ : origin && config.allowedOrigins.includes(origin)
80
75
  ? origin
81
76
  : config.allowedOrigins[0];
82
77
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Middleware Services
3
+ * @description Middleware service implementations
4
+ */
5
+
6
+ export { cors, addCorsHeaders } from './cors.service';
7
+ export { cache, setCache, invalidateCache } from './cache.service';
8
+ export { checkRateLimit } from './rate-limit.service';
9
+ export { requireAuth, addUserContext } from './auth.service';
@@ -1,21 +1,13 @@
1
1
  /**
2
- * Rate Limit Middleware
2
+ * Rate Limit Service
3
3
  * @description Rate limiting middleware for Cloudflare Workers
4
4
  */
5
5
 
6
- export interface RateLimitConfig {
7
- enabled: boolean;
8
- maxRequests: number;
9
- window: number;
10
- by?: 'ip' | 'user' | 'both';
11
- customKeys?: string[];
12
- whitelist?: string[];
13
- response?: {
14
- status: number;
15
- message: string;
16
- retryAfter?: number;
17
- };
18
- }
6
+ import type { MiddlewareRateLimitConfig } from '../entities';
7
+
8
+ // Type aliases for backwards compatibility
9
+ export type { MiddlewareRateLimitConfig };
10
+ export type RateLimitConfig = MiddlewareRateLimitConfig;
19
11
 
20
12
  interface RateLimitEntry {
21
13
  count: number;
@@ -27,7 +19,7 @@ const rateLimitStore = new Map<string, RateLimitEntry>();
27
19
  /**
28
20
  * Get rate limit key
29
21
  */
30
- function getRateLimitKey(request: Request, config: RateLimitConfig): string {
22
+ function getRateLimitKey(request: Request, config: MiddlewareRateLimitConfig): string {
31
23
  const parts: string[] = [];
32
24
 
33
25
  if (config.by === 'ip' || config.by === 'both') {
@@ -55,7 +47,7 @@ function getRateLimitKey(request: Request, config: RateLimitConfig): string {
55
47
  */
56
48
  export async function checkRateLimit(
57
49
  request: Request,
58
- config: RateLimitConfig
50
+ config: MiddlewareRateLimitConfig
59
51
  ): Promise<Response | null> {
60
52
  if (!config.enabled) {
61
53
  return null;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Middleware Domain Types
3
+ */
4
+
5
+ export * from './service.interface';
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Middleware Service Interface
3
+ * @description Defines the contract for middleware operations
4
+ */
5
+
6
+ import type {
7
+ MiddlewareCORSConfig,
8
+ MiddlewareCacheConfig,
9
+ MiddlewareRateLimitConfig,
10
+ MiddlewareAuthConfig,
11
+ SecurityHeadersConfig,
12
+ IPFilterConfig,
13
+ LogConfig,
14
+ HealthCheckConfig,
15
+ ErrorHandlerConfig,
16
+ } from '../entities';
17
+
18
+ // Type aliases for backwards compatibility
19
+ export type CORSConfig = MiddlewareCORSConfig;
20
+ export type CacheConfig = MiddlewareCacheConfig;
21
+ export type RateLimitConfig = MiddlewareRateLimitConfig;
22
+ export type AuthConfig = MiddlewareAuthConfig;
23
+
24
+ export interface IMiddlewareService {
25
+ /**
26
+ * CORS middleware
27
+ */
28
+ cors(request: Request, config: MiddlewareCORSConfig): Promise<Response | null>;
29
+ addCorsHeaders(request: Request, response: Response, config: MiddlewareCORSConfig): Response;
30
+
31
+ /**
32
+ * Cache middleware
33
+ */
34
+ cache(request: Request, config: MiddlewareCacheConfig): Promise<Response | null>;
35
+ setCache(request: Request, response: Response, config: MiddlewareCacheConfig): void;
36
+ invalidateCache(pattern?: string): void;
37
+
38
+ /**
39
+ * Rate limit middleware
40
+ */
41
+ checkRateLimit(request: Request, config: MiddlewareRateLimitConfig): Promise<Response | null>;
42
+
43
+ /**
44
+ * Authentication middleware
45
+ */
46
+ requireAuth(request: Request, config: MiddlewareAuthConfig): Promise<Response | null>;
47
+ addUserContext(request: Request, user: {
48
+ id: string;
49
+ [key: string]: unknown;
50
+ }): Request;
51
+
52
+ /**
53
+ * Security headers
54
+ */
55
+ addSecurityHeaders(response: Response, config: SecurityHeadersConfig): Response;
56
+
57
+ /**
58
+ * Bot detection
59
+ */
60
+ detectBot(request: Request): Promise<{
61
+ isBot: boolean;
62
+ botType?: string;
63
+ }>;
64
+
65
+ /**
66
+ * Request logging
67
+ */
68
+ logRequest(request: Request, config: LogConfig): Promise<void>;
69
+
70
+ /**
71
+ * Response time tracking
72
+ */
73
+ trackResponseTime(handler: () => Promise<Response>): Promise<{
74
+ response: Response;
75
+ duration: number;
76
+ }>;
77
+
78
+ /**
79
+ * IP filter
80
+ */
81
+ checkIPFilter(request: Request, config: IPFilterConfig): Response | null;
82
+
83
+ /**
84
+ * Method override
85
+ */
86
+ methodOverride(request: Request): Request;
87
+
88
+ /**
89
+ * Request ID
90
+ */
91
+ addRequestID(request: Request): string;
92
+
93
+ /**
94
+ * Health check
95
+ */
96
+ healthCheck(
97
+ env: Record<string, unknown>,
98
+ config?: HealthCheckConfig
99
+ ): Promise<Response>;
100
+
101
+ /**
102
+ * Error handling
103
+ */
104
+ handleMiddlewareError(error: Error, config: ErrorHandlerConfig): Response;
105
+
106
+ /**
107
+ * Conditional middleware
108
+ */
109
+ conditionalChainMiddleware(
110
+ condition: (request: Request) => boolean,
111
+ middleware: (request: Request) => Response | null
112
+ ): (request: Request) => Response | null;
113
+
114
+ /**
115
+ * Chain middleware
116
+ */
117
+ chainMiddleware(
118
+ ...middlewares: Array<(request: Request) => Response | null>
119
+ ): (request: Request) => Response | null;
120
+
121
+ /**
122
+ * Chain async middleware
123
+ */
124
+ chainAsyncMiddleware(
125
+ request: Request,
126
+ ...middlewares: Array<(request: Request) => Promise<Response | null>>
127
+ ): Promise<Response | null>;
128
+ }
@@ -3,7 +3,7 @@
3
3
  * @description Cloudflare R2 object storage operations
4
4
  */
5
5
 
6
- import type { R2Object, R2ListOptions, R2ListResult, R2PutOptions, R2PresignedURL } from "../entities";
6
+ import type { R2Object, R2ListOptions, R2ListResult, R2PutOptions, R2PresignedURL } from "../../../domain/entities/r2.entity";
7
7
  import type { IR2Service } from "../../../domain/interfaces/services.interface";
8
8
  import { validationUtils } from "../../../infrastructure/utils";
9
9
 
@@ -51,8 +51,6 @@ class R2Service implements IR2Service {
51
51
  key: object.key,
52
52
  size: object.size,
53
53
  uploaded: object.uploaded,
54
- httpMetadata: object.httpMetadata,
55
- customMetadata: object.customMetadata,
56
54
  };
57
55
  }
58
56
 
@@ -70,7 +68,6 @@ class R2Service implements IR2Service {
70
68
  await bucket.put(key, data, {
71
69
  httpMetadata: options?.httpMetadata,
72
70
  customMetadata: options?.customMetadata,
73
- checksum: options?.checksum,
74
71
  });
75
72
  }
76
73
 
@@ -94,9 +91,12 @@ class R2Service implements IR2Service {
94
91
  });
95
92
 
96
93
  return {
97
- objects: listed.objects,
98
- truncated: listed.truncated,
99
- cursor: listed.cursor,
94
+ objects: listed.objects.map((obj) => ({
95
+ key: obj.key,
96
+ size: obj.size,
97
+ uploaded: obj.uploaded,
98
+ })),
99
+ cursor: (listed as any).cursor as string | undefined,
100
100
  };
101
101
  }
102
102
 
@@ -105,11 +105,11 @@ class R2Service implements IR2Service {
105
105
  // This would typically use the AWS SDK or custom signing logic
106
106
  // For now, return a placeholder
107
107
 
108
- const expiresAt = new Date(Date.now() + expiresIn * 1000);
108
+ const expires = Date.now() + expiresIn * 1000;
109
109
 
110
110
  return {
111
- url: `https://presigned-url-placeholder/${key}?expires=${expiresAt.getTime()}`,
112
- expiresAt,
111
+ url: `https://presigned-url-placeholder/${key}?expires=${expires}`,
112
+ expires,
113
113
  };
114
114
  }
115
115
 
@@ -120,11 +120,11 @@ class R2Service implements IR2Service {
120
120
  const objectKey = key || `uploads/${Date.now()}-${file.name}`;
121
121
 
122
122
  await this.put(objectKey, file.stream(), {
123
- ...options,
124
123
  httpMetadata: {
125
124
  contentType: file.type,
126
125
  ...options?.httpMetadata,
127
126
  },
127
+ customMetadata: options?.customMetadata,
128
128
  });
129
129
  }
130
130
 
@@ -134,10 +134,16 @@ class R2Service implements IR2Service {
134
134
  throw new Error(`Failed to fetch URL: ${response.statusText}`);
135
135
  }
136
136
 
137
- await this.put(key, response.body, {
137
+ const body = response.body;
138
+ if (!body) {
139
+ throw new Error(`Failed to read response body`);
140
+ }
141
+
142
+ await this.put(key, body, {
138
143
  httpMetadata: {
139
144
  contentType: response.headers.get("Content-Type") || undefined,
140
145
  },
146
+ binding,
141
147
  });
142
148
  }
143
149
 
@@ -155,10 +161,12 @@ class R2Service implements IR2Service {
155
161
 
156
162
  await this.deleteMultiple(list.objects.map((obj) => obj.key), binding);
157
163
 
158
- if (list.truncated && list.cursor) {
164
+ if (list.cursor) {
159
165
  await this.deletePrefix(prefix, binding);
160
166
  }
161
167
  }
162
168
  }
163
169
 
170
+ // Export class and singleton instance
171
+ export { R2Service };
164
172
  export const r2Service = new R2Service();
@@ -3,8 +3,24 @@
3
3
  * @description Cloudflare Worker configuration and types
4
4
  */
5
5
 
6
- export interface WorkerRequest extends Request {
6
+ export interface WorkerRequest {
7
+ url: string;
8
+ method: string;
9
+ headers: Headers;
10
+ body?: ReadableStream | null;
7
11
  cf?: IncomingRequestCfProperties;
12
+ cache?: RequestCache;
13
+ credentials?: RequestCredentials;
14
+ integrity?: string;
15
+ mode?: RequestMode;
16
+ redirect?: RequestRedirect;
17
+ referrer?: string;
18
+ referrerPolicy?: ReferrerPolicy;
19
+ json(): Promise<unknown>;
20
+ text(): Promise<string>;
21
+ arrayBuffer(): Promise<ArrayBuffer>;
22
+ blob(): Promise<Blob>;
23
+ formData(): Promise<FormData>;
8
24
  }
9
25
 
10
26
  export interface WorkerResponse extends Response {
@@ -3,7 +3,8 @@
3
3
  * @description Example worker using @umituz/web-cloudflare package
4
4
  */
5
5
 
6
- import { workersService, kvService, r2Service } from "@umituz/web-cloudflare";
6
+ import { workersService, kvService } from "@umituz/web-cloudflare";
7
+ import type { Env } from "../types";
7
8
 
8
9
  // Configure routes
9
10
  workersService.route("/", async () => {
@@ -11,14 +12,15 @@ workersService.route("/", async () => {
11
12
  });
12
13
 
13
14
  workersService.route("/api/hello", async (request) => {
14
- const { name } = await request.json();
15
+ const data = await request.json() as { name?: string };
16
+ const name = data?.name || "World";
15
17
 
16
18
  return workersService.json({ message: `Hello, ${name}!` });
17
19
  });
18
20
 
19
- workersService.route("/api/cache/:key", async (request, env) => {
21
+ workersService.route("/api/cache/:key", async (request, env?: Env) => {
20
22
  const url = new URL(request.url);
21
- const key = url.pathname.split("/").pop();
23
+ const key = url.pathname.split("/").pop() || "";
22
24
 
23
25
  if (request.method === "GET") {
24
26
  const value = await kvService.get(key, env?.KV ? "KV" : undefined);
@@ -26,8 +28,8 @@ workersService.route("/api/cache/:key", async (request, env) => {
26
28
  }
27
29
 
28
30
  if (request.method === "POST") {
29
- const { value } = await request.json();
30
- await kvService.put(key, value, { ttl: 3600 });
31
+ const data = await request.json() as { value?: unknown };
32
+ await kvService.put(key, data.value || "", { ttl: 3600 });
31
33
  return workersService.json({ success: true });
32
34
  }
33
35
 
@@ -36,6 +38,6 @@ workersService.route("/api/cache/:key", async (request, env) => {
36
38
 
37
39
  // Export for Cloudflare Workers
38
40
  export default {
39
- fetch: (request: Request, env: Env, ctx: ExecutionContext) =>
40
- workersService.fetch(request, env, ctx),
41
+ fetch: (request: Request, env?: Env, ctx?: ExecutionContext) =>
42
+ workersService.fetch(request as any, env, ctx),
41
43
  };
@@ -3,7 +3,7 @@
3
3
  * @description Cloudflare Workers HTTP handler and routing
4
4
  */
5
5
 
6
- import type { WorkerRequest, WorkerResponse, WorkerConfig } from "../entities";
6
+ import type { WorkerRequest, WorkerResponse, CloudflareWorkerConfig } from "../entities";
7
7
  import type { Env } from "../types";
8
8
 
9
9
  export interface WorkerFetchOptions {
@@ -61,9 +61,9 @@ class WorkersService {
61
61
  * Fetch handler
62
62
  */
63
63
  async fetch(request: WorkerRequest, env?: Env, ctx?: ExecutionContext): Promise<WorkerResponse> {
64
- // Initialize cache
65
- if (!this.cache && env) {
66
- this.cache = caches.default;
64
+ // Initialize cache if available in Workers runtime
65
+ if (!this.cache && env && typeof caches !== 'undefined') {
66
+ this.cache = (caches as any).default;
67
67
  }
68
68
 
69
69
  // Try middleware
@@ -161,4 +161,6 @@ class WorkersService {
161
161
  }
162
162
  }
163
163
 
164
+ // Export class and singleton instance
165
+ export { WorkersService };
164
166
  export const workersService = new WorkersService();
@@ -18,6 +18,7 @@ export interface WorkflowStep {
18
18
  maxDelay: number;
19
19
  };
20
20
  dependencies?: string[];
21
+ inputs?: Record<string, unknown>;
21
22
  }
22
23
 
23
24
  /**
@@ -27,7 +28,14 @@ export interface WorkflowDefinition {
27
28
  id: string;
28
29
  name: string;
29
30
  description?: string;
31
+ version?: string;
30
32
  steps: WorkflowStep[];
33
+ retryConfig?: {
34
+ maxAttempts: number;
35
+ backoffMultiplier: number;
36
+ initialDelay: number;
37
+ maxDelay: number;
38
+ };
31
39
  }
32
40
 
33
41
  /**
@@ -36,13 +44,18 @@ export interface WorkflowDefinition {
36
44
  export interface WorkflowExecution {
37
45
  id: string;
38
46
  workflowId: string;
39
- status: 'pending' | 'running' | 'completed' | 'failed';
47
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'retrying';
40
48
  currentStep?: string;
41
49
  startedAt: number;
42
50
  completedAt?: number;
43
51
  input: unknown;
44
52
  output?: unknown;
53
+ outputs?: Record<string, unknown>;
45
54
  error?: string;
55
+ completedSteps: string[];
56
+ failedSteps: string[];
57
+ inputs: Record<string, unknown>;
58
+ retryCount: number;
46
59
  }
47
60
 
48
61
  /**
@@ -7,12 +7,42 @@ import type {
7
7
  WorkflowDefinition,
8
8
  WorkflowExecution,
9
9
  WorkflowStep,
10
- WorkflowInstanceState,
11
- MediaProcessingWorkflow,
12
- AIGenerationWorkflow,
13
- BatchOperationWorkflow,
14
10
  } from '../entities';
15
11
 
12
+ // Additional workflow types
13
+ export type WorkflowInstanceState = 'pending' | 'running' | 'completed' | 'failed';
14
+
15
+ export interface WorkflowStepState {
16
+ data: Record<string, unknown>;
17
+ status: WorkflowInstanceState;
18
+ completedAt?: number;
19
+ }
20
+
21
+ export interface MediaProcessingWorkflow {
22
+ type: 'media-processing';
23
+ input: {
24
+ url: string;
25
+ operations: Array<{ type: string; params: Record<string, unknown> }>;
26
+ };
27
+ }
28
+
29
+ export interface AIGenerationWorkflow {
30
+ type: 'ai-generation';
31
+ input: {
32
+ prompt: string;
33
+ model: string;
34
+ parameters?: Record<string, unknown>;
35
+ };
36
+ }
37
+
38
+ export interface BatchOperationWorkflow {
39
+ type: 'batch-operation';
40
+ input: {
41
+ items: Array<{ id: string; data: unknown }>;
42
+ operation: string;
43
+ };
44
+ }
45
+
16
46
  export interface WorkflowServiceConfig {
17
47
  KV?: KVNamespace;
18
48
  D1?: D1Database;
@@ -73,6 +103,7 @@ export class WorkflowService {
73
103
  completedSteps: [],
74
104
  failedSteps: [],
75
105
  inputs,
106
+ input: inputs,
76
107
  startedAt: Date.now(),
77
108
  retryCount: 0,
78
109
  };
@@ -122,7 +153,7 @@ export class WorkflowService {
122
153
  execution.completedSteps.push(step.id);
123
154
 
124
155
  // Save step state for idempotency
125
- await this.saveStepState(execution.id, step.id, stepResult);
156
+ await this.saveStepState(execution.id, step.id, { result: stepResult } as Record<string, unknown>);
126
157
 
127
158
  } catch (error) {
128
159
  stepStatus[step.id] = 'failed';
@@ -257,11 +288,10 @@ export class WorkflowService {
257
288
  data: Record<string, unknown>
258
289
  ): Promise<void> {
259
290
  if (this.kv) {
260
- const state: WorkflowInstanceState = {
261
- executionId,
262
- stepId,
291
+ const state: WorkflowStepState = {
263
292
  data,
264
- timestamp: Date.now(),
293
+ status: 'completed',
294
+ completedAt: Date.now(),
265
295
  };
266
296
  await this.kv.put(
267
297
  `step:${executionId}:${stepId}`,
@@ -276,7 +306,7 @@ export class WorkflowService {
276
306
  private async getStepState(
277
307
  executionId: string,
278
308
  stepId: string
279
- ): Promise<WorkflowInstanceState | null> {
309
+ ): Promise<WorkflowStepState | null> {
280
310
  if (this.kv) {
281
311
  const data = await this.kv.get(`step:${executionId}:${stepId}`);
282
312
  return data ? JSON.parse(data) : null;
@@ -435,3 +465,6 @@ export const WORKFLOW_TEMPLATES: Record<string, Partial<WorkflowDefinition>> = {
435
465
  ],
436
466
  },
437
467
  };
468
+
469
+ // Export singleton instance
470
+ export const workflowService = new WorkflowService();