@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-cloudflare",
3
- "version": "1.4.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
  },
@@ -3,8 +3,8 @@
3
3
  * @description Reusable configuration patterns for different use cases
4
4
  */
5
5
 
6
- import type { AIGatewayConfig } from '../infrastructure/domain/ai-gateway.entity';
7
- import type { WorkflowDefinition } from '../infrastructure/domain/workflows.entity';
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
- fallback: 'workers-ai',
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<T>[]
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>(target: T, source: Partial<T>): T {
245
- const output = { ...target } as T;
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
- if (isObject(source[key as keyof T])) {
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
- Object.assign(output, { [key]: source[key as keyof T] });
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] = deepMerge(target[key as keyof T], source[key as keyof T]);
272
+ (output as any)[key] = sourceValue;
254
273
  }
255
274
  } else {
256
- Object.assign(output, { [key]: source[key as keyof T] });
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, unknown> {
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 AnalyticsConfig {
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 AnalyticsData {
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
+ }