@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
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* @description Rate limiting middleware for Cloudflare Workers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { MiddlewareRateLimitConfig } from '../entities';
|
|
7
|
+
|
|
8
|
+
// Type aliases for backwards compatibility
|
|
9
|
+
export type { MiddlewareRateLimitConfig };
|
|
10
|
+
export type RateLimitConfig = MiddlewareRateLimitConfig;
|
|
7
11
|
|
|
8
12
|
interface RateLimitEntry {
|
|
9
13
|
count: number;
|
|
@@ -15,7 +19,7 @@ const rateLimitStore = new Map<string, RateLimitEntry>();
|
|
|
15
19
|
/**
|
|
16
20
|
* Get rate limit key
|
|
17
21
|
*/
|
|
18
|
-
function getRateLimitKey(request: Request, config:
|
|
22
|
+
function getRateLimitKey(request: Request, config: MiddlewareRateLimitConfig): string {
|
|
19
23
|
const parts: string[] = [];
|
|
20
24
|
|
|
21
25
|
if (config.by === 'ip' || config.by === 'both') {
|
|
@@ -43,7 +47,7 @@ function getRateLimitKey(request: Request, config: RateLimitConfig): string {
|
|
|
43
47
|
*/
|
|
44
48
|
export async function checkRateLimit(
|
|
45
49
|
request: Request,
|
|
46
|
-
config:
|
|
50
|
+
config: MiddlewareRateLimitConfig
|
|
47
51
|
): Promise<Response | null> {
|
|
48
52
|
if (!config.enabled) {
|
|
49
53
|
return null;
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
MiddlewareCORSConfig,
|
|
8
|
+
MiddlewareCacheConfig,
|
|
9
|
+
MiddlewareRateLimitConfig,
|
|
10
|
+
MiddlewareAuthConfig,
|
|
11
11
|
SecurityHeadersConfig,
|
|
12
12
|
IPFilterConfig,
|
|
13
13
|
LogConfig,
|
|
@@ -15,29 +15,35 @@ import type {
|
|
|
15
15
|
ErrorHandlerConfig,
|
|
16
16
|
} from '../entities';
|
|
17
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
|
+
|
|
18
24
|
export interface IMiddlewareService {
|
|
19
25
|
/**
|
|
20
26
|
* CORS middleware
|
|
21
27
|
*/
|
|
22
|
-
cors(request: Request, config:
|
|
23
|
-
addCorsHeaders(request: Request, response: Response, config:
|
|
28
|
+
cors(request: Request, config: MiddlewareCORSConfig): Promise<Response | null>;
|
|
29
|
+
addCorsHeaders(request: Request, response: Response, config: MiddlewareCORSConfig): Response;
|
|
24
30
|
|
|
25
31
|
/**
|
|
26
32
|
* Cache middleware
|
|
27
33
|
*/
|
|
28
|
-
cache(request: Request, config:
|
|
29
|
-
setCache(request: Request, response: Response, config:
|
|
34
|
+
cache(request: Request, config: MiddlewareCacheConfig): Promise<Response | null>;
|
|
35
|
+
setCache(request: Request, response: Response, config: MiddlewareCacheConfig): void;
|
|
30
36
|
invalidateCache(pattern?: string): void;
|
|
31
37
|
|
|
32
38
|
/**
|
|
33
39
|
* Rate limit middleware
|
|
34
40
|
*/
|
|
35
|
-
checkRateLimit(request: Request, config:
|
|
41
|
+
checkRateLimit(request: Request, config: MiddlewareRateLimitConfig): Promise<Response | null>;
|
|
36
42
|
|
|
37
43
|
/**
|
|
38
44
|
* Authentication middleware
|
|
39
45
|
*/
|
|
40
|
-
requireAuth(request: Request, config:
|
|
46
|
+
requireAuth(request: Request, config: MiddlewareAuthConfig): Promise<Response | null>;
|
|
41
47
|
addUserContext(request: Request, user: {
|
|
42
48
|
id: string;
|
|
43
49
|
[key: string]: unknown;
|
|
@@ -88,7 +94,7 @@ export interface IMiddlewareService {
|
|
|
88
94
|
* Health check
|
|
89
95
|
*/
|
|
90
96
|
healthCheck(
|
|
91
|
-
env:
|
|
97
|
+
env: Record<string, unknown>,
|
|
92
98
|
config?: HealthCheckConfig
|
|
93
99
|
): Promise<Response>;
|
|
94
100
|
|
|
@@ -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 "
|
|
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
|
-
|
|
99
|
-
|
|
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
|
|
108
|
+
const expires = Date.now() + expiresIn * 1000;
|
|
109
109
|
|
|
110
110
|
return {
|
|
111
|
-
url: `https://presigned-url-placeholder/${key}?expires=${
|
|
112
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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:
|
|
261
|
-
executionId,
|
|
262
|
-
stepId,
|
|
291
|
+
const state: WorkflowStepState = {
|
|
263
292
|
data,
|
|
264
|
-
|
|
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<
|
|
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();
|