@umituz/web-cloudflare 1.4.4 → 1.4.6

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 (28) hide show
  1. package/package.json +3 -2
  2. package/src/config/patterns.ts +43 -24
  3. package/src/domain/entities/analytics.entity.ts +30 -0
  4. package/src/domain/entities/d1.entity.ts +27 -0
  5. package/src/domain/entities/image.entity.ts +48 -0
  6. package/src/domain/entities/kv.entity.ts +37 -0
  7. package/src/domain/entities/r2.entity.ts +49 -0
  8. package/src/domain/entities/worker.entity.ts +35 -0
  9. package/src/domains/analytics/entities/index.ts +2 -2
  10. package/src/domains/middleware/entities/index.ts +106 -0
  11. package/src/domains/middleware/index.ts +14 -0
  12. package/src/domains/middleware/services/auth.service.ts +110 -0
  13. package/src/domains/middleware/services/cache.service.ts +93 -0
  14. package/src/domains/middleware/services/cors.service.ts +87 -0
  15. package/src/domains/middleware/services/index.ts +9 -0
  16. package/src/domains/middleware/services/rate-limit.service.ts +93 -0
  17. package/src/domains/middleware/types/index.ts +5 -0
  18. package/src/domains/middleware/types/service.interface.ts +122 -0
  19. package/src/domains/workers/entities/index.ts +1 -1
  20. package/src/domains/workflows/entities/index.ts +60 -0
  21. package/src/domains/wrangler/entities/index.ts +2 -2
  22. package/src/domains/wrangler/services/wrangler.service.ts +16 -8
  23. package/src/domains/wrangler/types/service.interface.ts +2 -2
  24. package/src/index.ts +2 -2
  25. package/src/infrastructure/middleware/index.ts +23 -8
  26. package/src/infrastructure/router/index.ts +26 -4
  27. package/src/infrastructure/utils/helpers.ts +25 -11
  28. package/src/infrastructure/utils/utils.util.ts +3 -2
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Cache Service
3
+ * @description Caching middleware for Cloudflare Workers
4
+ */
5
+
6
+ import type { CacheConfig } from '../entities';
7
+
8
+ interface CacheEntry {
9
+ response: Response;
10
+ expires: number;
11
+ }
12
+
13
+ const cacheStore = new Map<string, CacheEntry>();
14
+
15
+ /**
16
+ * Cache middleware
17
+ */
18
+ export async function cache(
19
+ request: Request,
20
+ config: CacheConfig
21
+ ): Promise<Response | null> {
22
+ if (!config.enabled) {
23
+ return null;
24
+ }
25
+
26
+ const url = new URL(request.url);
27
+ const cacheKey = `${config.prefix || 'cache'}:${url.pathname}${url.search}`;
28
+
29
+ // Check if path should bypass cache
30
+ if (config.bypassPaths?.some((path) => url.pathname.startsWith(path))) {
31
+ return null;
32
+ }
33
+
34
+ // Check cache
35
+ const cached = cacheStore.get(cacheKey);
36
+ if (cached && cached.expires > Date.now()) {
37
+ return cached.response;
38
+ }
39
+
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Set cache
45
+ */
46
+ export function setCache(
47
+ request: Request,
48
+ response: Response,
49
+ config: CacheConfig
50
+ ): void {
51
+ if (!config.enabled) {
52
+ return;
53
+ }
54
+
55
+ const url = new URL(request.url);
56
+ const cacheKey = `${config.prefix || 'cache'}:${url.pathname}${url.search}`;
57
+
58
+ // Determine TTL
59
+ let ttl = config.defaultTTL;
60
+ for (const [path, pathTTL] of Object.entries(config.paths || {})) {
61
+ if (url.pathname.startsWith(path)) {
62
+ ttl = pathTTL;
63
+ break;
64
+ }
65
+ }
66
+
67
+ // Don't cache if TTL is 0
68
+ if (ttl === 0) {
69
+ return;
70
+ }
71
+
72
+ // Cache the response
73
+ cacheStore.set(cacheKey, {
74
+ response: response.clone(),
75
+ expires: Date.now() + ttl * 1000,
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Invalidate cache
81
+ */
82
+ export function invalidateCache(pattern?: string): void {
83
+ if (!pattern) {
84
+ cacheStore.clear();
85
+ return;
86
+ }
87
+
88
+ for (const key of cacheStore.keys()) {
89
+ if (key.includes(pattern)) {
90
+ cacheStore.delete(key);
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * CORS Service
3
+ * @description Cross-Origin Resource Sharing middleware for Cloudflare Workers
4
+ */
5
+
6
+ import type { CORSConfig } from '../entities';
7
+
8
+ /**
9
+ * Add CORS headers to response
10
+ */
11
+ export function addCorsHeaders(
12
+ request: Request,
13
+ response: Response,
14
+ config: CORSConfig
15
+ ): Response {
16
+ if (!config.enabled) {
17
+ return response;
18
+ }
19
+
20
+ const headers = new Headers(response.headers);
21
+ const origin = request.headers.get('Origin');
22
+
23
+ // Check if origin is allowed
24
+ const allowedOrigin = config.allowedOrigins.includes('*')
25
+ ? '*'
26
+ : config.allowedOrigins.includes(origin || '')
27
+ ? origin
28
+ : config.allowedOrigins[0];
29
+
30
+ headers.set('Access-Control-Allow-Origin', allowedOrigin);
31
+ headers.set('Access-Control-Allow-Methods', config.allowedMethods.join(', '));
32
+ headers.set('Access-Control-Allow-Headers', config.allowedHeaders.join(', '));
33
+
34
+ if (config.exposedHeaders) {
35
+ headers.set('Access-Control-Expose-Headers', config.exposedHeaders.join(', '));
36
+ }
37
+
38
+ if (config.allowCredentials) {
39
+ headers.set('Access-Control-Allow-Credentials', 'true');
40
+ }
41
+
42
+ if (config.maxAge) {
43
+ headers.set('Access-Control-Max-Age', config.maxAge.toString());
44
+ }
45
+
46
+ return new Response(response.body, {
47
+ status: response.status,
48
+ statusText: response.statusText,
49
+ headers,
50
+ });
51
+ }
52
+
53
+ /**
54
+ * CORS middleware
55
+ */
56
+ export async function cors(
57
+ request: Request,
58
+ config: CORSConfig
59
+ ): Promise<Response | null> {
60
+ if (!config.enabled) {
61
+ return null;
62
+ }
63
+
64
+ // Handle preflight request
65
+ if (request.method === 'OPTIONS') {
66
+ const headers = new Headers();
67
+ const origin = request.headers.get('Origin');
68
+
69
+ const allowedOrigin = config.allowedOrigins.includes('*')
70
+ ? '*'
71
+ : config.allowedOrigins.includes(origin || '')
72
+ ? origin
73
+ : config.allowedOrigins[0];
74
+
75
+ headers.set('Access-Control-Allow-Origin', allowedOrigin);
76
+ headers.set('Access-Control-Allow-Methods', config.allowedMethods.join(', '));
77
+ headers.set('Access-Control-Allow-Headers', config.allowedHeaders.join(', '));
78
+
79
+ if (config.maxAge) {
80
+ headers.set('Access-Control-Max-Age', config.maxAge.toString());
81
+ }
82
+
83
+ return new Response(null, { headers });
84
+ }
85
+
86
+ return null;
87
+ }
@@ -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';
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Rate Limit Service
3
+ * @description Rate limiting middleware for Cloudflare Workers
4
+ */
5
+
6
+ import type { RateLimitConfig } from '../entities';
7
+
8
+ interface RateLimitEntry {
9
+ count: number;
10
+ resetTime: number;
11
+ }
12
+
13
+ const rateLimitStore = new Map<string, RateLimitEntry>();
14
+
15
+ /**
16
+ * Get rate limit key
17
+ */
18
+ function getRateLimitKey(request: Request, config: RateLimitConfig): string {
19
+ const parts: string[] = [];
20
+
21
+ if (config.by === 'ip' || config.by === 'both') {
22
+ parts.push(request.headers.get('CF-Connecting-IP') || 'unknown');
23
+ }
24
+
25
+ if (config.by === 'user' || config.by === 'both') {
26
+ const auth = request.headers.get('Authorization');
27
+ if (auth) {
28
+ parts.push(auth.substring(0, 20));
29
+ }
30
+ }
31
+
32
+ if (config.customKeys) {
33
+ for (const key of config.customKeys) {
34
+ parts.push(request.headers.get(key) || '');
35
+ }
36
+ }
37
+
38
+ return parts.join(':') || 'default';
39
+ }
40
+
41
+ /**
42
+ * Check rate limit
43
+ */
44
+ export async function checkRateLimit(
45
+ request: Request,
46
+ config: RateLimitConfig
47
+ ): Promise<Response | null> {
48
+ if (!config.enabled) {
49
+ return null;
50
+ }
51
+
52
+ const key = getRateLimitKey(request, config);
53
+
54
+ // Check whitelist
55
+ if (config.whitelist?.includes(key)) {
56
+ return null;
57
+ }
58
+
59
+ const now = Date.now();
60
+ const entry = rateLimitStore.get(key);
61
+
62
+ // Reset if window expired
63
+ if (!entry || now > entry.resetTime) {
64
+ rateLimitStore.set(key, {
65
+ count: 1,
66
+ resetTime: now + config.window * 1000,
67
+ });
68
+ return null;
69
+ }
70
+
71
+ // Increment count
72
+ entry.count++;
73
+
74
+ // Check if exceeded
75
+ if (entry.count > config.maxRequests) {
76
+ const retryAfter = Math.ceil((entry.resetTime - now) / 1000);
77
+ return new Response(
78
+ JSON.stringify({
79
+ error: config.response?.message || 'Rate limit exceeded',
80
+ retryAfter,
81
+ }),
82
+ {
83
+ status: config.response?.status || 429,
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ 'Retry-After': retryAfter.toString(),
87
+ },
88
+ }
89
+ );
90
+ }
91
+
92
+ return null;
93
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Middleware Domain Types
3
+ */
4
+
5
+ export * from './service.interface';
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Middleware Service Interface
3
+ * @description Defines the contract for middleware operations
4
+ */
5
+
6
+ import type {
7
+ CORSConfig,
8
+ CacheConfig,
9
+ RateLimitConfig,
10
+ AuthConfig,
11
+ SecurityHeadersConfig,
12
+ IPFilterConfig,
13
+ LogConfig,
14
+ HealthCheckConfig,
15
+ ErrorHandlerConfig,
16
+ } from '../entities';
17
+
18
+ export interface IMiddlewareService {
19
+ /**
20
+ * CORS middleware
21
+ */
22
+ cors(request: Request, config: CORSConfig): Promise<Response | null>;
23
+ addCorsHeaders(request: Request, response: Response, config: CORSConfig): Response;
24
+
25
+ /**
26
+ * Cache middleware
27
+ */
28
+ cache(request: Request, config: CacheConfig): Promise<Response | null>;
29
+ setCache(request: Request, response: Response, config: CacheConfig): void;
30
+ invalidateCache(pattern?: string): void;
31
+
32
+ /**
33
+ * Rate limit middleware
34
+ */
35
+ checkRateLimit(request: Request, config: RateLimitConfig): Promise<Response | null>;
36
+
37
+ /**
38
+ * Authentication middleware
39
+ */
40
+ requireAuth(request: Request, config: AuthConfig): Promise<Response | null>;
41
+ addUserContext(request: Request, user: {
42
+ id: string;
43
+ [key: string]: unknown;
44
+ }): Request;
45
+
46
+ /**
47
+ * Security headers
48
+ */
49
+ addSecurityHeaders(response: Response, config: SecurityHeadersConfig): Response;
50
+
51
+ /**
52
+ * Bot detection
53
+ */
54
+ detectBot(request: Request): Promise<{
55
+ isBot: boolean;
56
+ botType?: string;
57
+ }>;
58
+
59
+ /**
60
+ * Request logging
61
+ */
62
+ logRequest(request: Request, config: LogConfig): Promise<void>;
63
+
64
+ /**
65
+ * Response time tracking
66
+ */
67
+ trackResponseTime(handler: () => Promise<Response>): Promise<{
68
+ response: Response;
69
+ duration: number;
70
+ }>;
71
+
72
+ /**
73
+ * IP filter
74
+ */
75
+ checkIPFilter(request: Request, config: IPFilterConfig): Response | null;
76
+
77
+ /**
78
+ * Method override
79
+ */
80
+ methodOverride(request: Request): Request;
81
+
82
+ /**
83
+ * Request ID
84
+ */
85
+ addRequestID(request: Request): string;
86
+
87
+ /**
88
+ * Health check
89
+ */
90
+ healthCheck(
91
+ env: import('../../router').CloudflareEnv,
92
+ config?: HealthCheckConfig
93
+ ): Promise<Response>;
94
+
95
+ /**
96
+ * Error handling
97
+ */
98
+ handleMiddlewareError(error: Error, config: ErrorHandlerConfig): Response;
99
+
100
+ /**
101
+ * Conditional middleware
102
+ */
103
+ conditionalChainMiddleware(
104
+ condition: (request: Request) => boolean,
105
+ middleware: (request: Request) => Response | null
106
+ ): (request: Request) => Response | null;
107
+
108
+ /**
109
+ * Chain middleware
110
+ */
111
+ chainMiddleware(
112
+ ...middlewares: Array<(request: Request) => Response | null>
113
+ ): (request: Request) => Response | null;
114
+
115
+ /**
116
+ * Chain async middleware
117
+ */
118
+ chainAsyncMiddleware(
119
+ request: Request,
120
+ ...middlewares: Array<(request: Request) => Promise<Response | null>>
121
+ ): Promise<Response | null>;
122
+ }
@@ -11,7 +11,7 @@ export interface WorkerResponse extends Response {
11
11
  waitUntil?: (promise: Promise<unknown>) => void;
12
12
  }
13
13
 
14
- export interface WorkerConfig {
14
+ export interface CloudflareWorkerConfig {
15
15
  readonly name: string;
16
16
  readonly routes?: string[];
17
17
  readonly schedule?: string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Workflows Domain Entities
3
+ * @description Workflow orchestration entities for Cloudflare Workers
4
+ */
5
+
6
+ /**
7
+ * Workflow step definition
8
+ */
9
+ export interface WorkflowStep {
10
+ id: string;
11
+ name: string;
12
+ handler: string;
13
+ timeout?: number;
14
+ retryPolicy?: {
15
+ maxAttempts: number;
16
+ backoffMultiplier: number;
17
+ initialDelay: number;
18
+ maxDelay: number;
19
+ };
20
+ dependencies?: string[];
21
+ }
22
+
23
+ /**
24
+ * Workflow definition
25
+ */
26
+ export interface WorkflowDefinition {
27
+ id: string;
28
+ name: string;
29
+ description?: string;
30
+ steps: WorkflowStep[];
31
+ }
32
+
33
+ /**
34
+ * Workflow execution state
35
+ */
36
+ export interface WorkflowExecution {
37
+ id: string;
38
+ workflowId: string;
39
+ status: 'pending' | 'running' | 'completed' | 'failed';
40
+ currentStep?: string;
41
+ startedAt: number;
42
+ completedAt?: number;
43
+ input: unknown;
44
+ output?: unknown;
45
+ error?: string;
46
+ }
47
+
48
+ /**
49
+ * Workflow config
50
+ */
51
+ export interface CloudflareWorkflowConfig {
52
+ enabled: boolean;
53
+ maxExecutionTime: number;
54
+ defaultRetries: number;
55
+ workflows?: Record<string, WorkflowDefinition>;
56
+ storage?: 'kv' | 'd1';
57
+ }
58
+
59
+ // Type alias for compatibility
60
+ export type WorkflowConfig = CloudflareWorkflowConfig;
@@ -132,9 +132,9 @@ export interface WorkerVersionInfo {
132
132
  }
133
133
 
134
134
  /**
135
- * Analytics data
135
+ * Wrangler analytics data
136
136
  */
137
- export interface AnalyticsData {
137
+ export interface WranglerAnalyticsData {
138
138
  requests?: number;
139
139
  errors?: number;
140
140
  statusCodes?: Record<string, number>;
@@ -118,6 +118,14 @@ export class WranglerService implements IWranglerService {
118
118
  }
119
119
  }
120
120
 
121
+ /**
122
+ * Convert string result to void result
123
+ */
124
+ private asVoidResult(result: WranglerResult<string>): WranglerResult<void> {
125
+ const { data, ...rest } = result;
126
+ return rest;
127
+ }
128
+
121
129
  // ==================== Authentication ====================
122
130
 
123
131
  async login(options?: WranglerCommandOptions): Promise<WranglerResult<AuthInfo>> {
@@ -132,7 +140,7 @@ export class WranglerService implements IWranglerService {
132
140
  }
133
141
 
134
142
  async logout(options?: WranglerCommandOptions): Promise<WranglerResult<void>> {
135
- return this.execute(['logout'], options);
143
+ return this.asVoidResult(await this.execute(['logout'], options));
136
144
  }
137
145
 
138
146
  async whoami(options?: WranglerCommandOptions): Promise<WranglerResult<AuthInfo>> {
@@ -158,7 +166,7 @@ export class WranglerService implements IWranglerService {
158
166
  if (template) {
159
167
  args.push('--template', template);
160
168
  }
161
- return this.execute(args, options);
169
+ return this.asVoidResult(await this.execute(args, options));
162
170
  }
163
171
 
164
172
  async dev(
@@ -237,7 +245,7 @@ export class WranglerService implements IWranglerService {
237
245
  value: string,
238
246
  options?: WranglerCommandOptions
239
247
  ): Promise<WranglerResult<void>> {
240
- return this.execute(['kv:key', 'put', '--namespace-id', namespaceId, key, value], options);
248
+ return this.asVoidResult(await this.execute(['kv:key', 'put', '--namespace-id', namespaceId, key, value], options));
241
249
  }
242
250
 
243
251
  async kvKeyGet(
@@ -253,7 +261,7 @@ export class WranglerService implements IWranglerService {
253
261
  key: string,
254
262
  options?: WranglerCommandOptions
255
263
  ): Promise<WranglerResult<void>> {
256
- return this.execute(['kv:key', 'delete', '--namespace-id', namespaceId, key], options);
264
+ return this.asVoidResult(await this.execute(['kv:key', 'delete', '--namespace-id', namespaceId, key], options));
257
265
  }
258
266
 
259
267
  // ==================== R2 Operations ====================
@@ -289,7 +297,7 @@ export class WranglerService implements IWranglerService {
289
297
  bucketName: string,
290
298
  options?: WranglerCommandOptions
291
299
  ): Promise<WranglerResult<void>> {
292
- return this.execute(['r2', 'bucket', 'delete', bucketName], options);
300
+ return this.asVoidResult(await this.execute(['r2', 'bucket', 'delete', bucketName], options));
293
301
  }
294
302
 
295
303
  async r2ObjectPut(
@@ -298,7 +306,7 @@ export class WranglerService implements IWranglerService {
298
306
  file: string,
299
307
  options?: WranglerCommandOptions
300
308
  ): Promise<WranglerResult<void>> {
301
- return this.execute(['r2', 'object', 'put', bucketName, key, '--file', file], options);
309
+ return this.asVoidResult(await this.execute(['r2', 'object', 'put', bucketName, key, '--file', file], options));
302
310
  }
303
311
 
304
312
  // ==================== D1 Operations ====================
@@ -412,7 +420,7 @@ export class WranglerService implements IWranglerService {
412
420
  secretName: string,
413
421
  options?: WranglerCommandOptions
414
422
  ): Promise<WranglerResult<void>> {
415
- return this.execute(['secret', 'delete', secretName], options);
423
+ return this.asVoidResult(await this.execute(['secret', 'delete', secretName], options));
416
424
  }
417
425
 
418
426
  // ==================== Monitoring ====================
@@ -446,7 +454,7 @@ export class WranglerService implements IWranglerService {
446
454
  versionId: string,
447
455
  options?: WranglerCommandOptions
448
456
  ): Promise<WranglerResult<void>> {
449
- return this.execute(['versions', 'rollback', '--version-id', versionId], options);
457
+ return this.asVoidResult(await this.execute(['versions', 'rollback', '--version-id', versionId], options));
450
458
  }
451
459
 
452
460
  // ==================== Generic Command Execution ====================
@@ -12,8 +12,8 @@ import type {
12
12
  D1DatabaseInfo,
13
13
  SecretInfo,
14
14
  WorkerVersionInfo,
15
- AnalyticsData,
16
- } from '../entities/wrangler.entity';
15
+ WranglerAnalyticsData,
16
+ } from '../entities';
17
17
 
18
18
  export interface IWranglerService {
19
19
  // Authentication
package/src/index.ts CHANGED
@@ -35,10 +35,10 @@ export * from "./domains/kv";
35
35
  export * from "./domains/images";
36
36
  export * from "./domains/analytics";
37
37
  export * from "./domains/workflows";
38
+ export * from "./domains/middleware";
38
39
 
39
- // Infrastructure - Router, Middleware, Utils
40
+ // Infrastructure - Router, Utils
40
41
  export * from "./infrastructure/router";
41
- export * from "./infrastructure/middleware";
42
42
  export * from "./infrastructure/utils/helpers";
43
43
  export * from "./infrastructure/constants";
44
44
 
@@ -1,13 +1,28 @@
1
1
  /**
2
2
  * Cloudflare Middleware Collection
3
3
  * @description Comprehensive middleware for Cloudflare Workers
4
+ * @deprecated Import from '@umituz/web-cloudflare/middleware' instead
4
5
  */
5
6
 
6
- // Re-export existing middleware
7
- export { cors, addCorsHeaders } from './cors';
8
- export { cache, setCache, invalidateCache } from './cache';
9
- export { checkRateLimit } from './rate-limit';
10
- export { requireAuth, addUserContext } from './auth';
7
+ // Re-export from middleware domain
8
+ export * from '../../domains/middleware';
9
+
10
+ // ============================================================
11
+ // Environment Types (kept for backwards compatibility)
12
+ // ============================================================
13
+
14
+ export interface CloudflareMiddlewareEnv {
15
+ KV?: KVNamespace;
16
+ R2?: R2Bucket;
17
+ D1?: D1Database;
18
+ DO?: Record<string, DurableObjectNamespace>;
19
+ QUEUE?: Record<string, Queue>;
20
+ AI?: any;
21
+ vars?: Record<string, string>;
22
+ }
23
+
24
+ // Type alias for backwards compatibility
25
+ export type Env = CloudflareMiddlewareEnv;
11
26
 
12
27
  // ============================================================
13
28
  // New Middleware
@@ -300,7 +315,7 @@ export interface HealthCheckConfig {
300
315
  }
301
316
 
302
317
  export async function healthCheck(
303
- env: Env,
318
+ env: CloudflareMiddlewareEnv,
304
319
  config?: HealthCheckConfig
305
320
  ): Promise<Response> {
306
321
  const checks: Record<string, boolean | string> = {
@@ -357,9 +372,9 @@ export function handleMiddlewareError(
357
372
  }
358
373
 
359
374
  /**
360
- * Conditional Middleware
375
+ * Conditional Middleware (Chain)
361
376
  */
362
- export function conditionalMiddleware(
377
+ export function conditionalChainMiddleware(
363
378
  condition: (request: Request) => boolean,
364
379
  middleware: (request: Request) => Response | null
365
380
  ): (request: Request) => Response | null {