@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-cloudflare",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
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",
@@ -246,7 +246,7 @@ export const minimalConfig: Partial<WorkerConfig> = {
246
246
  export function mergeConfigs<T extends Record<string, any>>(
247
247
  base: T,
248
248
  ...overrides: Array<Partial<Record<string, any>>>
249
- ): T {
249
+ ): Record<string, any> {
250
250
  return overrides.reduce((acc, override) => {
251
251
  return deepMerge(acc, override);
252
252
  }, base);
@@ -255,7 +255,7 @@ export function mergeConfigs<T extends Record<string, any>>(
255
255
  function deepMerge<T extends Record<string, any>>(
256
256
  target: T,
257
257
  source: Partial<Record<string, any>>
258
- ): T {
258
+ ): Record<string, any> {
259
259
  const output = { ...target };
260
260
 
261
261
  if (isObject(target) && isObject(source)) {
@@ -127,17 +127,27 @@ export class AIGatewayService {
127
127
  throw new Error(`Provider error: ${response.statusText}`);
128
128
  }
129
129
 
130
- const data = await response.json();
130
+ const data = await response.json() as {
131
+ id?: string;
132
+ content?: string;
133
+ text?: string;
134
+ output?: string;
135
+ usage?: {
136
+ promptTokens?: number;
137
+ completionTokens?: number;
138
+ totalTokens?: number;
139
+ };
140
+ };
131
141
 
132
142
  return {
133
143
  id: data.id || this.generateId(),
134
144
  provider: provider.id,
135
145
  model: request.model,
136
- content: data.content || data.text || data.output,
137
- usage: data.usage || {
138
- promptTokens: 0,
139
- completionTokens: 0,
140
- totalTokens: 0,
146
+ content: data.content || data.text || data.output || '',
147
+ usage: {
148
+ promptTokens: data.usage?.promptTokens || 0,
149
+ completionTokens: data.usage?.completionTokens || 0,
150
+ totalTokens: data.usage?.totalTokens || 0,
141
151
  },
142
152
  cached: false,
143
153
  timestamp: Date.now(),
@@ -215,9 +225,9 @@ import type {
215
225
 
216
226
  export class WorkersAIService {
217
227
  private env: {
218
- AI?: AiTextGeneration;
228
+ AI?: any;
219
229
  bindings?: {
220
- AI?: Ai;
230
+ AI?: any;
221
231
  };
222
232
  };
223
233
 
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Analytics Service
3
- * @description Cloudflare Web Analytics operations
3
+ * @description Cloudflare Web Analytics operations for Workers runtime
4
4
  */
5
5
 
6
- import type { AnalyticsEvent, AnalyticsPageviewEvent, AnalyticsCustomEvent, AnalyticsData } from "../entities";
6
+ import type { AnalyticsEvent, AnalyticsPageviewEvent, AnalyticsCustomEvent } from "../entities";
7
+ import type { AnalyticsData } from "../../../domain/entities/analytics.entity";
7
8
  import type { IAnalyticsService } from "../../../domain/interfaces/services.interface";
8
9
 
9
10
  export interface AnalyticsClientOptions {
@@ -29,13 +30,9 @@ class AnalyticsService implements IAnalyticsService {
29
30
 
30
31
  async trackEvent(event: AnalyticsEvent): Promise<void> {
31
32
  this.ensureInitialized();
32
-
33
33
  this.eventQueue.push(event);
34
-
35
- // In a browser environment, send to Cloudflare Analytics
36
- if (typeof window !== "undefined" && (window as any)._cfAnalytics) {
37
- (window as any)._cfAnalytics.track(event);
38
- }
34
+ // In Workers runtime, events are queued for batch processing
35
+ // The actual sending would be done via Cloudflare Analytics API
39
36
  }
40
37
 
41
38
  async trackPageview(url: string, title: string, referrer?: string): Promise<void> {
@@ -50,12 +47,10 @@ class AnalyticsService implements IAnalyticsService {
50
47
  await this.trackEvent(event);
51
48
  }
52
49
 
53
- async trackCustom(eventName: string, data?: Record<string, unknown>): Promise<void> {
54
- if (typeof window === "undefined") return;
55
-
50
+ async trackCustom(eventName: string, data?: Record<string, unknown>, url?: string): Promise<void> {
56
51
  const event: AnalyticsCustomEvent = {
57
52
  timestamp: Date.now(),
58
- url: window.location.href,
53
+ url: url || '/workers',
59
54
  eventType: "custom",
60
55
  eventName,
61
56
  eventData: data,
@@ -65,11 +60,9 @@ class AnalyticsService implements IAnalyticsService {
65
60
  }
66
61
 
67
62
  async trackOutboundLink(url: string, linkType?: string): Promise<void> {
68
- if (typeof window === "undefined") return;
69
-
70
63
  const event: AnalyticsCustomEvent = {
71
64
  timestamp: Date.now(),
72
- url: window.location.href,
65
+ url: '/workers',
73
66
  eventType: "custom",
74
67
  eventName: "outbound-link",
75
68
  eventData: {
@@ -82,11 +75,9 @@ class AnalyticsService implements IAnalyticsService {
82
75
  }
83
76
 
84
77
  async trackTiming(name: string, value: number, label?: string): Promise<void> {
85
- if (typeof window === "undefined") return;
86
-
87
78
  const event: AnalyticsEvent = {
88
79
  timestamp: Date.now(),
89
- url: window.location.href,
80
+ url: '/workers',
90
81
  eventType: "timing",
91
82
  eventData: {
92
83
  name,
@@ -101,89 +92,22 @@ class AnalyticsService implements IAnalyticsService {
101
92
  async getAnalytics(): Promise<AnalyticsData> {
102
93
  this.ensureInitialized();
103
94
 
104
- // In a real implementation, this would fetch from Cloudflare Analytics API
105
- // For now, return queued events
106
-
107
95
  return {
108
96
  siteId: this.siteId!,
109
- events: this.eventQueue,
97
+ events: [...this.eventQueue],
110
98
  metrics: {
111
- pageviews: this.eventQueue.filter((e) => e.eventType === "pageview").length,
112
- uniqueVisitors: new Set(this.eventQueue.map((e) => e.url)).size,
99
+ pageviews: this.eventQueue.filter(e => e.eventType === "pageview").length,
100
+ uniqueVisitors: 0, // Would be calculated from stored data
113
101
  },
114
102
  };
115
103
  }
116
104
 
117
- /**
118
- * Get analytics script tag
119
- */
120
- getScriptTag(): string {
121
- this.ensureInitialized();
122
-
123
- const scriptUrl = this.scriptUrl || `https://static.cloudflareinsights.com/beacon.min.js`;
124
-
125
- return `
126
- <script defer src='${scriptUrl}' data-cf-beacon='{"token": "${this.siteId}"}'></script>
127
- `.trim();
128
- }
129
-
130
- /**
131
- * Clear queued events
132
- */
133
- clearEvents(): void {
105
+ async flush(): Promise<void> {
106
+ // In Workers, this would send events to Cloudflare Analytics API
134
107
  this.eventQueue = [];
135
108
  }
136
-
137
- /**
138
- * Get queued events
139
- */
140
- getQueuedEvents(): readonly AnalyticsEvent[] {
141
- return this.eventQueue;
142
- }
143
-
144
- /**
145
- * E-commerce helpers
146
- */
147
- async trackPurchase(transactionId: string, revenue: number, items: readonly { id: string; name: string; price: number; quantity: number }[]): Promise<void> {
148
- await this.trackCustom("purchase", {
149
- transactionId,
150
- revenue,
151
- items,
152
- });
153
- }
154
-
155
- async trackAddToCart(itemId: string, price: number, quantity: number): Promise<void> {
156
- await this.trackCustom("add-to-cart", {
157
- itemId,
158
- price,
159
- quantity,
160
- });
161
- }
162
-
163
- async trackRemoveFromCart(itemId: string, quantity: number): Promise<void> {
164
- await this.trackCustom("remove-from-cart", {
165
- itemId,
166
- quantity,
167
- });
168
- }
169
-
170
- /**
171
- * Engagement helpers
172
- */
173
- async trackScrollDepth(depth: number): Promise<void> {
174
- await this.trackCustom("scroll-depth", { depth });
175
- }
176
-
177
- async trackTimeOnPage(seconds: number): Promise<void> {
178
- await this.trackCustom("time-on-page", { seconds });
179
- }
180
-
181
- async trackEngagement(action: string, target?: string): Promise<void> {
182
- await this.trackCustom("engagement", {
183
- action,
184
- target,
185
- });
186
- }
187
109
  }
188
110
 
111
+ // Export class and singleton instance
112
+ export { AnalyticsService };
189
113
  export const analyticsService = new AnalyticsService();
@@ -3,7 +3,7 @@
3
3
  * @description Abstract interface for Analytics operations
4
4
  */
5
5
 
6
- import type { AnalyticsEvent, AnalyticsData } from '../entities';
6
+ import type { AnalyticsEvent, AnalyticsData } from '../../../domain/entities/analytics.entity';
7
7
 
8
8
  export interface IAnalyticsService {
9
9
  trackEvent(event: AnalyticsEvent): Promise<void>;
@@ -3,7 +3,7 @@
3
3
  * @description Cloudflare D1 database operations
4
4
  */
5
5
 
6
- import type { D1QueryResult, D1BatchResult } from "../entities";
6
+ import type { D1QueryResult, D1BatchResult } from "../../../domain/entities/d1.entity";
7
7
  import type { ID1Service } from "../../../domain/interfaces/services.interface";
8
8
 
9
9
  export interface D1ExecOptions {
@@ -33,14 +33,10 @@ class D1Service implements ID1Service {
33
33
  const result = params ? await stmt.bind(...params).all() : await stmt.all();
34
34
 
35
35
  return {
36
- results: result.results as T[],
37
- success: result.success,
36
+ rows: result.results as T[],
38
37
  meta: result.meta
39
38
  ? {
40
39
  duration: result.meta.duration,
41
- rows_read: result.meta.rows_read,
42
- rows_written: result.meta.rows_written,
43
- last_row_id: result.meta.last_row_id,
44
40
  changes: result.meta.changes,
45
41
  }
46
42
  : undefined,
@@ -48,20 +44,20 @@ class D1Service implements ID1Service {
48
44
  }
49
45
 
50
46
  async batch(
51
- statements: readonly { sql: string; params?: readonly unknown[]; binding?: string }[]
47
+ sqlStatements: readonly { sql: string; params?: readonly unknown[]; binding?: string }[]
52
48
  ): Promise<D1BatchResult> {
53
49
  // Group by binding
54
50
  const grouped = new Map<string, D1PreparedStatement[]>();
55
51
 
56
- for (const stmt of statements) {
57
- const binding = stmt.binding || "default";
58
- const database = this.getDatabase(binding);
52
+ for (const stmt of sqlStatements) {
53
+ const bindingName = stmt.binding || "default";
54
+ const database = this.getDatabase(bindingName);
59
55
 
60
- if (!grouped.has(binding)) {
61
- grouped.set(binding, []);
56
+ if (!grouped.has(bindingName)) {
57
+ grouped.set(bindingName, []);
62
58
  }
63
59
 
64
- grouped.get(binding)!.push(database.prepare(stmt.sql));
60
+ grouped.get(bindingName)!.push(database.prepare(stmt.sql));
65
61
  }
66
62
 
67
63
  // Note: D1 batch requires all statements to be from the same database
@@ -69,18 +65,17 @@ class D1Service implements ID1Service {
69
65
  throw new Error("Batch operations must be from the same database binding");
70
66
  }
71
67
 
72
- const [binding, statements] = Array.from(grouped.entries())[0];
73
- const database = this.getDatabase(binding);
68
+ const [bindingName, preparedStatements] = Array.from(grouped.entries())[0];
69
+ const database = this.getDatabase(bindingName);
74
70
 
75
- const results = await database.batch(statements as D1PreparedStatement[]);
71
+ const results = await database.batch(preparedStatements as D1PreparedStatement[]);
76
72
 
77
73
  return {
78
74
  success: results.every((r) => r.success),
79
75
  results: results.map((r) => ({
80
- results: r.results,
81
- success: r.success,
82
- meta: r.meta,
83
- })),
76
+ rows: r.results,
77
+ meta: r.meta ? { duration: r.meta.duration, changes: r.meta.changes } : undefined,
78
+ })) as D1QueryResult[],
84
79
  };
85
80
  }
86
81
 
@@ -94,7 +89,7 @@ class D1Service implements ID1Service {
94
89
  async findOne<T>(sql: string, params?: readonly unknown[], binding?: string): Promise<T | null> {
95
90
  const result = await this.query<T>(sql, params, binding);
96
91
 
97
- return (result.results[0] as T) ?? null;
92
+ return (result.rows[0] as T) ?? null;
98
93
  }
99
94
 
100
95
  async insert<T>(
@@ -165,7 +160,7 @@ class D1Service implements ID1Service {
165
160
 
166
161
  const result = await this.query<{ name: string }>(sql, [table], binding);
167
162
 
168
- return result.results.length > 0;
163
+ return result.rows.length > 0;
169
164
  }
170
165
 
171
166
  /**
@@ -188,4 +183,6 @@ interface D1Transaction {
188
183
  query<T>(sql: string, params?: readonly unknown[]): Promise<D1QueryResult<T>>;
189
184
  }
190
185
 
186
+ // Export class and singleton instance
187
+ export { D1Service };
191
188
  export const d1Service = new D1Service();
@@ -3,7 +3,7 @@
3
3
  * @description Cloudflare Images operations
4
4
  */
5
5
 
6
- import type { ImageUploadResult, ImageUploadOptions, ImageTransformation, SignedURL } from "../entities";
6
+ import type { ImageUploadResult, ImageUploadOptions, ImageTransformation, SignedURL } from "../../../domain/entities/image.entity";
7
7
  import type { IImageService } from "../../../domain/interfaces/services.interface";
8
8
  import { validationUtils, transformUtils } from "../../../infrastructure/utils";
9
9
  import { MAX_IMAGE_SIZE, ALLOWED_IMAGE_TYPES } from "../../../infrastructure/constants";
@@ -59,14 +59,13 @@ class ImagesService implements IImageService {
59
59
  const formData = new FormData();
60
60
  formData.append("file", file);
61
61
 
62
- if (options?.metadata) {
63
- for (const [key, value] of Object.entries(options.metadata)) {
64
- formData.append(`metadata[${key}]`, value);
65
- }
66
- }
67
-
68
- if (options?.requireSignedURLs !== undefined) {
69
- formData.append("requireSignedURLs", options.requireSignedURLs.toString());
62
+ // Apply transformations if specified in options
63
+ if (options?.width || options?.height || options?.format || options?.quality) {
64
+ const variants: Array<{ width?: number; height?: number; format?: string; quality?: number }> = [];
65
+ if (options.width) variants.push({ width: options.width });
66
+ if (options.height) variants.push({ height: options.height });
67
+ if (options.format) variants.push({ format: options.format });
68
+ if (options.quality) variants.push({ quality: options.quality });
70
69
  }
71
70
 
72
71
  // Upload
@@ -81,19 +80,31 @@ class ImagesService implements IImageService {
81
80
  throw new Error(`Upload failed: ${error}`);
82
81
  }
83
82
 
84
- const data = await response.json();
83
+ const data = await response.json() as {
84
+ result: {
85
+ id: string;
86
+ filename: string;
87
+ uploaded: string;
88
+ variants: string[];
89
+ requireSignedURLs: boolean;
90
+ };
91
+ };
85
92
 
93
+ // Map API response to old entity type
86
94
  return {
87
95
  id: data.result.id,
88
- filename: data.result.filename,
89
- uploaded: new Date(data.result.uploaded),
90
- variants: data.result.variants,
91
- requireSignedURLs: data.result.requireSignedURLs,
96
+ url: data.result.variants[0] || '',
97
+ variants: data.result.variants.map((v) => ({
98
+ width: 0,
99
+ height: 0,
100
+ format: (options?.format || 'jpeg') as 'jpeg' | 'png' | 'webp' | 'avif',
101
+ url: v,
102
+ })),
92
103
  };
93
104
  }
94
105
 
95
106
  async getSignedURL(imageId: string, expiresIn = 3600): Promise<SignedURL> {
96
- const expiresAt = new Date(Date.now() + expiresIn * 1000);
107
+ const expires = Date.now() + expiresIn * 1000;
97
108
 
98
109
  const response = await fetch(`${this.getAPIBaseURL()}/${imageId}`, {
99
110
  method: "POST",
@@ -102,7 +113,7 @@ class ImagesService implements IImageService {
102
113
  "Content-Type": "application/json",
103
114
  },
104
115
  body: JSON.stringify({
105
- expiry: expiresAt.toISOString(),
116
+ expiry: new Date(expires).toISOString(),
106
117
  }),
107
118
  });
108
119
 
@@ -111,11 +122,16 @@ class ImagesService implements IImageService {
111
122
  throw new Error(`Failed to get signed URL: ${error}`);
112
123
  }
113
124
 
114
- const data = await response.json();
125
+ const data = await response.json() as {
126
+ result: {
127
+ signedURLs?: string[];
128
+ variants?: string[];
129
+ };
130
+ };
115
131
 
116
132
  return {
117
- url: data.result.signedURLs?.[0] || data.result.variants?.[0],
118
- expiresAt,
133
+ url: data.result.signedURLs?.[0] || data.result.variants?.[0] || '',
134
+ expires,
119
135
  };
120
136
  }
121
137
 
@@ -164,10 +180,29 @@ class ImagesService implements IImageService {
164
180
  throw new Error(`List failed: ${error}`);
165
181
  }
166
182
 
167
- const data = await response.json();
183
+ const data = await response.json() as {
184
+ result: {
185
+ images: Array<{
186
+ id: string;
187
+ filename: string;
188
+ uploaded: string;
189
+ variants: string[];
190
+ }>;
191
+ totalCount: number;
192
+ };
193
+ };
168
194
 
169
195
  return {
170
- images: data.result.images,
196
+ images: data.result.images.map((img) => ({
197
+ id: img.id,
198
+ url: img.variants[0] || '',
199
+ variants: img.variants.map((v) => ({
200
+ width: 0,
201
+ height: 0,
202
+ format: 'jpeg' as const,
203
+ url: v,
204
+ })),
205
+ })),
171
206
  totalCount: data.result.totalCount,
172
207
  };
173
208
  }
@@ -224,4 +259,6 @@ class ImagesService implements IImageService {
224
259
  }
225
260
  }
226
261
 
262
+ // Export class and singleton instance
263
+ export { ImagesService };
227
264
  export const imagesService = new ImagesService();
@@ -3,7 +3,7 @@
3
3
  * @description Cloudflare KV key-value storage operations
4
4
  */
5
5
 
6
- import type { KVEntry, KVListOptions, KVListResult } from "../entities";
6
+ import type { KVEntry, KVListOptions, KVListResult } from "../../../domain/entities/kv.entity";
7
7
  import type { IKVService } from "../../../domain/interfaces/services.interface";
8
8
  import { validationUtils, cacheUtils } from "../../../infrastructure/utils";
9
9
 
@@ -79,9 +79,12 @@ class KVService implements IKVService {
79
79
  });
80
80
 
81
81
  return {
82
- keys: list.keys,
83
- list_complete: list.list_complete,
84
- cursor: list.cursor,
82
+ keys: list.keys.map((k) => ({
83
+ key: k.name,
84
+ value: '',
85
+ metadata: k.metadata as Record<string, unknown> | undefined,
86
+ })),
87
+ cursor: (list as any).cursor as string | undefined,
85
88
  };
86
89
  }
87
90
 
@@ -107,10 +110,12 @@ class KVService implements IKVService {
107
110
 
108
111
  await Promise.all(
109
112
  list.keys.map(async (key) => {
110
- await this.delete(key.name, binding);
113
+ await this.delete(key.key, binding);
111
114
  })
112
115
  );
113
116
  }
114
117
  }
115
118
 
119
+ // Export class and singleton instance
120
+ export { KVService };
116
121
  export const kvService = new KVService();
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * CORS configuration
8
8
  */
9
- export interface CORSConfig {
9
+ export interface MiddlewareCORSConfig {
10
10
  enabled: boolean;
11
11
  allowedOrigins: string[];
12
12
  allowedMethods: string[];
@@ -19,7 +19,7 @@ export interface CORSConfig {
19
19
  /**
20
20
  * Cache configuration
21
21
  */
22
- export interface CacheConfig {
22
+ export interface MiddlewareCacheConfig {
23
23
  enabled: boolean;
24
24
  defaultTTL: number;
25
25
  paths?: Record<string, number>;
@@ -31,7 +31,7 @@ export interface CacheConfig {
31
31
  /**
32
32
  * Rate limit configuration
33
33
  */
34
- export interface RateLimitConfig {
34
+ export interface MiddlewareRateLimitConfig {
35
35
  enabled: boolean;
36
36
  maxRequests: number;
37
37
  window: number;
@@ -48,7 +48,7 @@ export interface RateLimitConfig {
48
48
  /**
49
49
  * Authentication configuration
50
50
  */
51
- export interface AuthConfig {
51
+ export interface MiddlewareAuthConfig {
52
52
  enabled: boolean;
53
53
  type: 'bearer' | 'apikey' | 'basic';
54
54
  token?: string;
@@ -3,14 +3,18 @@
3
3
  * @description Authentication middleware for Cloudflare Workers
4
4
  */
5
5
 
6
- import type { AuthConfig } from '../entities';
6
+ import type { MiddlewareAuthConfig } from '../entities';
7
+
8
+ // Type aliases for backwards compatibility
9
+ export type { MiddlewareAuthConfig };
10
+ export type AuthConfig = MiddlewareAuthConfig;
7
11
 
8
12
  /**
9
13
  * Require authentication
10
14
  */
11
15
  export async function requireAuth(
12
16
  request: Request,
13
- config: AuthConfig
17
+ config: MiddlewareAuthConfig
14
18
  ): Promise<Response | null> {
15
19
  if (!config.enabled) {
16
20
  return null;
@@ -3,7 +3,11 @@
3
3
  * @description Caching middleware for Cloudflare Workers
4
4
  */
5
5
 
6
- import type { CacheConfig } from '../entities';
6
+ import type { MiddlewareCacheConfig } from '../entities';
7
+
8
+ // Type aliases for backwards compatibility
9
+ export type { MiddlewareCacheConfig };
10
+ export type CacheConfig = MiddlewareCacheConfig;
7
11
 
8
12
  interface CacheEntry {
9
13
  response: Response;
@@ -17,7 +21,7 @@ const cacheStore = new Map<string, CacheEntry>();
17
21
  */
18
22
  export async function cache(
19
23
  request: Request,
20
- config: CacheConfig
24
+ config: MiddlewareCacheConfig
21
25
  ): Promise<Response | null> {
22
26
  if (!config.enabled) {
23
27
  return null;
@@ -46,7 +50,7 @@ export async function cache(
46
50
  export function setCache(
47
51
  request: Request,
48
52
  response: Response,
49
- config: CacheConfig
53
+ config: MiddlewareCacheConfig
50
54
  ): void {
51
55
  if (!config.enabled) {
52
56
  return;
@@ -3,7 +3,10 @@
3
3
  * @description Cross-Origin Resource Sharing middleware for Cloudflare Workers
4
4
  */
5
5
 
6
- import type { CORSConfig } from '../entities';
6
+ import type { MiddlewareCORSConfig } from '../entities';
7
+
8
+ // Type alias for backwards compatibility
9
+ export type CORSConfig = MiddlewareCORSConfig;
7
10
 
8
11
  /**
9
12
  * Add CORS headers to response
@@ -11,7 +14,7 @@ import type { CORSConfig } from '../entities';
11
14
  export function addCorsHeaders(
12
15
  request: Request,
13
16
  response: Response,
14
- config: CORSConfig
17
+ config: MiddlewareCORSConfig
15
18
  ): Response {
16
19
  if (!config.enabled) {
17
20
  return response;
@@ -23,7 +26,7 @@ export function addCorsHeaders(
23
26
  // Check if origin is allowed
24
27
  const allowedOrigin = config.allowedOrigins.includes('*')
25
28
  ? '*'
26
- : config.allowedOrigins.includes(origin || '')
29
+ : origin && config.allowedOrigins.includes(origin)
27
30
  ? origin
28
31
  : config.allowedOrigins[0];
29
32
 
@@ -55,7 +58,7 @@ export function addCorsHeaders(
55
58
  */
56
59
  export async function cors(
57
60
  request: Request,
58
- config: CORSConfig
61
+ config: MiddlewareCORSConfig
59
62
  ): Promise<Response | null> {
60
63
  if (!config.enabled) {
61
64
  return null;
@@ -68,7 +71,7 @@ export async function cors(
68
71
 
69
72
  const allowedOrigin = config.allowedOrigins.includes('*')
70
73
  ? '*'
71
- : config.allowedOrigins.includes(origin || '')
74
+ : origin && config.allowedOrigins.includes(origin)
72
75
  ? origin
73
76
  : config.allowedOrigins[0];
74
77