@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,405 @@
1
+ /**
2
+ * Cloudflare Middleware Collection
3
+ * @description Comprehensive middleware for Cloudflare Workers
4
+ */
5
+
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';
11
+
12
+ // ============================================================
13
+ // New Middleware
14
+ // ============================================================
15
+
16
+ /**
17
+ * Compression Middleware
18
+ */
19
+ export async function compression(
20
+ request: Request,
21
+ response: Response
22
+ ): Promise<Response> {
23
+ const acceptEncoding = request.headers.get('Accept-Encoding') || '';
24
+ const contentType = response.headers.get('Content-Type') || '';
25
+
26
+ // Check if client accepts compression
27
+ if (!acceptEncoding.includes('gzip') && !acceptEncoding.includes('br')) {
28
+ return response;
29
+ }
30
+
31
+ // Check if content type is compressible
32
+ const compressibleTypes = [
33
+ 'text/',
34
+ 'application/json',
35
+ 'application/javascript',
36
+ 'application/xml',
37
+ 'application/xhtml+xml',
38
+ ];
39
+
40
+ const isCompressible = compressibleTypes.some((type) =>
41
+ contentType.includes(type)
42
+ );
43
+
44
+ if (!isCompressible) {
45
+ return response;
46
+ }
47
+
48
+ // Return original response (Cloudflare handles compression automatically)
49
+ return response;
50
+ }
51
+
52
+ /**
53
+ * Security Headers Middleware
54
+ */
55
+ export interface SecurityHeadersConfig {
56
+ frameGuard?: boolean;
57
+ contentTypeNosniff?: boolean;
58
+ xssProtection?: boolean;
59
+ strictTransportSecurity?: boolean;
60
+ referrerPolicy?: string;
61
+ contentSecurityPolicy?: string;
62
+ }
63
+
64
+ export function addSecurityHeaders(
65
+ response: Response,
66
+ config: SecurityHeadersConfig = {}
67
+ ): Response {
68
+ const headers = new Headers(response.headers);
69
+
70
+ if (config.frameGuard !== false) {
71
+ headers.set('X-Frame-Options', 'DENY');
72
+ }
73
+
74
+ if (config.contentTypeNosniff !== false) {
75
+ headers.set('X-Content-Type-Options', 'nosniff');
76
+ }
77
+
78
+ if (config.xssProtection !== false) {
79
+ headers.set('X-XSS-Protection', '1; mode=block');
80
+ }
81
+
82
+ if (config.strictTransportSecurity) {
83
+ headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
84
+ }
85
+
86
+ if (config.referrerPolicy) {
87
+ headers.set('Referrer-Policy', config.referrerPolicy);
88
+ }
89
+
90
+ if (config.contentSecurityPolicy) {
91
+ headers.set('Content-Security-Policy', config.contentSecurityPolicy);
92
+ }
93
+
94
+ return new Response(response.body, {
95
+ status: response.status,
96
+ statusText: response.statusText,
97
+ headers,
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Bot Detection Middleware
103
+ */
104
+ export async function detectBot(request: Request): Promise<{
105
+ isBot: boolean;
106
+ botType?: string;
107
+ }> {
108
+ const userAgent = request.headers.get('User-Agent') || '';
109
+ const botPatterns = [
110
+ /googlebot/i,
111
+ /bingbot/i,
112
+ /slurp/i,
113
+ /duckduckbot/i,
114
+ /baiduspider/i,
115
+ /yandexbot/i,
116
+ /facebookexternalhit/i,
117
+ /twitterbot/i,
118
+ /linkedinbot/i,
119
+ /whatsapp/i,
120
+ /telegrambot/i,
121
+ /applebot/i,
122
+ /semrushbot/i,
123
+ /ahrefsbot/i,
124
+ /mj12bot/i,
125
+ ];
126
+
127
+ for (const pattern of botPatterns) {
128
+ if (pattern.test(userAgent)) {
129
+ return {
130
+ isBot: true,
131
+ botType: pattern.source.replace('/i', '').replace('/', ''),
132
+ };
133
+ }
134
+ }
135
+
136
+ return { isBot: false };
137
+ }
138
+
139
+ /**
140
+ * Request Logging Middleware
141
+ */
142
+ export interface LogConfig {
143
+ level: 'debug' | 'info' | 'warn' | 'error';
144
+ includeHeaders?: boolean;
145
+ includeBody?: boolean;
146
+ sampleRate?: number;
147
+ }
148
+
149
+ export async function logRequest(
150
+ request: Request,
151
+ config: LogConfig = { level: 'info' }
152
+ ): Promise<void> {
153
+ // Sample rate check
154
+ if (config.sampleRate && Math.random() > config.sampleRate) {
155
+ return;
156
+ }
157
+
158
+ const url = new URL(request.url);
159
+ const logData: Record<string, unknown> = {
160
+ method: request.method,
161
+ url: url.pathname,
162
+ timestamp: Date.now(),
163
+ userAgent: request.headers.get('User-Agent'),
164
+ ip: request.headers.get('CF-Connecting-IP'),
165
+ };
166
+
167
+ if (config.includeHeaders) {
168
+ const headers: Record<string, string> = {};
169
+ request.headers.forEach((value, key) => {
170
+ headers[key] = value;
171
+ });
172
+ logData.headers = headers;
173
+ }
174
+
175
+ if (config.includeBody && request.method !== 'GET') {
176
+ try {
177
+ const clone = request.clone();
178
+ logData.body = await clone.json();
179
+ } catch {
180
+ // Body not JSON or already consumed
181
+ }
182
+ }
183
+
184
+ switch (config.level) {
185
+ case 'debug':
186
+ console.debug('[Request]', JSON.stringify(logData));
187
+ break;
188
+ case 'info':
189
+ console.info('[Request]', JSON.stringify(logData));
190
+ break;
191
+ case 'warn':
192
+ console.warn('[Request]', JSON.stringify(logData));
193
+ break;
194
+ case 'error':
195
+ console.error('[Request]', JSON.stringify(logData));
196
+ break;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Response Time Tracking Middleware
202
+ */
203
+ export async function trackResponseTime(
204
+ handler: () => Promise<Response>
205
+ ): Promise<{ response: Response; duration: number }> {
206
+ const start = Date.now();
207
+ const response = await handler();
208
+ const duration = Date.now() - start;
209
+
210
+ // Add timing header
211
+ response.headers.set('X-Response-Time', `${duration}ms`);
212
+
213
+ return { response, duration };
214
+ }
215
+
216
+ /**
217
+ * IP Filter Middleware
218
+ */
219
+ export interface IPFilterConfig {
220
+ mode: 'whitelist' | 'blacklist';
221
+ ips: string[];
222
+ cidrs?: string[];
223
+ }
224
+
225
+ export function checkIPFilter(
226
+ request: Request,
227
+ config: IPFilterConfig
228
+ ): Response | null {
229
+ const ip = request.headers.get('CF-Connecting-IP');
230
+
231
+ if (!ip) {
232
+ return new Response('IP not found', { status: 400 });
233
+ }
234
+
235
+ const isAllowed = config.ips.includes(ip);
236
+
237
+ if (config.mode === 'whitelist' && !isAllowed) {
238
+ return new Response('IP not allowed', { status: 403 });
239
+ }
240
+
241
+ if (config.mode === 'blacklist' && isAllowed) {
242
+ return new Response('IP blocked', { status: 403 });
243
+ }
244
+
245
+ return null;
246
+ }
247
+
248
+ /**
249
+ * Method Override Middleware
250
+ */
251
+ export function methodOverride(request: Request): Request {
252
+ const method = request.headers.get('X-HTTP-Method-Override');
253
+ const url = new URL(request.url);
254
+ const bodyMethod = url.searchParams.get('_method');
255
+
256
+ const overrideMethod = method || bodyMethod;
257
+
258
+ if (overrideMethod && ['PUT', 'PATCH', 'DELETE'].includes(overrideMethod.toUpperCase())) {
259
+ // Return modified request
260
+ return new Request(request.url, {
261
+ method: overrideMethod.toUpperCase(),
262
+ headers: request.headers,
263
+ body: request.body,
264
+ });
265
+ }
266
+
267
+ return request;
268
+ }
269
+
270
+ /**
271
+ * Request ID Middleware
272
+ */
273
+ export function addRequestID(request: Request): string {
274
+ const existingID = request.headers.get('X-Request-ID');
275
+
276
+ if (existingID) {
277
+ return existingID;
278
+ }
279
+
280
+ const requestID = crypto.randomUUID();
281
+ return requestID;
282
+ }
283
+
284
+ /**
285
+ * Response Time Middleware
286
+ */
287
+ export function responseTime(): Response {
288
+ const response = new Response(JSON.stringify({ time: Date.now() }), {
289
+ headers: { 'Content-Type': 'application/json' },
290
+ });
291
+ return response;
292
+ }
293
+
294
+ /**
295
+ * Health Check Middleware
296
+ */
297
+ export interface HealthCheckConfig {
298
+ uptime: number;
299
+ checks: Record<string, () => Promise<boolean>>;
300
+ }
301
+
302
+ export async function healthCheck(
303
+ env: Env,
304
+ config?: HealthCheckConfig
305
+ ): Promise<Response> {
306
+ const checks: Record<string, boolean | string> = {
307
+ healthy: true,
308
+ timestamp: new Date().toISOString(),
309
+ uptime: config?.uptime || process.uptime?.() || 0,
310
+ };
311
+
312
+ if (config?.checks) {
313
+ for (const [name, check] of Object.entries(config.checks)) {
314
+ try {
315
+ checks[name] = await check();
316
+ } catch (error) {
317
+ checks[name] = error instanceof Error ? error.message : String(error);
318
+ checks.healthy = false;
319
+ }
320
+ }
321
+ }
322
+
323
+ const status = checks.healthy ? 200 : 503;
324
+ return Response.json(checks, { status });
325
+ }
326
+
327
+ /**
328
+ * Error Handling Middleware
329
+ */
330
+ export interface ErrorHandlerConfig {
331
+ debug: boolean;
332
+ logger?: (error: Error) => void;
333
+ }
334
+
335
+ export function handleMiddlewareError(
336
+ error: Error,
337
+ config: ErrorHandlerConfig = { debug: false }
338
+ ): Response {
339
+ if (config.logger) {
340
+ config.logger(error);
341
+ } else {
342
+ console.error('[Middleware Error]', error);
343
+ }
344
+
345
+ const status = 500;
346
+ const body: Record<string, unknown> = {
347
+ error: 'Internal Server Error',
348
+ status,
349
+ };
350
+
351
+ if (config.debug) {
352
+ body.message = error.message;
353
+ body.stack = error.stack;
354
+ }
355
+
356
+ return Response.json(body, { status });
357
+ }
358
+
359
+ /**
360
+ * Conditional Middleware
361
+ */
362
+ export function conditionalMiddleware(
363
+ condition: (request: Request) => boolean,
364
+ middleware: (request: Request) => Response | null
365
+ ): (request: Request) => Response | null {
366
+ return (request: Request) => {
367
+ if (condition(request)) {
368
+ return middleware(request);
369
+ }
370
+ return null;
371
+ };
372
+ }
373
+
374
+ /**
375
+ * Chain Middleware
376
+ */
377
+ export function chainMiddleware(
378
+ ...middlewares: Array<(request: Request) => Response | null>
379
+ ): (request: Request) => Response | null {
380
+ return (request: Request) => {
381
+ for (const middleware of middlewares) {
382
+ const response = middleware(request);
383
+ if (response) {
384
+ return response;
385
+ }
386
+ }
387
+ return null;
388
+ };
389
+ }
390
+
391
+ /**
392
+ * Async Middleware Chain
393
+ */
394
+ export async function chainAsyncMiddleware(
395
+ request: Request,
396
+ ...middlewares: Array<(request: Request) => Promise<Response | null>>
397
+ ): Promise<Response | null> {
398
+ for (const middleware of middlewares) {
399
+ const response = await middleware(request);
400
+ if (response) {
401
+ return response;
402
+ }
403
+ }
404
+ return null;
405
+ }