@vinext/cloudflare 1.0.0-beta.7 → 1.0.0-beta.8
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 +55 -22
- package/dist/cache/cdn-adapter-config.d.ts +16 -8
- package/dist/cache/cdn-adapter-config.js +57 -17
- package/dist/cache/cdn-adapter.d.ts +25 -24
- package/dist/cache/cdn-adapter.js +24 -20
- package/dist/cache/cdn-adapter.runtime.d.ts +9 -3
- package/dist/cache/cdn-adapter.runtime.js +63 -40
- package/dist/cache/cdn-adapter.worker.d.ts +30 -0
- package/dist/cache/cdn-adapter.worker.js +371 -0
- package/dist/cache/cdn-build-id.d.ts +4 -3
- package/dist/cache/cdn-build-id.js +5 -1
- package/dist/cache/kv-data-adapter.d.ts +21 -5
- package/dist/cache/kv-data-adapter.runtime.d.ts +38 -4
- package/dist/cache/kv-data-adapter.runtime.js +115 -41
- package/dist/cache/kv-key.d.ts +4 -5
- package/dist/cache/response-store-adapter.d.ts +37 -0
- package/dist/cache/response-store-adapter.js +39 -0
- package/dist/cache/response-store-adapter.self-contained.worker.d.ts +17 -0
- package/dist/cache/response-store-adapter.self-contained.worker.js +10 -0
- package/dist/cache/response-store-adapter.service-binding.worker.d.ts +15 -0
- package/dist/cache/response-store-adapter.service-binding.worker.js +9 -0
- package/dist/cache/response-store-adapter.worker.d.ts +14 -0
- package/dist/cache/response-store-adapter.worker.js +254 -0
- package/dist/cache/response-store-cdn.runtime.d.ts +5 -0
- package/dist/cache/response-store-cdn.runtime.js +41 -0
- package/dist/cache/response-store-data.runtime.d.ts +34 -0
- package/dist/cache/response-store-data.runtime.js +289 -0
- package/dist/cacheability-artifact.d.ts +2 -3
- package/dist/cacheability-artifact.js +27 -1
- package/dist/cacheability-manifest-limits.d.ts +3 -4
- package/dist/cacheability-manifest-limits.js +1 -1
- package/dist/cacheability-probe.d.ts +8 -9
- package/dist/cacheability-probe.js +255 -91
- package/dist/cdn-warm.d.ts +31 -25
- package/dist/cdn-warm.js +208 -50
- package/dist/deploy-config.d.ts +11 -12
- package/dist/deploy-config.js +8 -3
- package/dist/deploy-help.d.ts +3 -4
- package/dist/deploy.d.ts +23 -24
- package/dist/deploy.js +44 -11
- package/dist/images/images-optimizer.d.ts +3 -4
- package/dist/prerender-kv-populate.d.ts +3 -4
- package/dist/prerender-kv-populate.js +3 -2
- package/dist/tpr.d.ts +9 -10
- package/dist/tpr.js +1 -1
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +17 -17
- package/dist/version-deploy.js +6 -3
- package/dist/worker-deployment-url.d.ts +3 -4
- package/dist/workers-dev-url.d.ts +2 -3
- package/dist/wrangler-version-metadata.d.ts +2 -3
- package/package.json +13 -7
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import createCloudflareCdnCacheAdapter from "./cdn-adapter.runtime.js";
|
|
2
|
+
import { CdnCacheAdapter } from "vinext/shims/cdn-cache";
|
|
3
|
+
//#region src/cache/response-store-cdn.runtime.d.ts
|
|
4
|
+
export default function createResponseStoreCdnCacheAdapter(context: Parameters<typeof createCloudflareCdnCacheAdapter>[0]): CdnCacheAdapter;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import createCloudflareCdnCacheAdapter from "./cdn-adapter.runtime.js";
|
|
2
|
+
import { captureResponseStoreRscData, deferResponseStoreAdmission } from "./response-store-data.runtime.js";
|
|
3
|
+
//#region src/cache/response-store-cdn.runtime.ts
|
|
4
|
+
/** Response Store owns page serving and SWR; vinext only emits admitted response policy. */
|
|
5
|
+
var ResponseStoreCdnCacheAdapter = class {
|
|
6
|
+
headers;
|
|
7
|
+
ownsBackgroundRevalidation = false;
|
|
8
|
+
requiresCompletedResponseAdmission = true;
|
|
9
|
+
responsePolicy;
|
|
10
|
+
constructor(headers) {
|
|
11
|
+
this.headers = headers;
|
|
12
|
+
this.responsePolicy = headers.responsePolicy;
|
|
13
|
+
}
|
|
14
|
+
get(...args) {
|
|
15
|
+
return this.headers.get(...args);
|
|
16
|
+
}
|
|
17
|
+
set(...args) {
|
|
18
|
+
return this.headers.set(...args);
|
|
19
|
+
}
|
|
20
|
+
validateRequest(request) {
|
|
21
|
+
return this.headers.validateRequest?.(request) ?? null;
|
|
22
|
+
}
|
|
23
|
+
buildResponseIdentityHeaders() {
|
|
24
|
+
return this.headers.buildResponseIdentityHeaders?.() ?? {};
|
|
25
|
+
}
|
|
26
|
+
buildResponseHeaders(input) {
|
|
27
|
+
return this.headers.buildResponseHeaders(input);
|
|
28
|
+
}
|
|
29
|
+
deferCompletedPageResponseAdmission(response, complete) {
|
|
30
|
+
return deferResponseStoreAdmission(response, complete);
|
|
31
|
+
}
|
|
32
|
+
captureAppPageRscData(rscData) {
|
|
33
|
+
captureResponseStoreRscData(rscData);
|
|
34
|
+
}
|
|
35
|
+
async revalidateTag() {}
|
|
36
|
+
};
|
|
37
|
+
function createResponseStoreCdnCacheAdapter(context) {
|
|
38
|
+
return new ResponseStoreCdnCacheAdapter(createCloudflareCdnCacheAdapter(context));
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { createResponseStoreCdnCacheAdapter as default };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { WorkersResponseStore } from "@cloudflare/workers-response-store";
|
|
2
|
+
import { CacheHandler, CacheHandlerValue, IncrementalCacheValue } from "vinext/shims/cache";
|
|
3
|
+
//#region src/cache/response-store-data.runtime.d.ts
|
|
4
|
+
export type ResponseStoreInvocationCapture = {
|
|
5
|
+
admittedResponse?: Promise<Response>;
|
|
6
|
+
captureRscData?: boolean;
|
|
7
|
+
rscData?: Promise<ArrayBuffer>;
|
|
8
|
+
streamResponse?: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare const DATA_REVALIDATOR_ID = "vinext:data";
|
|
11
|
+
declare const CACHE_FUNCTION_REVALIDATOR_ID = "vinext:cache-function";
|
|
12
|
+
/** Connect the generated Cloudflare worker entry to the configured data adapter. */
|
|
13
|
+
export declare function setResponseStore(store: WorkersResponseStore): void;
|
|
14
|
+
/** Associate data-cache writes with the response-stage invocation that produced them. */
|
|
15
|
+
export declare function runWithResponseStoreInvocation<T>(serialized: string, replayable: boolean, callback: () => T, capture?: ResponseStoreInvocationCapture): T;
|
|
16
|
+
/** Retain the RSC side stream only when the outer response-store invocation requested it. */
|
|
17
|
+
export declare function captureResponseStoreRscData(rscData: Promise<ArrayBuffer>): void;
|
|
18
|
+
/** Stream the foreground body while retaining an independent admission branch. */
|
|
19
|
+
export declare function deferResponseStoreAdmission(response: Response, complete: (response: Response) => Promise<Response>): Response | null;
|
|
20
|
+
/** Re-render an invocation and return the exact rewritten data-cache entry. */
|
|
21
|
+
export declare function captureResponseStoreDataRegeneration(key: string, callback: () => Promise<void>): Promise<Response>;
|
|
22
|
+
export declare class WorkersResponseStoreCacheHandler implements CacheHandler {
|
|
23
|
+
private readonly store;
|
|
24
|
+
private readonly tagExpirations;
|
|
25
|
+
constructor(store?: WorkersResponseStore);
|
|
26
|
+
get(key: string, context?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
|
|
27
|
+
set(key: string, value: IncrementalCacheValue | null, context?: Record<string, unknown>): Promise<void>;
|
|
28
|
+
revalidateTag(tags: string | string[], durations?: {
|
|
29
|
+
expire?: number;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export default function createResponseStoreDataCacheAdapter(): CacheHandler;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { CACHE_FUNCTION_REVALIDATOR_ID, DATA_REVALIDATOR_ID };
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { encodeCloudflareCacheTag } from "./cdn-adapter.runtime.js";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { cacheForRequest } from "vinext/cache";
|
|
5
|
+
//#region src/cache/response-store-data.runtime.ts
|
|
6
|
+
const ARRAY_BUFFER_MARKER = "$vinextArrayBuffer";
|
|
7
|
+
const CACHE_MAX_AGE_SECONDS = 31536e4;
|
|
8
|
+
const CACHE_MAX_AGE = `public, max-age=${CACHE_MAX_AGE_SECONDS}`;
|
|
9
|
+
const DATA_ENTRY_PATH = "__vinext_data";
|
|
10
|
+
const REPLAYABLE_HEADER = "X-Vinext-Response-Store-Replayable";
|
|
11
|
+
const MAX_CACHE_TAG_HEADER_BYTES = 16384;
|
|
12
|
+
const DATA_REVALIDATOR_ID = "vinext:data";
|
|
13
|
+
const CACHE_FUNCTION_REVALIDATOR_ID = "vinext:cache-function";
|
|
14
|
+
const invocationStorage = new AsyncLocalStorage();
|
|
15
|
+
const regenerationStorage = new AsyncLocalStorage();
|
|
16
|
+
let responseStore;
|
|
17
|
+
/** Connect the generated Cloudflare worker entry to the configured data adapter. */
|
|
18
|
+
function setResponseStore(store) {
|
|
19
|
+
responseStore = store;
|
|
20
|
+
}
|
|
21
|
+
/** Associate data-cache writes with the response-stage invocation that produced them. */
|
|
22
|
+
function runWithResponseStoreInvocation(serialized, replayable, callback, capture) {
|
|
23
|
+
return invocationStorage.run({
|
|
24
|
+
capture,
|
|
25
|
+
replayable,
|
|
26
|
+
serialized
|
|
27
|
+
}, callback);
|
|
28
|
+
}
|
|
29
|
+
/** Retain the RSC side stream only when the outer response-store invocation requested it. */
|
|
30
|
+
function captureResponseStoreRscData(rscData) {
|
|
31
|
+
const capture = invocationStorage.getStore()?.capture;
|
|
32
|
+
if (capture?.captureRscData) capture.rscData = rscData;
|
|
33
|
+
else rscData.catch(() => {});
|
|
34
|
+
}
|
|
35
|
+
/** Stream the foreground body while retaining an independent admission branch. */
|
|
36
|
+
function deferResponseStoreAdmission(response, complete) {
|
|
37
|
+
const capture = invocationStorage.getStore()?.capture;
|
|
38
|
+
if (!capture?.streamResponse || !response.body) return null;
|
|
39
|
+
const source = response.body.getReader();
|
|
40
|
+
const admission = new TransformStream(void 0, { highWaterMark: 0 }, { highWaterMark: 0 });
|
|
41
|
+
const writer = admission.writable.getWriter();
|
|
42
|
+
let admissionOpen = true;
|
|
43
|
+
let foregroundCancelled = false;
|
|
44
|
+
let foregroundCancelReason;
|
|
45
|
+
let writerReleased = false;
|
|
46
|
+
const releaseSource = () => {
|
|
47
|
+
try {
|
|
48
|
+
source.releaseLock();
|
|
49
|
+
} catch {}
|
|
50
|
+
};
|
|
51
|
+
const releaseWriter = () => {
|
|
52
|
+
if (writerReleased) return;
|
|
53
|
+
writerReleased = true;
|
|
54
|
+
try {
|
|
55
|
+
writer.releaseLock();
|
|
56
|
+
} catch {}
|
|
57
|
+
};
|
|
58
|
+
const cancelSource = (reason) => {
|
|
59
|
+
try {
|
|
60
|
+
const cancellation = source.cancel(reason);
|
|
61
|
+
releaseSource();
|
|
62
|
+
cancellation.catch(() => {}).finally(releaseSource);
|
|
63
|
+
} catch {
|
|
64
|
+
releaseSource();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const finishAdmission = async (failure) => {
|
|
68
|
+
if (!admissionOpen) return;
|
|
69
|
+
admissionOpen = false;
|
|
70
|
+
if (failure && foregroundCancelled) cancelSource(foregroundCancelReason);
|
|
71
|
+
try {
|
|
72
|
+
if (failure) await writer.abort(failure.reason);
|
|
73
|
+
else await writer.close();
|
|
74
|
+
} catch {} finally {
|
|
75
|
+
releaseWriter();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const writeAdmission = async (chunk) => {
|
|
79
|
+
if (!admissionOpen) return;
|
|
80
|
+
try {
|
|
81
|
+
await writer.write(chunk);
|
|
82
|
+
} catch {
|
|
83
|
+
admissionOpen = false;
|
|
84
|
+
releaseWriter();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const foreground = new ReadableStream({
|
|
88
|
+
async pull(controller) {
|
|
89
|
+
try {
|
|
90
|
+
const result = await source.read();
|
|
91
|
+
if (result.done) {
|
|
92
|
+
releaseSource();
|
|
93
|
+
controller.close();
|
|
94
|
+
await finishAdmission();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
controller.enqueue(result.value);
|
|
98
|
+
await writeAdmission(result.value);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
releaseSource();
|
|
101
|
+
controller.error(error);
|
|
102
|
+
await finishAdmission({ reason: error });
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
cancel(reason) {
|
|
106
|
+
foregroundCancelled = true;
|
|
107
|
+
foregroundCancelReason = reason;
|
|
108
|
+
if (!admissionOpen) {
|
|
109
|
+
cancelSource(reason);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
(async () => {
|
|
113
|
+
try {
|
|
114
|
+
while (true) {
|
|
115
|
+
const result = await source.read();
|
|
116
|
+
if (result.done) break;
|
|
117
|
+
await writeAdmission(result.value);
|
|
118
|
+
if (!admissionOpen) {
|
|
119
|
+
cancelSource(reason);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
releaseSource();
|
|
124
|
+
await finishAdmission();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
releaseSource();
|
|
127
|
+
await finishAdmission({ reason: error });
|
|
128
|
+
}
|
|
129
|
+
})();
|
|
130
|
+
}
|
|
131
|
+
}, { highWaterMark: 0 });
|
|
132
|
+
capture.admittedResponse = complete(new Response(admission.readable, response));
|
|
133
|
+
capture.admittedResponse.catch((error) => finishAdmission({ reason: error }));
|
|
134
|
+
writer.closed.catch((error) => finishAdmission({ reason: error }));
|
|
135
|
+
return new Response(foreground, response);
|
|
136
|
+
}
|
|
137
|
+
/** Re-render an invocation and return the exact rewritten data-cache entry. */
|
|
138
|
+
async function captureResponseStoreDataRegeneration(key, callback) {
|
|
139
|
+
const scope = { targetKey: key };
|
|
140
|
+
await regenerationStorage.run(scope, callback);
|
|
141
|
+
if (!scope.captured) throw new Error(`vinext response-store regeneration did not rewrite data key ${key}`);
|
|
142
|
+
return scope.captured;
|
|
143
|
+
}
|
|
144
|
+
function readCacheControlField(context, field) {
|
|
145
|
+
const cacheControl = context?.cacheControl;
|
|
146
|
+
const value = cacheControl && typeof cacheControl === "object" ? Reflect.get(cacheControl, field) : context?.[field];
|
|
147
|
+
return typeof value === "number" || field === "revalidate" && value === false ? value : void 0;
|
|
148
|
+
}
|
|
149
|
+
async function digest(value) {
|
|
150
|
+
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
151
|
+
return Buffer.from(bytes).toString("hex");
|
|
152
|
+
}
|
|
153
|
+
async function cacheRequest(key, path = DATA_ENTRY_PATH) {
|
|
154
|
+
return new Request(`https://vinext-data-cache.invalid/${path}/${await digest(key)}`);
|
|
155
|
+
}
|
|
156
|
+
function readStringArrayField(context, field) {
|
|
157
|
+
const value = context?.[field];
|
|
158
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
159
|
+
}
|
|
160
|
+
function serialize(entry) {
|
|
161
|
+
return JSON.stringify(entry, (_key, value) => value instanceof ArrayBuffer ? { [ARRAY_BUFFER_MARKER]: Buffer.from(value).toString("base64") } : value);
|
|
162
|
+
}
|
|
163
|
+
function deserialize(value) {
|
|
164
|
+
let entry;
|
|
165
|
+
try {
|
|
166
|
+
entry = JSON.parse(value, (_key, item) => {
|
|
167
|
+
if (item && typeof item === "object" && typeof Reflect.get(item, ARRAY_BUFFER_MARKER) === "string") {
|
|
168
|
+
const bytes = Buffer.from(Reflect.get(item, ARRAY_BUFFER_MARKER), "base64");
|
|
169
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
170
|
+
}
|
|
171
|
+
return item;
|
|
172
|
+
});
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
if (!entry || typeof entry !== "object" || typeof Reflect.get(entry, "lastModified") !== "number") return null;
|
|
177
|
+
const cachedValue = Reflect.get(entry, "value");
|
|
178
|
+
if (cachedValue !== null && (!cachedValue || typeof cachedValue !== "object" || typeof Reflect.get(cachedValue, "kind") !== "string")) return null;
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
function cachePolicy(revalidate, expire) {
|
|
182
|
+
if (revalidate === false || revalidate === void 0) return `public, max-age=${CACHE_MAX_AGE_SECONDS}`;
|
|
183
|
+
return `public, max-age=${Math.max(0, revalidate)}, stale-while-revalidate=${Math.max(0, (expire ?? revalidate) - revalidate)}`;
|
|
184
|
+
}
|
|
185
|
+
var WorkersResponseStoreCacheHandler = class {
|
|
186
|
+
store;
|
|
187
|
+
tagExpirations = cacheForRequest(() => /* @__PURE__ */ new Map());
|
|
188
|
+
constructor(store = responseStore) {
|
|
189
|
+
this.store = store;
|
|
190
|
+
if (!store) throw new Error("[vinext] The Workers Response Store adapter must run through its generated Cloudflare worker entry.");
|
|
191
|
+
}
|
|
192
|
+
async get(key, context) {
|
|
193
|
+
if (regenerationStorage.getStore()?.targetKey === key) return null;
|
|
194
|
+
const request = await cacheRequest(key);
|
|
195
|
+
const response = await this.store.fetch(request);
|
|
196
|
+
if (response.status === 404 && response.headers.get("X-Workers-Response-Store") === "MISS") return null;
|
|
197
|
+
if (!response.ok) throw new Error(`Workers Response Store returned ${response.status}`);
|
|
198
|
+
const entry = deserialize(await response.text());
|
|
199
|
+
if (!entry) {
|
|
200
|
+
await this.store.purge({ pathPrefixes: [new URL(request.url).pathname] });
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const softTags = [...new Set(readStringArrayField(context, "softTags").map(encodeCloudflareCacheTag))].sort();
|
|
204
|
+
if (softTags.length) {
|
|
205
|
+
const key = softTags.join(",");
|
|
206
|
+
const expirations = this.tagExpirations();
|
|
207
|
+
let expiration = expirations.get(key);
|
|
208
|
+
if (!expiration) {
|
|
209
|
+
expiration = this.store.getTagExpiration(softTags);
|
|
210
|
+
expirations.set(key, expiration);
|
|
211
|
+
}
|
|
212
|
+
if (await expiration >= entry.lastModified) return null;
|
|
213
|
+
}
|
|
214
|
+
const age = Date.now() - entry.lastModified;
|
|
215
|
+
const requestedRevalidate = readCacheControlField(context, "revalidate");
|
|
216
|
+
const requestedStale = typeof requestedRevalidate === "number" && requestedRevalidate > 0 && age > requestedRevalidate * 1e3;
|
|
217
|
+
let cacheState;
|
|
218
|
+
if (response.headers.get(REPLAYABLE_HEADER) === "1") {
|
|
219
|
+
if (requestedStale) cacheState = "stale";
|
|
220
|
+
} else if (typeof entry.cacheControl?.expire === "number" && age > entry.cacheControl.expire * 1e3) cacheState = "expired";
|
|
221
|
+
else if (requestedStale || typeof entry.cacheControl?.revalidate === "number" && entry.cacheControl.revalidate > 0 && age > entry.cacheControl.revalidate * 1e3) cacheState = "stale";
|
|
222
|
+
return {
|
|
223
|
+
lastModified: entry.lastModified,
|
|
224
|
+
value: entry.value,
|
|
225
|
+
...cacheState ? { cacheState } : {},
|
|
226
|
+
...entry.cacheControl ? { cacheControl: entry.cacheControl } : {}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async set(key, value, context) {
|
|
230
|
+
let revalidate = readCacheControlField(context, "revalidate");
|
|
231
|
+
if (value && "revalidate" in value) revalidate = value.revalidate;
|
|
232
|
+
if (revalidate === 0) return;
|
|
233
|
+
const rawExpire = readCacheControlField(context, "expire");
|
|
234
|
+
const expire = typeof rawExpire === "number" ? rawExpire : void 0;
|
|
235
|
+
const stale = readCacheControlField(context, "stale");
|
|
236
|
+
const cacheControl = typeof revalidate === "number" || revalidate === false ? {
|
|
237
|
+
revalidate,
|
|
238
|
+
...typeof expire === "number" ? { expire } : {},
|
|
239
|
+
...typeof stale === "number" ? { stale } : {}
|
|
240
|
+
} : void 0;
|
|
241
|
+
const tags = /* @__PURE__ */ new Set();
|
|
242
|
+
if (value && "tags" in value && Array.isArray(value.tags)) for (const tag of value.tags) tags.add(tag);
|
|
243
|
+
if (Array.isArray(context?.tags)) {
|
|
244
|
+
for (const tag of context.tags) if (typeof tag === "string") tags.add(tag);
|
|
245
|
+
}
|
|
246
|
+
const tagHeader = [...tags].map(encodeCloudflareCacheTag).join(",");
|
|
247
|
+
if (tagHeader.length > MAX_CACHE_TAG_HEADER_BYTES) throw new Error("Workers Response Store cache tags exceed the Workers Cache header limit");
|
|
248
|
+
const invocation = invocationStorage.getStore();
|
|
249
|
+
const cacheFunctionInvocation = context?.cacheFunctionInvocation;
|
|
250
|
+
const revalidator = cacheFunctionInvocation && typeof cacheFunctionInvocation === "object" && typeof Reflect.get(cacheFunctionInvocation, "referenceId") === "string" && typeof Reflect.get(cacheFunctionInvocation, "encryptedArgs") === "string" ? {
|
|
251
|
+
id: CACHE_FUNCTION_REVALIDATOR_ID,
|
|
252
|
+
args: [key, JSON.stringify(cacheFunctionInvocation)]
|
|
253
|
+
} : invocation?.replayable ? {
|
|
254
|
+
id: DATA_REVALIDATOR_ID,
|
|
255
|
+
args: [key, invocation.serialized]
|
|
256
|
+
} : void 0;
|
|
257
|
+
const response = new Response(serialize({
|
|
258
|
+
...cacheControl ? { cacheControl } : {},
|
|
259
|
+
lastModified: Date.now(),
|
|
260
|
+
value
|
|
261
|
+
}), { headers: {
|
|
262
|
+
"Cache-Control": revalidator ? cachePolicy(revalidate, expire) : CACHE_MAX_AGE,
|
|
263
|
+
...tagHeader ? { "Cache-Tag": tagHeader } : {},
|
|
264
|
+
"Content-Type": "application/json",
|
|
265
|
+
...revalidator ? { [REPLAYABLE_HEADER]: "1" } : {}
|
|
266
|
+
} });
|
|
267
|
+
const regeneration = regenerationStorage.getStore();
|
|
268
|
+
if (regeneration) {
|
|
269
|
+
if (key === regeneration.targetKey) regeneration.captured = response;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
await this.store.put(await cacheRequest(key), response, {
|
|
273
|
+
...revalidator ? { revalidator } : {},
|
|
274
|
+
coalesce: true,
|
|
275
|
+
purgeExisting: true
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
async revalidateTag(tags, durations) {
|
|
279
|
+
const encodedTags = (Array.isArray(tags) ? tags : [tags]).map(encodeCloudflareCacheTag);
|
|
280
|
+
if (!encodedTags.length) return;
|
|
281
|
+
if (durations?.expire && durations.expire > 0) await this.store.refresh({ tags: encodedTags });
|
|
282
|
+
else await this.store.purge({ tags: encodedTags });
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
function createResponseStoreDataCacheAdapter() {
|
|
286
|
+
return new WorkersResponseStoreCacheHandler();
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
export { CACHE_FUNCTION_REVALIDATOR_ID, DATA_REVALIDATOR_ID, WorkersResponseStoreCacheHandler, captureResponseStoreDataRegeneration, captureResponseStoreRscData, createResponseStoreDataCacheAdapter as default, deferResponseStoreAdmission, runWithResponseStoreInvocation, setResponseStore };
|
|
@@ -5,6 +5,5 @@ import { CacheabilityManifest } from "vinext/internal/server/cacheability-manife
|
|
|
5
5
|
* The application build already imports this stable module asset, so the
|
|
6
6
|
* completed dist directory remains the exact input to the final upload.
|
|
7
7
|
*/
|
|
8
|
-
declare function writeCacheabilityManifestArtifact(root: string, configuredPath: string | undefined, manifest: CacheabilityManifest): string;
|
|
9
|
-
//#endregion
|
|
10
|
-
export { writeCacheabilityManifestArtifact };
|
|
8
|
+
export declare function writeCacheabilityManifestArtifact(root: string, configuredPath: string | undefined, manifest: CacheabilityManifest): string;
|
|
9
|
+
//#endregion
|
|
@@ -135,7 +135,33 @@ function assertManifestModuleReachable(configPath) {
|
|
|
135
135
|
if (typeof main !== "string" || main.length === 0) throw new Error("Two-stage CDN warming requires a generated Wrangler main module.");
|
|
136
136
|
const mainPath = path.resolve(serverDirectory, main);
|
|
137
137
|
if (!fs.existsSync(mainPath) || !fs.lstatSync(mainPath).isFile()) throw new Error("Two-stage CDN warming could not find the generated Wrangler main module.");
|
|
138
|
-
|
|
138
|
+
const viteManifestPath = path.join(serverDirectory, ".vite", "manifest.json");
|
|
139
|
+
let viteManifest;
|
|
140
|
+
try {
|
|
141
|
+
viteManifest = JSON.parse(fs.readFileSync(viteManifestPath, "utf8"));
|
|
142
|
+
} catch (cause) {
|
|
143
|
+
throw new Error("Two-stage CDN warming could not read the generated Vite manifest.", { cause });
|
|
144
|
+
}
|
|
145
|
+
const normalizedMain = main.replace(/^\.\//, "");
|
|
146
|
+
const entryKey = Object.entries(viteManifest).find(([, entry]) => entry?.file === normalizedMain)?.[0];
|
|
147
|
+
const queue = entryKey ? [entryKey] : [];
|
|
148
|
+
const visited = /* @__PURE__ */ new Set();
|
|
149
|
+
let reachable = false;
|
|
150
|
+
while (queue.length > 0 && !reachable) {
|
|
151
|
+
const key = queue.shift();
|
|
152
|
+
if (visited.has(key)) continue;
|
|
153
|
+
visited.add(key);
|
|
154
|
+
const entry = viteManifest[key];
|
|
155
|
+
if (!entry || typeof entry.file !== "string") continue;
|
|
156
|
+
const modulePath = path.resolve(serverDirectory, entry.file);
|
|
157
|
+
if (fs.existsSync(modulePath) && fs.lstatSync(modulePath).isFile()) {
|
|
158
|
+
const relativeManifest = path.relative(path.dirname(modulePath), path.join(serverDirectory, CACHEABILITY_MANIFEST_MODULE)).split(path.sep).join("/");
|
|
159
|
+
const specifier = relativeManifest.startsWith(".") ? relativeManifest : `./${relativeManifest}`;
|
|
160
|
+
reachable = hasStaticModuleSpecifier(fs.readFileSync(modulePath, "utf8"), specifier);
|
|
161
|
+
}
|
|
162
|
+
for (const references of [entry.imports, entry.dynamicImports]) if (Array.isArray(references)) queue.push(...references.filter((value) => typeof value === "string"));
|
|
163
|
+
}
|
|
164
|
+
if (!reachable) throw new Error(`Two-stage CDN warming requires the generated Worker graph to statically import ${CACHEABILITY_MANIFEST_MODULE}.`);
|
|
139
165
|
}
|
|
140
166
|
/**
|
|
141
167
|
* Write the version-specific manifest into the built Worker artifact.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
//#region src/cacheability-manifest-limits.d.ts
|
|
2
|
-
declare const MAX_CACHEABILITY_MANIFEST_BYTES: number;
|
|
3
|
-
declare function cacheabilityManifestByteLimitError(manifestBytes: number, limit?: number): Error;
|
|
4
|
-
//#endregion
|
|
5
|
-
export { MAX_CACHEABILITY_MANIFEST_BYTES, cacheabilityManifestByteLimitError };
|
|
2
|
+
export declare const MAX_CACHEABILITY_MANIFEST_BYTES: number;
|
|
3
|
+
export declare function cacheabilityManifestByteLimitError(manifestBytes: number, limit?: number): Error;
|
|
4
|
+
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/cacheability-manifest-limits.ts
|
|
2
|
-
const MAX_CACHEABILITY_MANIFEST_BYTES =
|
|
2
|
+
const MAX_CACHEABILITY_MANIFEST_BYTES = 2097152;
|
|
3
3
|
function cacheabilityManifestByteLimitError(manifestBytes, limit = MAX_CACHEABILITY_MANIFEST_BYTES) {
|
|
4
4
|
return /* @__PURE__ */ new Error(`Two-stage CDN warming produced a ${manifestBytes}-byte route-pattern cacheability manifest; the limit is ${limit} bytes. Reduce the number or length of route patterns, or split the deployment before retrying.`);
|
|
5
5
|
}
|
|
@@ -2,10 +2,10 @@ import { CdnWarmTarget } from "./cdn-warm.js";
|
|
|
2
2
|
import { CacheabilityManifest } from "vinext/internal/server/cacheability-manifest";
|
|
3
3
|
import { PrerenderRoutePattern } from "vinext/internal/build/prerender-paths";
|
|
4
4
|
//#region src/cacheability-probe.d.ts
|
|
5
|
-
declare const DEFAULT_CACHEABILITY_PROBE_PHASE_TIMEOUT_MS = 120000;
|
|
6
|
-
declare const DEFAULT_CACHEABILITY_PROBE_RETRIES = 2;
|
|
7
|
-
declare const DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS = 1000;
|
|
8
|
-
type CacheabilityProbeProgress = {
|
|
5
|
+
export declare const DEFAULT_CACHEABILITY_PROBE_PHASE_TIMEOUT_MS = 120000;
|
|
6
|
+
export declare const DEFAULT_CACHEABILITY_PROBE_RETRIES = 2;
|
|
7
|
+
export declare const DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS = 1000;
|
|
8
|
+
export type CacheabilityProbeProgress = {
|
|
9
9
|
completed: number;
|
|
10
10
|
dynamic: number;
|
|
11
11
|
failed: number;
|
|
@@ -14,7 +14,7 @@ type CacheabilityProbeProgress = {
|
|
|
14
14
|
static: number;
|
|
15
15
|
total: number;
|
|
16
16
|
};
|
|
17
|
-
type CacheabilityProbeResult = {
|
|
17
|
+
export type CacheabilityProbeResult = {
|
|
18
18
|
cacheableTargets: CdnWarmTarget[];
|
|
19
19
|
classified: number;
|
|
20
20
|
dynamic: number;
|
|
@@ -25,8 +25,8 @@ type CacheabilityProbeResult = {
|
|
|
25
25
|
/** Paired representations admitted only if their own final warm render remains cacheable. */
|
|
26
26
|
speculativeTargets: CdnWarmTarget[];
|
|
27
27
|
};
|
|
28
|
-
declare function readPrerenderSecret(root: string): string;
|
|
29
|
-
declare function probeStagedWorkerCacheability(options: {
|
|
28
|
+
export declare function readPrerenderSecret(root: string): string;
|
|
29
|
+
export declare function probeStagedWorkerCacheability(options: {
|
|
30
30
|
buildId: string;
|
|
31
31
|
concurrency?: number;
|
|
32
32
|
expectedResponseBuildId?: string;
|
|
@@ -46,5 +46,4 @@ declare function probeStagedWorkerCacheability(options: {
|
|
|
46
46
|
maxBytes?: number;
|
|
47
47
|
};
|
|
48
48
|
}): Promise<CacheabilityProbeResult>;
|
|
49
|
-
//#endregion
|
|
50
|
-
export { CacheabilityProbeProgress, CacheabilityProbeResult, DEFAULT_CACHEABILITY_PROBE_PHASE_TIMEOUT_MS, DEFAULT_CACHEABILITY_PROBE_RETRIES, DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS, probeStagedWorkerCacheability, readPrerenderSecret };
|
|
49
|
+
//#endregion
|