@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.
- package/package.json +3 -2
- package/src/config/patterns.ts +43 -24
- package/src/domain/entities/analytics.entity.ts +30 -0
- package/src/domain/entities/d1.entity.ts +27 -0
- package/src/domain/entities/image.entity.ts +48 -0
- package/src/domain/entities/kv.entity.ts +37 -0
- package/src/domain/entities/r2.entity.ts +49 -0
- package/src/domain/entities/worker.entity.ts +35 -0
- package/src/domains/analytics/entities/index.ts +2 -2
- package/src/domains/middleware/entities/index.ts +106 -0
- package/src/domains/middleware/index.ts +14 -0
- package/src/domains/middleware/services/auth.service.ts +110 -0
- package/src/domains/middleware/services/cache.service.ts +93 -0
- package/src/domains/middleware/services/cors.service.ts +87 -0
- package/src/domains/middleware/services/index.ts +9 -0
- package/src/domains/middleware/services/rate-limit.service.ts +93 -0
- package/src/domains/middleware/types/index.ts +5 -0
- package/src/domains/middleware/types/service.interface.ts +122 -0
- package/src/domains/workers/entities/index.ts +1 -1
- package/src/domains/workflows/entities/index.ts +60 -0
- package/src/domains/wrangler/entities/index.ts +2 -2
- package/src/domains/wrangler/services/wrangler.service.ts +16 -8
- package/src/domains/wrangler/types/service.interface.ts +2 -2
- package/src/index.ts +2 -2
- package/src/infrastructure/middleware/index.ts +23 -8
- package/src/infrastructure/router/index.ts +26 -4
- package/src/infrastructure/utils/helpers.ts +25 -11
- package/src/infrastructure/utils/utils.util.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-cloudflare",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
4
4
|
"description": "Comprehensive Cloudflare Workers integration with config-based patterns, middleware, router, workflows, and AI (Patch-only versioning: only z in x.y.z increments)",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"./ai-gateway": "./src/domains/ai-gateway/index.ts",
|
|
18
18
|
"./workers-ai": "./src/domains/ai-gateway/index.ts",
|
|
19
19
|
"./wrangler": "./src/domains/wrangler/index.ts",
|
|
20
|
+
"./middleware": "./src/domains/middleware/index.ts",
|
|
20
21
|
"./router": "./src/infrastructure/router/index.ts",
|
|
21
|
-
"./middleware": "./src/infrastructure/middleware/index.ts",
|
|
22
22
|
"./utils": "./src/infrastructure/utils/helpers.ts",
|
|
23
23
|
"./helpers": "./src/infrastructure/utils/helpers.ts",
|
|
24
24
|
"./config": "./src/config/patterns.ts",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"typescript": ">=5.0.0"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
|
+
"@cloudflare/workers-types": "^4.20260317.1",
|
|
78
79
|
"@types/node": "~22.13.10",
|
|
79
80
|
"typescript": "~5.9.2"
|
|
80
81
|
},
|
package/src/config/patterns.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @description Reusable configuration patterns for different use cases
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { AIGatewayConfig } from '../
|
|
7
|
-
import type { WorkflowDefinition } from '../
|
|
6
|
+
import type { AIGatewayConfig } from '../domains/ai-gateway/entities';
|
|
7
|
+
import type { WorkflowDefinition } from '../domains/workflows/entities';
|
|
8
8
|
import type { WorkerConfig } from './types';
|
|
9
9
|
|
|
10
10
|
// ============================================================
|
|
@@ -106,6 +106,8 @@ export const saasConfig: Partial<WorkerConfig> = {
|
|
|
106
106
|
},
|
|
107
107
|
workflows: {
|
|
108
108
|
enabled: true,
|
|
109
|
+
maxExecutionTime: 300,
|
|
110
|
+
defaultRetries: 2,
|
|
109
111
|
},
|
|
110
112
|
};
|
|
111
113
|
|
|
@@ -149,6 +151,8 @@ export const cdnConfig: Partial<WorkerConfig> = {
|
|
|
149
151
|
},
|
|
150
152
|
rateLimit: {
|
|
151
153
|
enabled: false,
|
|
154
|
+
maxRequests: 0,
|
|
155
|
+
window: 0,
|
|
152
156
|
},
|
|
153
157
|
compression: {
|
|
154
158
|
enabled: true,
|
|
@@ -194,7 +198,7 @@ export const aiFirstConfig: Partial<WorkerConfig> = {
|
|
|
194
198
|
baseURL: 'https://api.openai.com/v1',
|
|
195
199
|
apiKey: '',
|
|
196
200
|
models: ['gpt-4', 'gpt-3.5-turbo'],
|
|
197
|
-
|
|
201
|
+
fallbackProvider: 'workers-ai',
|
|
198
202
|
weight: 1,
|
|
199
203
|
},
|
|
200
204
|
],
|
|
@@ -206,6 +210,8 @@ export const aiFirstConfig: Partial<WorkerConfig> = {
|
|
|
206
210
|
},
|
|
207
211
|
workflows: {
|
|
208
212
|
enabled: true,
|
|
213
|
+
maxExecutionTime: 600,
|
|
214
|
+
defaultRetries: 3,
|
|
209
215
|
},
|
|
210
216
|
};
|
|
211
217
|
|
|
@@ -215,13 +221,18 @@ export const aiFirstConfig: Partial<WorkerConfig> = {
|
|
|
215
221
|
export const minimalConfig: Partial<WorkerConfig> = {
|
|
216
222
|
cache: {
|
|
217
223
|
enabled: false,
|
|
224
|
+
defaultTTL: 0,
|
|
218
225
|
},
|
|
219
226
|
rateLimit: {
|
|
220
227
|
enabled: false,
|
|
228
|
+
maxRequests: 0,
|
|
229
|
+
window: 0,
|
|
221
230
|
},
|
|
222
231
|
cors: {
|
|
223
232
|
enabled: true,
|
|
224
233
|
allowedOrigins: ['*'],
|
|
234
|
+
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
235
|
+
allowedHeaders: ['*'],
|
|
225
236
|
},
|
|
226
237
|
};
|
|
227
238
|
|
|
@@ -234,26 +245,34 @@ export const minimalConfig: Partial<WorkerConfig> = {
|
|
|
234
245
|
*/
|
|
235
246
|
export function mergeConfigs<T extends Record<string, any>>(
|
|
236
247
|
base: T,
|
|
237
|
-
...overrides: Partial<
|
|
248
|
+
...overrides: Array<Partial<Record<string, any>>>
|
|
238
249
|
): T {
|
|
239
250
|
return overrides.reduce((acc, override) => {
|
|
240
251
|
return deepMerge(acc, override);
|
|
241
252
|
}, base);
|
|
242
253
|
}
|
|
243
254
|
|
|
244
|
-
function deepMerge<T
|
|
245
|
-
|
|
255
|
+
function deepMerge<T extends Record<string, any>>(
|
|
256
|
+
target: T,
|
|
257
|
+
source: Partial<Record<string, any>>
|
|
258
|
+
): T {
|
|
259
|
+
const output = { ...target };
|
|
246
260
|
|
|
247
261
|
if (isObject(target) && isObject(source)) {
|
|
248
262
|
Object.keys(source).forEach((key) => {
|
|
249
|
-
|
|
263
|
+
const sourceValue = source[key];
|
|
264
|
+
const targetValue = target[key as keyof T];
|
|
265
|
+
|
|
266
|
+
if (isObject(sourceValue)) {
|
|
250
267
|
if (!(key in target)) {
|
|
251
|
-
|
|
268
|
+
(output as any)[key] = sourceValue;
|
|
269
|
+
} else if (isObject(targetValue)) {
|
|
270
|
+
(output as any)[key] = deepMerge(targetValue, sourceValue);
|
|
252
271
|
} else {
|
|
253
|
-
(output as any)[key] =
|
|
272
|
+
(output as any)[key] = sourceValue;
|
|
254
273
|
}
|
|
255
274
|
} else {
|
|
256
|
-
|
|
275
|
+
(output as any)[key] = sourceValue;
|
|
257
276
|
}
|
|
258
277
|
});
|
|
259
278
|
}
|
|
@@ -261,7 +280,7 @@ function deepMerge<T>(target: T, source: Partial<T>): T {
|
|
|
261
280
|
return output;
|
|
262
281
|
}
|
|
263
282
|
|
|
264
|
-
function isObject(item: unknown): item is Record<string,
|
|
283
|
+
function isObject(item: unknown): item is Record<string, any> {
|
|
265
284
|
return Boolean(item && typeof item === 'object' && !Array.isArray(item));
|
|
266
285
|
}
|
|
267
286
|
|
|
@@ -316,52 +335,52 @@ export class ConfigBuilder {
|
|
|
316
335
|
}
|
|
317
336
|
|
|
318
337
|
withCache(config: Partial<NonNullable<WorkerConfig['cache']>>): ConfigBuilder {
|
|
319
|
-
this.config.cache = { ...this.config.cache, ...config }
|
|
338
|
+
this.config.cache = this.config.cache ? { ...this.config.cache, ...config } : config as NonNullable<WorkerConfig['cache']>;
|
|
320
339
|
return this;
|
|
321
340
|
}
|
|
322
341
|
|
|
323
342
|
withRateLimit(config: Partial<NonNullable<WorkerConfig['rateLimit']>>): ConfigBuilder {
|
|
324
|
-
this.config.rateLimit = { ...this.config.rateLimit, ...config }
|
|
343
|
+
this.config.rateLimit = this.config.rateLimit ? { ...this.config.rateLimit, ...config } : config as NonNullable<WorkerConfig['rateLimit']>;
|
|
325
344
|
return this;
|
|
326
345
|
}
|
|
327
346
|
|
|
328
347
|
withAI(config: Partial<NonNullable<WorkerConfig['ai']>>): ConfigBuilder {
|
|
329
|
-
this.config.ai = { ...this.config.ai, ...config }
|
|
348
|
+
this.config.ai = this.config.ai ? { ...this.config.ai, ...config } : config as NonNullable<WorkerConfig['ai']>;
|
|
330
349
|
return this;
|
|
331
350
|
}
|
|
332
351
|
|
|
333
352
|
withWorkflows(config: Partial<NonNullable<WorkerConfig['workflows']>>): ConfigBuilder {
|
|
334
|
-
this.config.workflows = { ...this.config.workflows, ...config }
|
|
353
|
+
this.config.workflows = this.config.workflows ? { ...this.config.workflows, ...config } : config as NonNullable<WorkerConfig['workflows']>;
|
|
335
354
|
return this;
|
|
336
355
|
}
|
|
337
356
|
|
|
338
357
|
withCORS(config: Partial<NonNullable<WorkerConfig['cors']>>): ConfigBuilder {
|
|
339
|
-
this.config.cors = { ...this.config.cors, ...config }
|
|
358
|
+
this.config.cors = this.config.cors ? { ...this.config.cors, ...config } : config as NonNullable<WorkerConfig['cors']>;
|
|
340
359
|
return this;
|
|
341
360
|
}
|
|
342
361
|
|
|
343
362
|
withAnalytics(config: Partial<NonNullable<WorkerConfig['analytics']>>): ConfigBuilder {
|
|
344
|
-
this.config.analytics = { ...this.config.analytics, ...config }
|
|
363
|
+
this.config.analytics = this.config.analytics ? { ...this.config.analytics, ...config } : config as NonNullable<WorkerConfig['analytics']>;
|
|
345
364
|
return this;
|
|
346
365
|
}
|
|
347
366
|
|
|
348
367
|
withCompression(config: Partial<NonNullable<WorkerConfig['compression']>>): ConfigBuilder {
|
|
349
|
-
this.config.compression = { ...this.config.compression, ...config }
|
|
368
|
+
this.config.compression = this.config.compression ? { ...this.config.compression, ...config } : config as NonNullable<WorkerConfig['compression']>;
|
|
350
369
|
return this;
|
|
351
370
|
}
|
|
352
371
|
|
|
353
372
|
withImageOptimization(config: Partial<NonNullable<WorkerConfig['imageOptimization']>>): ConfigBuilder {
|
|
354
|
-
this.config.imageOptimization = { ...this.config.imageOptimization, ...config }
|
|
373
|
+
this.config.imageOptimization = this.config.imageOptimization ? { ...this.config.imageOptimization, ...config } : config as NonNullable<WorkerConfig['imageOptimization']>;
|
|
355
374
|
return this;
|
|
356
375
|
}
|
|
357
376
|
|
|
358
377
|
withQueues(config: Partial<NonNullable<WorkerConfig['queues']>>): ConfigBuilder {
|
|
359
|
-
this.config.queues = { ...this.config.queues, ...config }
|
|
378
|
+
this.config.queues = this.config.queues ? { ...this.config.queues, ...config } : config as NonNullable<WorkerConfig['queues']>;
|
|
360
379
|
return this;
|
|
361
380
|
}
|
|
362
381
|
|
|
363
382
|
withScheduledTasks(config: Partial<NonNullable<WorkerConfig['scheduledTasks']>>): ConfigBuilder {
|
|
364
|
-
this.config.scheduledTasks = { ...this.config.scheduledTasks, ...config }
|
|
383
|
+
this.config.scheduledTasks = this.config.scheduledTasks ? { ...this.config.scheduledTasks, ...config } : config as NonNullable<WorkerConfig['scheduledTasks']>;
|
|
365
384
|
return this;
|
|
366
385
|
}
|
|
367
386
|
|
|
@@ -449,9 +468,9 @@ export function getEnvironmentConfig(
|
|
|
449
468
|
switch (environment) {
|
|
450
469
|
case 'development':
|
|
451
470
|
return mergeConfigs(minimalConfig, {
|
|
452
|
-
cache: { enabled: false },
|
|
453
|
-
rateLimit: { enabled: false },
|
|
454
|
-
cors: { allowedOrigins: ['*'] },
|
|
471
|
+
cache: { enabled: false, defaultTTL: 0 },
|
|
472
|
+
rateLimit: { enabled: false, maxRequests: 0, window: 0 },
|
|
473
|
+
cors: { enabled: true, allowedOrigins: ['*'], allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['*'] },
|
|
455
474
|
});
|
|
456
475
|
|
|
457
476
|
case 'staging':
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Entity
|
|
3
|
+
* @description Basic Analytics entity placeholder
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface AnalyticsEntity {
|
|
7
|
+
siteId: string;
|
|
8
|
+
eventCount: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AnalyticsConfig {
|
|
12
|
+
siteId: string;
|
|
13
|
+
scriptUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AnalyticsEvent {
|
|
17
|
+
timestamp: number;
|
|
18
|
+
url: string;
|
|
19
|
+
eventType: string;
|
|
20
|
+
data?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AnalyticsData {
|
|
24
|
+
siteId: string;
|
|
25
|
+
events: AnalyticsEvent[];
|
|
26
|
+
metrics?: {
|
|
27
|
+
pageviews: number;
|
|
28
|
+
uniqueVisitors: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D1 Entity
|
|
3
|
+
* @description Basic D1 entity placeholder
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface D1Entity {
|
|
7
|
+
databaseId: string;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface D1DatabaseConfig {
|
|
12
|
+
name: string;
|
|
13
|
+
migrations?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface D1QueryResult<T = unknown> {
|
|
17
|
+
rows: T[];
|
|
18
|
+
meta?: {
|
|
19
|
+
duration: number;
|
|
20
|
+
changes?: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface D1BatchResult<T = unknown> {
|
|
25
|
+
success: boolean;
|
|
26
|
+
results?: D1QueryResult<T>[];
|
|
27
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Entity
|
|
3
|
+
* @description Basic Image entity placeholder
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ImageEntity {
|
|
7
|
+
id: string;
|
|
8
|
+
url: string;
|
|
9
|
+
variant?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ImageConfig {
|
|
13
|
+
formats?: Array<'webp' | 'avif' | 'jpeg' | 'png'>;
|
|
14
|
+
quality?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ImageVariant {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
format: string;
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ImageUploadResult {
|
|
25
|
+
id: string;
|
|
26
|
+
url: string;
|
|
27
|
+
variants: ImageVariant[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ImageUploadOptions {
|
|
31
|
+
format?: 'webp' | 'avif' | 'jpeg' | 'png';
|
|
32
|
+
quality?: number;
|
|
33
|
+
width?: number;
|
|
34
|
+
height?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ImageTransformation {
|
|
38
|
+
width?: number;
|
|
39
|
+
height?: number;
|
|
40
|
+
fit?: 'contain' | 'cover' | 'fill';
|
|
41
|
+
format?: 'webp' | 'avif' | 'jpeg' | 'png';
|
|
42
|
+
quality?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SignedURL {
|
|
46
|
+
url: string;
|
|
47
|
+
expires: number;
|
|
48
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KV Entity
|
|
3
|
+
* @description Basic KV entity placeholder
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface KVEntity {
|
|
7
|
+
namespaceId: string;
|
|
8
|
+
key: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface KVNamespaceConfig {
|
|
13
|
+
id: string;
|
|
14
|
+
ttl?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface KVEntry {
|
|
18
|
+
key: string;
|
|
19
|
+
value: string;
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface KVListOptions {
|
|
24
|
+
limit?: number;
|
|
25
|
+
cursor?: string;
|
|
26
|
+
prefix?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface KVListResult {
|
|
30
|
+
keys: KVEntry[];
|
|
31
|
+
cursor?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface KVKey {
|
|
35
|
+
name: string;
|
|
36
|
+
metadata?: Record<string, unknown>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R2 Entity
|
|
3
|
+
* @description Basic R2 entity placeholder
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface R2Entity {
|
|
7
|
+
bucketName: string;
|
|
8
|
+
key: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface R2BucketConfig {
|
|
12
|
+
name: string;
|
|
13
|
+
location?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface R2Object {
|
|
17
|
+
key: string;
|
|
18
|
+
size: number;
|
|
19
|
+
uploaded: Date;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface R2UploadResult {
|
|
23
|
+
key: string;
|
|
24
|
+
etag?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface R2ListOptions {
|
|
28
|
+
limit?: number;
|
|
29
|
+
cursor?: string;
|
|
30
|
+
prefix?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface R2ListResult {
|
|
34
|
+
objects: R2Object[];
|
|
35
|
+
cursor?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface R2PutOptions {
|
|
39
|
+
customMetadata?: Record<string, string>;
|
|
40
|
+
httpMetadata?: {
|
|
41
|
+
contentType?: string;
|
|
42
|
+
cacheControl?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface R2PresignedURL {
|
|
47
|
+
url: string;
|
|
48
|
+
expires: number;
|
|
49
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Entity
|
|
3
|
+
* @description Basic Worker entity placeholder
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface WorkerEntity {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WorkerConfig {
|
|
12
|
+
name: string;
|
|
13
|
+
routes?: string[];
|
|
14
|
+
schedule?: string;
|
|
15
|
+
bindings?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface WorkerResponse {
|
|
19
|
+
status: number;
|
|
20
|
+
body?: BodyInit | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IncomingRequestCfProperties {
|
|
24
|
+
colo?: string;
|
|
25
|
+
country?: string;
|
|
26
|
+
httpProtocol?: string;
|
|
27
|
+
tlsVersion?: string;
|
|
28
|
+
tlsCipher?: string;
|
|
29
|
+
asn?: number;
|
|
30
|
+
requestPriority?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type { WorkerRequest as _WorkerRequest } from '../../domains/workers/entities';
|
|
34
|
+
// Re-export WorkerRequest from workers domain
|
|
35
|
+
export { WorkerRequest } from '../../domains/workers/entities';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description Cloudflare Web Analytics configuration and types
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export interface
|
|
6
|
+
export interface WebAnalyticsConfig {
|
|
7
7
|
readonly siteId: string;
|
|
8
8
|
readonly scriptUrl?: string;
|
|
9
9
|
}
|
|
@@ -33,7 +33,7 @@ export interface AnalyticsTimingEvent extends AnalyticsEvent {
|
|
|
33
33
|
readonly label?: string;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export interface
|
|
36
|
+
export interface WebAnalyticsData {
|
|
37
37
|
readonly siteId: string;
|
|
38
38
|
readonly events: readonly AnalyticsEvent[];
|
|
39
39
|
readonly metrics?: AnalyticsMetrics;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware Domain Entities
|
|
3
|
+
* @description Middleware configuration and types for Cloudflare Workers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* CORS configuration
|
|
8
|
+
*/
|
|
9
|
+
export interface CORSConfig {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
allowedOrigins: string[];
|
|
12
|
+
allowedMethods: string[];
|
|
13
|
+
allowedHeaders: string[];
|
|
14
|
+
exposedHeaders?: string[];
|
|
15
|
+
allowCredentials?: boolean;
|
|
16
|
+
maxAge?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Cache configuration
|
|
21
|
+
*/
|
|
22
|
+
export interface CacheConfig {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
defaultTTL: number;
|
|
25
|
+
paths?: Record<string, number>;
|
|
26
|
+
prefix?: string;
|
|
27
|
+
bypassPaths?: string[];
|
|
28
|
+
respectHeaders?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Rate limit configuration
|
|
33
|
+
*/
|
|
34
|
+
export interface RateLimitConfig {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
maxRequests: number;
|
|
37
|
+
window: number;
|
|
38
|
+
by?: 'ip' | 'user' | 'both';
|
|
39
|
+
customKeys?: string[];
|
|
40
|
+
whitelist?: string[];
|
|
41
|
+
response?: {
|
|
42
|
+
status: number;
|
|
43
|
+
message: string;
|
|
44
|
+
retryAfter?: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Authentication configuration
|
|
50
|
+
*/
|
|
51
|
+
export interface AuthConfig {
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
type: 'bearer' | 'apikey' | 'basic';
|
|
54
|
+
token?: string;
|
|
55
|
+
apiKeyHeader?: string;
|
|
56
|
+
apiKeyValue?: string;
|
|
57
|
+
username?: string;
|
|
58
|
+
password?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Security headers configuration
|
|
63
|
+
*/
|
|
64
|
+
export interface SecurityHeadersConfig {
|
|
65
|
+
frameGuard?: boolean;
|
|
66
|
+
contentTypeNosniff?: boolean;
|
|
67
|
+
xssProtection?: boolean;
|
|
68
|
+
strictTransportSecurity?: boolean;
|
|
69
|
+
referrerPolicy?: string;
|
|
70
|
+
contentSecurityPolicy?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* IP filter configuration
|
|
75
|
+
*/
|
|
76
|
+
export interface IPFilterConfig {
|
|
77
|
+
mode: 'whitelist' | 'blacklist';
|
|
78
|
+
ips: string[];
|
|
79
|
+
cidrs?: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Log configuration
|
|
84
|
+
*/
|
|
85
|
+
export interface LogConfig {
|
|
86
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
87
|
+
includeHeaders?: boolean;
|
|
88
|
+
includeBody?: boolean;
|
|
89
|
+
sampleRate?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Health check configuration
|
|
94
|
+
*/
|
|
95
|
+
export interface HealthCheckConfig {
|
|
96
|
+
uptime: number;
|
|
97
|
+
checks: Record<string, () => Promise<boolean>>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Error handler configuration
|
|
102
|
+
*/
|
|
103
|
+
export interface ErrorHandlerConfig {
|
|
104
|
+
debug: boolean;
|
|
105
|
+
logger?: (error: Error) => void;
|
|
106
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware Domain
|
|
3
|
+
* @description Complete middleware integration for Cloudflare Workers
|
|
4
|
+
* Subpath: @umituz/web-cloudflare/middleware
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Entities
|
|
8
|
+
export * from './entities';
|
|
9
|
+
|
|
10
|
+
// Types
|
|
11
|
+
export * from './types';
|
|
12
|
+
|
|
13
|
+
// Services
|
|
14
|
+
export * from './services';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Service
|
|
3
|
+
* @description Authentication middleware for Cloudflare Workers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AuthConfig } from '../entities';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Require authentication
|
|
10
|
+
*/
|
|
11
|
+
export async function requireAuth(
|
|
12
|
+
request: Request,
|
|
13
|
+
config: AuthConfig
|
|
14
|
+
): Promise<Response | null> {
|
|
15
|
+
if (!config.enabled) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const authHeader = request.headers.get('Authorization');
|
|
20
|
+
|
|
21
|
+
if (!authHeader) {
|
|
22
|
+
return new Response(
|
|
23
|
+
JSON.stringify({ error: 'Missing authorization header' }),
|
|
24
|
+
{
|
|
25
|
+
status: 401,
|
|
26
|
+
headers: { 'Content-Type': 'application/json' },
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (config.type) {
|
|
32
|
+
case 'bearer':
|
|
33
|
+
if (!authHeader.startsWith('Bearer ')) {
|
|
34
|
+
return new Response(
|
|
35
|
+
JSON.stringify({ error: 'Invalid authorization type' }),
|
|
36
|
+
{
|
|
37
|
+
status: 401,
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const token = authHeader.substring(7);
|
|
43
|
+
if (token !== config.token) {
|
|
44
|
+
return new Response(
|
|
45
|
+
JSON.stringify({ error: 'Invalid token' }),
|
|
46
|
+
{
|
|
47
|
+
status: 401,
|
|
48
|
+
headers: { 'Content-Type': 'application/json' },
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case 'apikey':
|
|
55
|
+
const apiKey = request.headers.get(config.apiKeyHeader || 'X-API-Key');
|
|
56
|
+
if (apiKey !== config.apiKeyValue) {
|
|
57
|
+
return new Response(
|
|
58
|
+
JSON.stringify({ error: 'Invalid API key' }),
|
|
59
|
+
{
|
|
60
|
+
status: 401,
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case 'basic':
|
|
68
|
+
if (!authHeader.startsWith('Basic ')) {
|
|
69
|
+
return new Response(
|
|
70
|
+
JSON.stringify({ error: 'Invalid authorization type' }),
|
|
71
|
+
{
|
|
72
|
+
status: 401,
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const credentials = atob(authHeader.substring(6));
|
|
78
|
+
const [username, password] = credentials.split(':');
|
|
79
|
+
if (username !== config.username || password !== config.password) {
|
|
80
|
+
return new Response(
|
|
81
|
+
JSON.stringify({ error: 'Invalid credentials' }),
|
|
82
|
+
{
|
|
83
|
+
status: 401,
|
|
84
|
+
headers: { 'Content-Type': 'application/json' },
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Add user context to request
|
|
96
|
+
*/
|
|
97
|
+
export function addUserContext(request: Request, user: {
|
|
98
|
+
id: string;
|
|
99
|
+
[key: string]: unknown;
|
|
100
|
+
}): Request {
|
|
101
|
+
const headers = new Headers(request.headers);
|
|
102
|
+
headers.set('X-User-ID', user.id);
|
|
103
|
+
headers.set('X-User-Context', JSON.stringify(user));
|
|
104
|
+
|
|
105
|
+
return new Request(request.url, {
|
|
106
|
+
method: request.method,
|
|
107
|
+
headers,
|
|
108
|
+
body: request.body,
|
|
109
|
+
});
|
|
110
|
+
}
|