@vltpkg/vsr 0.2.0 → 0.2.2
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/README.md +1 -1
- package/bin/vsr.ts +1 -1
- package/package.json +7 -2
- package/test/README.md +0 -91
- package/test/access.test.js +0 -760
- package/test/cloudflare-waituntil.test.js +0 -141
- package/test/db.test.js +0 -447
- package/test/dist-tag.test.js +0 -415
- package/test/e2e.test.js +0 -904
- package/test/hono-context.test.js +0 -250
- package/test/integrity-validation.test.js +0 -183
- package/test/json-response.test.js +0 -76
- package/test/manifest-slimming.test.js +0 -449
- package/test/packument-consistency.test.js +0 -351
- package/test/packument-version-range.test.js +0 -144
- package/test/performance.test.js +0 -162
- package/test/route-with-waituntil.test.js +0 -298
- package/test/run-tests.js +0 -151
- package/test/setup-cache-tests.js +0 -190
- package/test/setup.js +0 -64
- package/test/stale-while-revalidate.test.js +0 -273
- package/test/static-assets.test.js +0 -85
- package/test/upstream-routing.test.js +0 -86
- package/test/utils/test-helpers.js +0 -84
- package/test/waituntil-correct.test.js +0 -208
- package/test/waituntil-demo.test.js +0 -138
- package/test/waituntil-readme.md +0 -113
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* This test correctly demonstrates Cloudflare's waitUntil API behavior
|
|
5
|
-
* by properly simulating how background tasks are queued and executed.
|
|
6
|
-
*/
|
|
7
|
-
describe('Cloudflare waitUntil Pattern', () => {
|
|
8
|
-
/**
|
|
9
|
-
* The key issue with the waitUntil API is that when using an IIFE, it executes immediately
|
|
10
|
-
* before waitUntil gets the resulting promise:
|
|
11
|
-
*
|
|
12
|
-
* c.waitUntil((async () => { ... })()); <-- runs immediately!
|
|
13
|
-
*
|
|
14
|
-
* To simulate properly in tests, we need to:
|
|
15
|
-
* 1. Store the function reference, not the promise
|
|
16
|
-
* 2. Execute the function only when we want to run background tasks
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// Simulate the Cloudflare Worker environment
|
|
20
|
-
function createCloudflareEnvironment() {
|
|
21
|
-
// Queue to store background task functions
|
|
22
|
-
const backgroundTaskFns = [];
|
|
23
|
-
|
|
24
|
-
// Execution context with waitUntil method
|
|
25
|
-
const executionCtx = {
|
|
26
|
-
waitUntil: vi.fn((promise) => {
|
|
27
|
-
// Instead of accepting the promise, we'll wrap it
|
|
28
|
-
// This stops the execution until we explicitly run it
|
|
29
|
-
const promiseCreator = () => promise;
|
|
30
|
-
backgroundTaskFns.push(promiseCreator);
|
|
31
|
-
})
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Function to run all queued background tasks
|
|
35
|
-
const runBackgroundTasks = async () => {
|
|
36
|
-
const tasks = [...backgroundTaskFns];
|
|
37
|
-
backgroundTaskFns.length = 0;
|
|
38
|
-
|
|
39
|
-
for (const taskFn of tasks) {
|
|
40
|
-
try {
|
|
41
|
-
await taskFn();
|
|
42
|
-
} catch (error) {
|
|
43
|
-
console.error('Background task error:', error);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
executionCtx,
|
|
50
|
-
runBackgroundTasks,
|
|
51
|
-
backgroundTaskFns
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
it('should run the main handler first, then background tasks', async () => {
|
|
56
|
-
// Setup
|
|
57
|
-
const { executionCtx, runBackgroundTasks, backgroundTaskFns } = createCloudflareEnvironment();
|
|
58
|
-
|
|
59
|
-
// Track execution order
|
|
60
|
-
const executionOrder = [];
|
|
61
|
-
let backgroundRan = false;
|
|
62
|
-
|
|
63
|
-
// Handler function (simulates a Cloudflare Worker)
|
|
64
|
-
async function handler() {
|
|
65
|
-
executionOrder.push('main-start');
|
|
66
|
-
|
|
67
|
-
// Queue a background task - this is how it's done in Cloudflare
|
|
68
|
-
executionCtx.waitUntil(new Promise((resolve) => {
|
|
69
|
-
const bgTask = () => {
|
|
70
|
-
executionOrder.push('background');
|
|
71
|
-
backgroundRan = true;
|
|
72
|
-
resolve();
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// Store the function reference so we can call it later
|
|
76
|
-
// In real code, this would start executing immediately
|
|
77
|
-
// but in our mock, we'll delay it
|
|
78
|
-
setTimeout(bgTask, 0);
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
executionOrder.push('main-end');
|
|
82
|
-
|
|
83
|
-
return { status: 'success' };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Execute the handler
|
|
87
|
-
const result = await handler();
|
|
88
|
-
|
|
89
|
-
// Verify:
|
|
90
|
-
// 1. The main handler completed
|
|
91
|
-
expect(result).toEqual({ status: 'success' });
|
|
92
|
-
|
|
93
|
-
// 2. Main handler started and completed before any background tasks ran
|
|
94
|
-
expect(executionOrder).toEqual(['main-start', 'main-end']);
|
|
95
|
-
expect(backgroundRan).toBe(false);
|
|
96
|
-
|
|
97
|
-
// 3. Background task was queued but not executed yet
|
|
98
|
-
expect(backgroundTaskFns.length).toBe(1);
|
|
99
|
-
|
|
100
|
-
// Now run the background tasks
|
|
101
|
-
await runBackgroundTasks();
|
|
102
|
-
|
|
103
|
-
// 4. Verify the execution order after background tasks were run
|
|
104
|
-
expect(executionOrder).toEqual(['main-start', 'main-end', 'background']);
|
|
105
|
-
expect(backgroundRan).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should implement the stale-while-revalidate caching pattern', async () => {
|
|
109
|
-
// Setup
|
|
110
|
-
const { executionCtx, runBackgroundTasks, backgroundTaskFns } = createCloudflareEnvironment();
|
|
111
|
-
|
|
112
|
-
// Mock cache with stale data
|
|
113
|
-
const cache = {
|
|
114
|
-
data: {
|
|
115
|
-
value: 'stale-data',
|
|
116
|
-
timestamp: Date.now() - 3600000 // 1 hour old (stale)
|
|
117
|
-
},
|
|
118
|
-
get: vi.fn(function() { return this.data; }),
|
|
119
|
-
set: vi.fn(function(newData) { this.data = newData; })
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// Mock external API
|
|
123
|
-
const fetchFromUpstream = vi.fn().mockResolvedValue('fresh-data');
|
|
124
|
-
|
|
125
|
-
// Track execution
|
|
126
|
-
const executionOrder = [];
|
|
127
|
-
let refreshStarted = false;
|
|
128
|
-
let refreshCompleted = false;
|
|
129
|
-
|
|
130
|
-
// Handler function implementing stale-while-revalidate
|
|
131
|
-
async function handleRequest() {
|
|
132
|
-
executionOrder.push('handler-start');
|
|
133
|
-
|
|
134
|
-
// Get data from cache
|
|
135
|
-
const cachedData = cache.get();
|
|
136
|
-
|
|
137
|
-
// Check if data is stale (older than 5 minutes)
|
|
138
|
-
const isStale = Date.now() - cachedData.timestamp > 5 * 60 * 1000;
|
|
139
|
-
|
|
140
|
-
if (isStale) {
|
|
141
|
-
// Queue background refresh without waiting
|
|
142
|
-
executionCtx.waitUntil(new Promise(async (resolve) => {
|
|
143
|
-
// In a real implementation this would run immediately
|
|
144
|
-
// In our test, we'll run it only when specifically requested
|
|
145
|
-
const bgRefresh = async () => {
|
|
146
|
-
refreshStarted = true;
|
|
147
|
-
executionOrder.push('refresh-start');
|
|
148
|
-
const freshData = await fetchFromUpstream();
|
|
149
|
-
cache.set({
|
|
150
|
-
value: freshData,
|
|
151
|
-
timestamp: Date.now()
|
|
152
|
-
});
|
|
153
|
-
executionOrder.push('refresh-end');
|
|
154
|
-
refreshCompleted = true;
|
|
155
|
-
resolve();
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
// Don't call bgRefresh() here - we'll call it during runBackgroundTasks
|
|
159
|
-
setTimeout(bgRefresh, 0);
|
|
160
|
-
}));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Return cached data immediately
|
|
164
|
-
executionOrder.push('handler-end');
|
|
165
|
-
return cachedData.value;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Execute the handler
|
|
169
|
-
const result = await handleRequest();
|
|
170
|
-
|
|
171
|
-
// Verify:
|
|
172
|
-
// 1. We got the stale data immediately
|
|
173
|
-
expect(result).toBe('stale-data');
|
|
174
|
-
|
|
175
|
-
// 2. Handler completed without waiting for refresh
|
|
176
|
-
expect(executionOrder).toEqual(['handler-start', 'handler-end']);
|
|
177
|
-
expect(refreshStarted).toBe(false);
|
|
178
|
-
expect(refreshCompleted).toBe(false);
|
|
179
|
-
|
|
180
|
-
// 3. The upstream API was not called yet
|
|
181
|
-
expect(fetchFromUpstream).not.toHaveBeenCalled();
|
|
182
|
-
|
|
183
|
-
// 4. Background task was queued
|
|
184
|
-
expect(backgroundTaskFns.length).toBe(1);
|
|
185
|
-
|
|
186
|
-
// Now run the background tasks
|
|
187
|
-
await runBackgroundTasks();
|
|
188
|
-
|
|
189
|
-
// 5. Verify the API was called during background refresh
|
|
190
|
-
expect(fetchFromUpstream).toHaveBeenCalled();
|
|
191
|
-
expect(refreshStarted).toBe(true);
|
|
192
|
-
expect(refreshCompleted).toBe(true);
|
|
193
|
-
|
|
194
|
-
// 6. Cache was updated with fresh data
|
|
195
|
-
expect(cache.set).toHaveBeenCalledWith({
|
|
196
|
-
value: 'fresh-data',
|
|
197
|
-
timestamp: expect.any(Number)
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// 7. Execution order shows background refresh happened after handler
|
|
201
|
-
expect(executionOrder).toEqual([
|
|
202
|
-
'handler-start',
|
|
203
|
-
'handler-end',
|
|
204
|
-
'refresh-start',
|
|
205
|
-
'refresh-end'
|
|
206
|
-
]);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* This is a minimalist test to demonstrate the waitUntil API in Cloudflare Workers.
|
|
5
|
-
* The waitUntil API allows a Worker to return a response to the client immediately
|
|
6
|
-
* while continuing to perform work in the background.
|
|
7
|
-
*
|
|
8
|
-
* This is especially useful for caching scenarios where:
|
|
9
|
-
* 1. We want to return cached data to the client as quickly as possible
|
|
10
|
-
* 2. We may need to refresh that cache in the background without blocking the response
|
|
11
|
-
*/
|
|
12
|
-
describe('waitUntil API', () => {
|
|
13
|
-
it('allows background work to continue after a response is sent', async () => {
|
|
14
|
-
// In a Cloudflare Worker, the execution context has a waitUntil method
|
|
15
|
-
// Here we create a mock of that context
|
|
16
|
-
let backgroundWorkCompleted = false;
|
|
17
|
-
|
|
18
|
-
const executionContext = {
|
|
19
|
-
// This mock implementation runs the task after a short delay
|
|
20
|
-
waitUntil: (promise) => {
|
|
21
|
-
// In a real Worker, this would track the promise and ensure
|
|
22
|
-
// the Worker doesn't terminate until all promises are resolved
|
|
23
|
-
setTimeout(async () => {
|
|
24
|
-
try {
|
|
25
|
-
await promise;
|
|
26
|
-
} catch (error) {
|
|
27
|
-
console.error('Background task error:', error);
|
|
28
|
-
}
|
|
29
|
-
}, 0);
|
|
30
|
-
|
|
31
|
-
// waitUntil itself returns immediately
|
|
32
|
-
return Promise.resolve();
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Record start time
|
|
37
|
-
const startTime = Date.now();
|
|
38
|
-
|
|
39
|
-
// This is how you use waitUntil in a real worker handler:
|
|
40
|
-
async function handleRequest(request, env, ctx) {
|
|
41
|
-
// Do some quick work and prepare a response
|
|
42
|
-
const quickData = { message: "Here's your fast response!" };
|
|
43
|
-
|
|
44
|
-
// Queue up some slow work to happen in the background
|
|
45
|
-
ctx.waitUntil((async () => {
|
|
46
|
-
// Simulate slow work (e.g., refreshing a cache, calling external API)
|
|
47
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
48
|
-
backgroundWorkCompleted = true;
|
|
49
|
-
})());
|
|
50
|
-
|
|
51
|
-
// Return the response immediately, without waiting for background work
|
|
52
|
-
return quickData;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Call our handler with the mock execution context
|
|
56
|
-
const response = await handleRequest({}, {}, executionContext);
|
|
57
|
-
|
|
58
|
-
// Measure response time
|
|
59
|
-
const responseTime = Date.now() - startTime;
|
|
60
|
-
|
|
61
|
-
// VERIFY:
|
|
62
|
-
|
|
63
|
-
// 1. We got a quick response (much less than 500ms)
|
|
64
|
-
expect(response).toEqual({ message: "Here's your fast response!" });
|
|
65
|
-
expect(responseTime).toBeLessThan(100);
|
|
66
|
-
|
|
67
|
-
// 2. Background work has NOT completed yet
|
|
68
|
-
expect(backgroundWorkCompleted).toBe(false);
|
|
69
|
-
|
|
70
|
-
// 3. Wait for background work and verify it completes
|
|
71
|
-
await new Promise(resolve => setTimeout(resolve, 600));
|
|
72
|
-
expect(backgroundWorkCompleted).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Conceptual demonstration of the caching pattern
|
|
77
|
-
*/
|
|
78
|
-
it('explains the stale-while-revalidate caching pattern', async () => {
|
|
79
|
-
// This test explains the concept without actually testing code
|
|
80
|
-
|
|
81
|
-
// 1. Mock a waitUntil function
|
|
82
|
-
const waitUntil = vi.fn();
|
|
83
|
-
|
|
84
|
-
// 2. Mock a database record with stale cached data
|
|
85
|
-
const mockCacheResponse = {
|
|
86
|
-
data: { version: '1.0.0' },
|
|
87
|
-
updated: Date.now() - 3600000 // 1 hour old (stale)
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// 3. Log what would happen in an actual implementation
|
|
91
|
-
console.log('In a real implementation, a stale-while-revalidate caching strategy would:');
|
|
92
|
-
console.log('1. Check if data exists in cache');
|
|
93
|
-
console.log('2. If it exists but is stale, queue a background task to refresh it');
|
|
94
|
-
console.log('3. Return the cached data immediately, without waiting for the refresh');
|
|
95
|
-
|
|
96
|
-
// 4. Show what the implementation pattern looks like conceptually
|
|
97
|
-
function conceptualImplementationPattern() {
|
|
98
|
-
// Simulate the pattern without actually executing code
|
|
99
|
-
const code = `
|
|
100
|
-
async function getDataWithBackgroundRefresh(key, db, ctx) {
|
|
101
|
-
// Get data from cache
|
|
102
|
-
const cached = await db.getData(key);
|
|
103
|
-
|
|
104
|
-
if (cached) {
|
|
105
|
-
// Check if cache is stale
|
|
106
|
-
const isStale = Date.now() - cached.updated > FIVE_MINUTES;
|
|
107
|
-
|
|
108
|
-
if (isStale) {
|
|
109
|
-
// Queue background refresh WITHOUT awaiting it
|
|
110
|
-
ctx.waitUntil((async () => {
|
|
111
|
-
const fresh = await fetchFromUpstream(key);
|
|
112
|
-
await db.saveToCache(key, fresh);
|
|
113
|
-
})());
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Return cached data immediately
|
|
117
|
-
return cached.data;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// If not in cache, fetch directly
|
|
121
|
-
const data = await fetchFromUpstream(key);
|
|
122
|
-
await db.saveToCache(key, data);
|
|
123
|
-
return data;
|
|
124
|
-
}`;
|
|
125
|
-
return code;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// 5. Verify the explanation was provided
|
|
129
|
-
expect(conceptualImplementationPattern()).toContain('ctx.waitUntil');
|
|
130
|
-
expect(conceptualImplementationPattern()).toContain('return cached.data');
|
|
131
|
-
|
|
132
|
-
// Note: In a real implementation, we would:
|
|
133
|
-
// 1. Return stale cached data immediately
|
|
134
|
-
// 2. In parallel, refresh the cache in the background
|
|
135
|
-
// 3. The client gets a fast response with slightly stale data
|
|
136
|
-
// 4. The next client gets fresh data
|
|
137
|
-
});
|
|
138
|
-
});
|
package/test/waituntil-readme.md
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# Testing Cloudflare Workers' `waitUntil` API
|
|
2
|
-
|
|
3
|
-
## Background
|
|
4
|
-
|
|
5
|
-
Cloudflare Workers have a special `waitUntil` API that allows background tasks to continue running after a response has been sent. This is particularly useful for implementing caching patterns like "stale-while-revalidate" where:
|
|
6
|
-
|
|
7
|
-
1. We return cached data to the client immediately
|
|
8
|
-
2. We refresh stale data in the background
|
|
9
|
-
3. The next client gets the fresh data
|
|
10
|
-
|
|
11
|
-
## The Challenge with Testing
|
|
12
|
-
|
|
13
|
-
Testing `waitUntil` behavior can be tricky because:
|
|
14
|
-
|
|
15
|
-
1. In real Cloudflare Workers, the main handler function completes and returns a response, *then* the background tasks run.
|
|
16
|
-
2. In a test environment, we need to simulate this behavior correctly.
|
|
17
|
-
3. Most test implementations incorrectly execute background tasks immediately.
|
|
18
|
-
|
|
19
|
-
## Common Testing Mistake
|
|
20
|
-
|
|
21
|
-
The most common mistake when testing `waitUntil` is to use an Immediately Invoked Function Expression (IIFE) that gets executed before `waitUntil` can store it:
|
|
22
|
-
|
|
23
|
-
```javascript
|
|
24
|
-
// PROBLEMATIC: This will execute the fetch immediately, before waitUntil gets it
|
|
25
|
-
ctx.waitUntil((async () => {
|
|
26
|
-
const freshData = await fetch('https://api.example.com/data');
|
|
27
|
-
return freshData;
|
|
28
|
-
})());
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
JavaScript evaluates function arguments before passing them to functions, so the IIFE executes immediately, and only its *resulting Promise* is passed to `waitUntil`.
|
|
32
|
-
|
|
33
|
-
## Correct Testing Approach
|
|
34
|
-
|
|
35
|
-
The correct way to test `waitUntil` is to simulate the Cloudflare Worker runtime:
|
|
36
|
-
|
|
37
|
-
```javascript
|
|
38
|
-
function simulateCloudflareWorker(handler) {
|
|
39
|
-
const bgTasks = [];
|
|
40
|
-
const executionCtx = {
|
|
41
|
-
waitUntil: (promise) => {
|
|
42
|
-
bgTasks.push(promise);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
return async () => {
|
|
47
|
-
// Run the main handler first to completion
|
|
48
|
-
const response = await handler(executionCtx);
|
|
49
|
-
|
|
50
|
-
// Then run background tasks
|
|
51
|
-
for (const task of bgTasks) {
|
|
52
|
-
await task;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return response;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
This approach:
|
|
61
|
-
1. Collects promises passed to `waitUntil` without executing them
|
|
62
|
-
2. Completes the main handler function first
|
|
63
|
-
3. Only then executes the background tasks
|
|
64
|
-
|
|
65
|
-
## Test Files in This Directory
|
|
66
|
-
|
|
67
|
-
1. `waituntil-demo.test.js` - A conceptual explanation of how `waitUntil` works
|
|
68
|
-
2. `cloudflare-waituntil.test.js` - A test that correctly simulates the Cloudflare Worker runtime
|
|
69
|
-
|
|
70
|
-
## Best Practices for Testing `waitUntil`
|
|
71
|
-
|
|
72
|
-
1. **Use a task queue:** Collect tasks to be executed later, don't execute them immediately.
|
|
73
|
-
2. **Complete the main handler first:** Make sure the main handler completes before running background tasks.
|
|
74
|
-
3. **Avoid relying on mock implementations:** Most mock implementations don't correctly simulate the timing behavior.
|
|
75
|
-
4. **Test the end state:** Verify that background tasks eventually complete, not when they run.
|
|
76
|
-
|
|
77
|
-
## Implementing the Pattern in Your Code
|
|
78
|
-
|
|
79
|
-
When implementing the stale-while-revalidate pattern:
|
|
80
|
-
|
|
81
|
-
```javascript
|
|
82
|
-
async function getDataWithBackgroundRefresh(c) {
|
|
83
|
-
// Get data from cache
|
|
84
|
-
const cached = await c.db.getData(key);
|
|
85
|
-
|
|
86
|
-
if (cached) {
|
|
87
|
-
// Check if cache is stale
|
|
88
|
-
const isStale = Date.now() - cached.timestamp > FIVE_MINUTES;
|
|
89
|
-
|
|
90
|
-
if (isStale) {
|
|
91
|
-
// Queue background refresh
|
|
92
|
-
c.executionCtx.waitUntil((async () => {
|
|
93
|
-
try {
|
|
94
|
-
const fresh = await fetch('https://upstream.api/data');
|
|
95
|
-
await c.db.saveData(fresh);
|
|
96
|
-
} catch (err) {
|
|
97
|
-
console.error('Background refresh failed:', err);
|
|
98
|
-
}
|
|
99
|
-
})());
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Return cached data immediately
|
|
103
|
-
return cached.data;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// If not in cache, fetch directly
|
|
107
|
-
const data = await fetch('https://upstream.api/data');
|
|
108
|
-
await c.db.saveData(data);
|
|
109
|
-
return data;
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
This pattern ensures users get a fast response while keeping your data fresh.
|