@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.
- package/LICENSE +21 -0
- package/README.md +621 -0
- package/package.json +87 -0
- package/src/config/patterns.ts +469 -0
- package/src/config/types.ts +648 -0
- package/src/domain/entities/analytics.entity.ts +47 -0
- package/src/domain/entities/d1.entity.ts +37 -0
- package/src/domain/entities/image.entity.ts +48 -0
- package/src/domain/entities/index.ts +11 -0
- package/src/domain/entities/kv.entity.ts +34 -0
- package/src/domain/entities/r2.entity.ts +55 -0
- package/src/domain/entities/worker.entity.ts +35 -0
- package/src/domain/index.ts +7 -0
- package/src/domain/interfaces/index.ts +6 -0
- package/src/domain/interfaces/services.interface.ts +82 -0
- package/src/index.ts +53 -0
- package/src/infrastructure/constants/index.ts +13 -0
- package/src/infrastructure/domain/ai-gateway.entity.ts +169 -0
- package/src/infrastructure/domain/workflows.entity.ts +108 -0
- package/src/infrastructure/middleware/index.ts +405 -0
- package/src/infrastructure/router/index.ts +549 -0
- package/src/infrastructure/services/ai-gateway/index.ts +416 -0
- package/src/infrastructure/services/analytics/analytics.service.ts +189 -0
- package/src/infrastructure/services/analytics/index.ts +7 -0
- package/src/infrastructure/services/d1/d1.service.ts +191 -0
- package/src/infrastructure/services/d1/index.ts +7 -0
- package/src/infrastructure/services/images/images.service.ts +227 -0
- package/src/infrastructure/services/images/index.ts +7 -0
- package/src/infrastructure/services/kv/index.ts +7 -0
- package/src/infrastructure/services/kv/kv.service.ts +116 -0
- package/src/infrastructure/services/r2/index.ts +7 -0
- package/src/infrastructure/services/r2/r2.service.ts +164 -0
- package/src/infrastructure/services/workers/index.ts +7 -0
- package/src/infrastructure/services/workers/workers.service.ts +164 -0
- package/src/infrastructure/services/workflows/index.ts +437 -0
- package/src/infrastructure/utils/helpers.ts +732 -0
- package/src/infrastructure/utils/index.ts +6 -0
- package/src/infrastructure/utils/utils.util.ts +150 -0
- package/src/presentation/hooks/cloudflare.hooks.ts +314 -0
- package/src/presentation/hooks/index.ts +6 -0
- package/src/worker.example.ts +41 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility Functions
|
|
3
|
+
* @description Common utility functions for Cloudflare operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Date utilities
|
|
8
|
+
*/
|
|
9
|
+
export const dateUtils = {
|
|
10
|
+
/**
|
|
11
|
+
* Calculate expiration time from TTL
|
|
12
|
+
*/
|
|
13
|
+
getExpirationTime(ttl: number): number {
|
|
14
|
+
return Math.floor(Date.now() / 1000) + ttl;
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert timestamp to Date
|
|
19
|
+
*/
|
|
20
|
+
fromTimestamp(timestamp: number): Date {
|
|
21
|
+
return new Date(timestamp * 1000);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Calculate remaining TTL from expiration
|
|
26
|
+
*/
|
|
27
|
+
getRemainingTTL(expiration: number): number {
|
|
28
|
+
return Math.max(0, expiration - Math.floor(Date.now() / 1000));
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Cache utilities
|
|
34
|
+
*/
|
|
35
|
+
export const cacheUtils = {
|
|
36
|
+
/**
|
|
37
|
+
* Generate cache key from parts
|
|
38
|
+
*/
|
|
39
|
+
generateKey(...parts: readonly string[]): string {
|
|
40
|
+
return parts.join(":");
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse cache key
|
|
45
|
+
*/
|
|
46
|
+
parseKey(key: string): readonly string[] {
|
|
47
|
+
return key.split(":");
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate cache control header
|
|
52
|
+
*/
|
|
53
|
+
getCacheControl(maxAge: number, immutable = false): string {
|
|
54
|
+
const directives = [`public`, `max-age=${maxAge}`];
|
|
55
|
+
if (immutable) {
|
|
56
|
+
directives.push("immutable");
|
|
57
|
+
}
|
|
58
|
+
return directives.join(", ");
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validation utilities
|
|
64
|
+
*/
|
|
65
|
+
export const validationUtils = {
|
|
66
|
+
/**
|
|
67
|
+
* Validate KV key
|
|
68
|
+
*/
|
|
69
|
+
isValidKVKey(key: string): boolean {
|
|
70
|
+
return /^[\w\-.:]+$/.test(key) && key.length <= 512;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate R2 object key
|
|
75
|
+
*/
|
|
76
|
+
isValidR2Key(key: string): boolean {
|
|
77
|
+
return key.length > 0 && key.length <= 1024 && !key.includes("//");
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validate image file type
|
|
82
|
+
*/
|
|
83
|
+
isValidImageType(type: string): boolean {
|
|
84
|
+
return ["image/jpeg", "image/png", "image/gif", "image/webp", "image/avif"].includes(type);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validate image size
|
|
89
|
+
*/
|
|
90
|
+
isValidImageSize(size: number, maxSize = 10 * 1024 * 1024): boolean {
|
|
91
|
+
return size > 0 && size <= maxSize;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Transformation utilities
|
|
97
|
+
*/
|
|
98
|
+
export const transformUtils = {
|
|
99
|
+
/**
|
|
100
|
+
* Convert file to blob
|
|
101
|
+
*/
|
|
102
|
+
async fileToBlob(file: File): Promise<Blob> {
|
|
103
|
+
return file;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Convert blob to array buffer
|
|
108
|
+
*/
|
|
109
|
+
async blobToArrayBuffer(blob: Blob): Promise<ArrayBuffer> {
|
|
110
|
+
return await blob.arrayBuffer();
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Convert readable stream to blob
|
|
115
|
+
*/
|
|
116
|
+
async streamToBlob(stream: ReadableStream): Promise<Blob> {
|
|
117
|
+
const reader = stream.getReader();
|
|
118
|
+
const chunks: Uint8Array[] = [];
|
|
119
|
+
|
|
120
|
+
while (true) {
|
|
121
|
+
const { done, value } = await reader.read();
|
|
122
|
+
if (done) break;
|
|
123
|
+
chunks.push(value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return new Blob(chunks);
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate image transformation URL
|
|
131
|
+
*/
|
|
132
|
+
generateTransformURL(baseUrl: string, transform: {
|
|
133
|
+
width?: number;
|
|
134
|
+
height?: number;
|
|
135
|
+
fit?: string;
|
|
136
|
+
format?: string;
|
|
137
|
+
quality?: number;
|
|
138
|
+
}): string {
|
|
139
|
+
const url = new URL(baseUrl);
|
|
140
|
+
const params = url.searchParams;
|
|
141
|
+
|
|
142
|
+
if (transform.width) params.set("width", transform.width.toString());
|
|
143
|
+
if (transform.height) params.set("height", transform.height.toString());
|
|
144
|
+
if (transform.fit) params.set("fit", transform.fit);
|
|
145
|
+
if (transform.format) params.set("format", transform.format);
|
|
146
|
+
if (transform.quality) params.set("quality", transform.quality.toString());
|
|
147
|
+
|
|
148
|
+
return url.toString();
|
|
149
|
+
},
|
|
150
|
+
};
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare React Hooks
|
|
3
|
+
* @description React hooks for Cloudflare services (client-side)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useState, useEffect } from "react";
|
|
7
|
+
import { kvService } from "../../infrastructure/services/kv";
|
|
8
|
+
import { r2Service } from "../../infrastructure/services/r2";
|
|
9
|
+
import { imagesService } from "../../infrastructure/services/images";
|
|
10
|
+
import { analyticsService } from "../../infrastructure/services/analytics";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cloudflare Worker Hook
|
|
14
|
+
* @description Fetch data from Cloudflare Workers
|
|
15
|
+
*/
|
|
16
|
+
export interface UseCloudflareWorkerOptions {
|
|
17
|
+
readonly enabled?: boolean;
|
|
18
|
+
readonly refetchInterval?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useCloudflareWorker<T = unknown>(
|
|
22
|
+
url: string,
|
|
23
|
+
options?: UseCloudflareWorkerOptions
|
|
24
|
+
) {
|
|
25
|
+
const [data, setData] = useState<T | null>(null);
|
|
26
|
+
const [loading, setLoading] = useState(false);
|
|
27
|
+
const [error, setError] = useState<Error | null>(null);
|
|
28
|
+
|
|
29
|
+
const fetcher = useCallback(async () => {
|
|
30
|
+
if (!options?.enabled ?? true) {
|
|
31
|
+
setLoading(true);
|
|
32
|
+
setError(null);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(url);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
38
|
+
}
|
|
39
|
+
const json = await response.json();
|
|
40
|
+
setData(json);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
setError(err as Error);
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, [url, options?.enabled]);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
fetcher();
|
|
51
|
+
|
|
52
|
+
if (options?.refetchInterval) {
|
|
53
|
+
const interval = setInterval(fetcher, options.refetchInterval);
|
|
54
|
+
return () => clearInterval(interval);
|
|
55
|
+
}
|
|
56
|
+
}, [fetcher, options?.refetchInterval]);
|
|
57
|
+
|
|
58
|
+
return { data, loading, error, refetch: fetcher };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Cloudflare KV Hook
|
|
63
|
+
* @description Read from Cloudflare KV (client-side, via Worker API)
|
|
64
|
+
*/
|
|
65
|
+
export interface UseCloudflareKVOptions {
|
|
66
|
+
readonly apiURL: string;
|
|
67
|
+
readonly enabled?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useCloudflareKV<T = unknown>(
|
|
71
|
+
key: string,
|
|
72
|
+
options: UseCloudflareKVOptions
|
|
73
|
+
) {
|
|
74
|
+
const [data, setData] = useState<T | null>(null);
|
|
75
|
+
const [loading, setLoading] = useState(false);
|
|
76
|
+
const [error, setError] = useState<Error | null>(null);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!options.enabled ?? true) {
|
|
80
|
+
setLoading(true);
|
|
81
|
+
setError(null);
|
|
82
|
+
|
|
83
|
+
fetch(`${options.apiURL}/kv/${key}`)
|
|
84
|
+
.then((res) => {
|
|
85
|
+
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
|
|
86
|
+
return res.json();
|
|
87
|
+
})
|
|
88
|
+
.then((json) => setData(json.value))
|
|
89
|
+
.catch((err) => setError(err))
|
|
90
|
+
.finally(() => setLoading(false));
|
|
91
|
+
}
|
|
92
|
+
}, [key, options.apiURL, options.enabled]);
|
|
93
|
+
|
|
94
|
+
return { data, loading, error };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Cloudflare R2 Hook
|
|
99
|
+
* @description Upload files to R2 (client-side, via Worker API)
|
|
100
|
+
*/
|
|
101
|
+
export interface UseCloudflareR2Options {
|
|
102
|
+
readonly uploadURL: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function useCloudflareR2(options: UseCloudflareR2Options) {
|
|
106
|
+
const [uploading, setUploading] = useState(false);
|
|
107
|
+
const [progress, setProgress] = useState(0);
|
|
108
|
+
const [error, setError] = useState<Error | null>(null);
|
|
109
|
+
|
|
110
|
+
const upload = useCallback(
|
|
111
|
+
async (file: File, key?: string) => {
|
|
112
|
+
setUploading(true);
|
|
113
|
+
setProgress(0);
|
|
114
|
+
setError(null);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const formData = new FormData();
|
|
118
|
+
formData.append("file", file);
|
|
119
|
+
if (key) formData.append("key", key);
|
|
120
|
+
|
|
121
|
+
const xhr = new XMLHttpRequest();
|
|
122
|
+
|
|
123
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
124
|
+
if (e.lengthComputable) {
|
|
125
|
+
setProgress((e.loaded / e.total) * 100);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const promise = new Promise<{ key: string; url: string }>((resolve, reject) => {
|
|
130
|
+
xhr.addEventListener("load", () => {
|
|
131
|
+
if (xhr.status === 200) {
|
|
132
|
+
const data = JSON.parse(xhr.responseText);
|
|
133
|
+
resolve(data);
|
|
134
|
+
} else {
|
|
135
|
+
reject(new Error(`Upload failed: ${xhr.statusText}`));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
xhr.addEventListener("error", () => {
|
|
140
|
+
reject(new Error("Upload failed"));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
xhr.open("POST", options.uploadURL);
|
|
144
|
+
xhr.send(formData);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return await promise;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
setError(err as Error);
|
|
150
|
+
throw err;
|
|
151
|
+
} finally {
|
|
152
|
+
setUploading(false);
|
|
153
|
+
setProgress(0);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
[options.uploadURL]
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
return { upload, uploading, progress, error };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Cloudflare D1 Hook
|
|
164
|
+
* @description Query D1 database (client-side, via Worker API)
|
|
165
|
+
*/
|
|
166
|
+
export interface UseCloudflareD1Options {
|
|
167
|
+
readonly apiURL: string;
|
|
168
|
+
readonly enabled?: boolean;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function useCloudflareD1<T = unknown>(
|
|
172
|
+
query: string,
|
|
173
|
+
params?: readonly unknown[],
|
|
174
|
+
options?: UseCloudflareD1Options
|
|
175
|
+
) {
|
|
176
|
+
const [data, setData] = useState<T[] | null>(null);
|
|
177
|
+
const [loading, setLoading] = useState(false);
|
|
178
|
+
const [error, setError] = useState<Error | null>(null);
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (!options?.enabled ?? true) {
|
|
182
|
+
setLoading(true);
|
|
183
|
+
setError(null);
|
|
184
|
+
|
|
185
|
+
fetch(`${options?.apiURL}/d1/query`, {
|
|
186
|
+
method: "POST",
|
|
187
|
+
headers: { "Content-Type": "application/json" },
|
|
188
|
+
body: JSON.stringify({ query, params }),
|
|
189
|
+
})
|
|
190
|
+
.then((res) => {
|
|
191
|
+
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
|
|
192
|
+
return res.json();
|
|
193
|
+
})
|
|
194
|
+
.then((json) => setData(json.results))
|
|
195
|
+
.catch((err) => setError(err))
|
|
196
|
+
.finally(() => setLoading(false));
|
|
197
|
+
}
|
|
198
|
+
}, [query, params, options?.apiURL, options?.enabled]);
|
|
199
|
+
|
|
200
|
+
return { data, loading, error };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Cloudflare Images Hook
|
|
205
|
+
* @description Upload and manage images
|
|
206
|
+
*/
|
|
207
|
+
export interface UseCloudflareImagesOptions {
|
|
208
|
+
readonly accountId?: string;
|
|
209
|
+
readonly apiToken?: string;
|
|
210
|
+
readonly uploadURL?: string;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function useCloudflareImages(options?: UseCloudflareImagesOptions) {
|
|
214
|
+
const [uploading, setUploading] = useState(false);
|
|
215
|
+
const [error, setError] = useState<Error | null>(null);
|
|
216
|
+
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
if (options?.accountId && options?.apiToken) {
|
|
219
|
+
imagesService.initialize({
|
|
220
|
+
accountId: options.accountId,
|
|
221
|
+
apiToken: options.apiToken,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}, [options?.accountId, options?.apiToken]);
|
|
225
|
+
|
|
226
|
+
const upload = useCallback(
|
|
227
|
+
async (file: File, metadata?: Record<string, string>) => {
|
|
228
|
+
setUploading(true);
|
|
229
|
+
setError(null);
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
if (options?.uploadURL) {
|
|
233
|
+
// Upload via Worker proxy
|
|
234
|
+
const formData = new FormData();
|
|
235
|
+
formData.append("file", file);
|
|
236
|
+
if (metadata) {
|
|
237
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
238
|
+
formData.append(`metadata[${key}]`, value);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const response = await fetch(options.uploadURL, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
body: formData,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
throw new Error(`Upload failed: ${response.statusText}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return await response.json();
|
|
252
|
+
} else {
|
|
253
|
+
// Direct upload to Cloudflare Images
|
|
254
|
+
return await imagesService.upload(file, { metadata });
|
|
255
|
+
}
|
|
256
|
+
} catch (err) {
|
|
257
|
+
setError(err as Error);
|
|
258
|
+
throw err;
|
|
259
|
+
} finally {
|
|
260
|
+
setUploading(false);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
[options?.uploadURL]
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const getTransformedURL = useCallback(
|
|
267
|
+
(imageId: string, transform: {
|
|
268
|
+
width?: number;
|
|
269
|
+
height?: number;
|
|
270
|
+
fit?: string;
|
|
271
|
+
format?: string;
|
|
272
|
+
quality?: number;
|
|
273
|
+
}) => {
|
|
274
|
+
return imagesService.getTransformedURL(imageId, transform);
|
|
275
|
+
},
|
|
276
|
+
[]
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
return { upload, uploading, error, getTransformedURL };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Cloudflare Analytics Hook
|
|
284
|
+
* @description Track analytics events
|
|
285
|
+
*/
|
|
286
|
+
export interface UseCloudflareAnalyticsOptions {
|
|
287
|
+
readonly siteId: string;
|
|
288
|
+
readonly scriptUrl?: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function useCloudflareAnalytics(options: UseCloudflareAnalyticsOptions) {
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
analyticsService.initialize({
|
|
294
|
+
siteId: options.siteId,
|
|
295
|
+
scriptUrl: options.scriptUrl,
|
|
296
|
+
});
|
|
297
|
+
}, [options.siteId, options.scriptUrl]);
|
|
298
|
+
|
|
299
|
+
const trackPageview = useCallback((title: string, referrer?: string) => {
|
|
300
|
+
if (typeof window !== "undefined") {
|
|
301
|
+
analyticsService.trackPageview(window.location.href, title, referrer);
|
|
302
|
+
}
|
|
303
|
+
}, []);
|
|
304
|
+
|
|
305
|
+
const trackEvent = useCallback((eventName: string, data?: Record<string, unknown>) => {
|
|
306
|
+
analyticsService.trackCustom(eventName, data);
|
|
307
|
+
}, []);
|
|
308
|
+
|
|
309
|
+
const getScriptTag = useCallback(() => {
|
|
310
|
+
return analyticsService.getScriptTag();
|
|
311
|
+
}, []);
|
|
312
|
+
|
|
313
|
+
return { trackPageview, trackEvent, getScriptTag };
|
|
314
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Worker Example
|
|
3
|
+
* @description Example worker using @umituz/web-cloudflare package
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { workersService, kvService, r2Service } from "@umituz/web-cloudflare";
|
|
7
|
+
|
|
8
|
+
// Configure routes
|
|
9
|
+
workersService.route("/", async () => {
|
|
10
|
+
return workersService.json({ message: "Hello from edge!" });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
workersService.route("/api/hello", async (request) => {
|
|
14
|
+
const { name } = await request.json();
|
|
15
|
+
|
|
16
|
+
return workersService.json({ message: `Hello, ${name}!` });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
workersService.route("/api/cache/:key", async (request, env) => {
|
|
20
|
+
const url = new URL(request.url);
|
|
21
|
+
const key = url.pathname.split("/").pop();
|
|
22
|
+
|
|
23
|
+
if (request.method === "GET") {
|
|
24
|
+
const value = await kvService.get(key, env?.KV ? "KV" : undefined);
|
|
25
|
+
return workersService.json({ key, value });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (request.method === "POST") {
|
|
29
|
+
const { value } = await request.json();
|
|
30
|
+
await kvService.put(key, value, { ttl: 3600 });
|
|
31
|
+
return workersService.json({ success: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return workersService.error("Method not allowed", 405);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Export for Cloudflare Workers
|
|
38
|
+
export default {
|
|
39
|
+
fetch: (request: Request, env: Env, ctx: ExecutionContext) =>
|
|
40
|
+
workersService.fetch(request, env, ctx),
|
|
41
|
+
};
|