@vinext/cloudflare 1.0.0-beta.0 → 1.0.0-beta.10
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 +94 -1
- package/dist/cache/cdn-adapter-config.d.ts +30 -0
- package/dist/cache/cdn-adapter-config.js +87 -0
- package/dist/cache/cdn-adapter.d.ts +47 -15
- package/dist/cache/cdn-adapter.js +49 -13
- package/dist/cache/cdn-adapter.runtime.d.ts +26 -4
- package/dist/cache/cdn-adapter.runtime.js +150 -37
- package/dist/cache/cdn-adapter.worker.d.ts +31 -0
- package/dist/cache/cdn-adapter.worker.js +373 -0
- package/dist/cache/cdn-build-id.d.ts +6 -0
- package/dist/cache/cdn-build-id.js +9 -0
- package/dist/cache/kv-data-adapter.d.ts +32 -8
- package/dist/cache/kv-data-adapter.js +5 -1
- package/dist/cache/kv-data-adapter.runtime.d.ts +45 -13
- package/dist/cache/kv-data-adapter.runtime.js +133 -53
- package/dist/cache/kv-key.d.ts +15 -0
- package/dist/cache/kv-key.js +43 -0
- package/dist/cache/response-store-adapter.d.ts +73 -0
- package/dist/cache/response-store-adapter.js +64 -0
- package/dist/cache/response-store-adapter.self-contained.worker.d.ts +18 -0
- package/dist/cache/response-store-adapter.self-contained.worker.js +12 -0
- package/dist/cache/response-store-adapter.service-binding.worker.d.ts +16 -0
- package/dist/cache/response-store-adapter.service-binding.worker.js +11 -0
- package/dist/cache/response-store-adapter.worker.d.ts +14 -0
- package/dist/cache/response-store-adapter.worker.js +278 -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 +300 -0
- package/dist/cacheability-artifact.d.ts +9 -0
- package/dist/cacheability-artifact.js +191 -0
- package/dist/cacheability-manifest-limits.d.ts +4 -0
- package/dist/cacheability-manifest-limits.js +7 -0
- package/dist/cacheability-probe.d.ts +49 -0
- package/dist/cacheability-probe.js +660 -0
- package/dist/cdn-warm.d.ts +110 -12
- package/dist/cdn-warm.js +880 -82
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +15 -1
- package/dist/deploy-config.d.ts +13 -11
- package/dist/deploy-config.js +21 -3
- package/dist/deploy-help.d.ts +3 -4
- package/dist/deploy-help.js +61 -18
- package/dist/deploy.d.ts +121 -41
- package/dist/deploy.js +787 -121
- package/dist/images/images-optimizer.d.ts +5 -5
- package/dist/images/images-optimizer.runtime.d.ts +1 -5
- package/dist/prerender-kv-populate.d.ts +3 -4
- package/dist/prerender-kv-populate.js +51 -18
- package/dist/tpr.d.ts +32 -70
- package/dist/tpr.js +45 -602
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +19 -17
- package/dist/version-deploy.js +32 -13
- package/dist/version-headers.d.ts +2 -0
- package/dist/version-headers.js +2 -0
- package/dist/worker-deployment-url.d.ts +9 -0
- package/dist/worker-deployment-url.js +45 -0
- package/dist/workers-dev-url.d.ts +2 -3
- package/dist/wrangler-config.d.ts +19 -0
- package/dist/wrangler-config.js +294 -0
- package/dist/wrangler-version-metadata.d.ts +7 -0
- package/dist/wrangler-version-metadata.js +9 -0
- package/package.json +14 -10
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { captureResponseStoreDataRegeneration, runWithResponseStoreInvocation, setResponseStore } from "./response-store-data.runtime.js";
|
|
2
|
+
import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createCanonicalRscRequestHeaders, createCanonicalRscRequestUrl } from "vinext/internal/server/app-rsc-cache-busting";
|
|
3
|
+
import { isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
4
|
+
import { loadVinextRequestStage } from "vinext/server/request-stage";
|
|
5
|
+
import { loadVinextResponseStage } from "vinext/server/response-stage";
|
|
6
|
+
import { traceCachedResponseStart } from "vinext/internal/server/response-start-tracing";
|
|
7
|
+
//#region src/cache/response-store-adapter.worker.ts
|
|
8
|
+
const ROUTE_REVALIDATOR_ID = "vinext:response";
|
|
9
|
+
const RESPONSE_STORE_KEY_PARAM = "__workers_response_store";
|
|
10
|
+
const AGE_BASIS_HEADER = "X-Workers-Response-Store-Age-Basis";
|
|
11
|
+
const WARMUP_USER_AGENT = "vinext-cloudflare-cdn-warm";
|
|
12
|
+
const REPLAY_REQUEST_HEADERS = VINEXT_RSC_VARY_HEADER.split(",").map((name) => name.trim().toLowerCase());
|
|
13
|
+
const CACHE_REQUEST_VARY_HEADERS = REPLAY_REQUEST_HEADERS.map((name) => [name, "vinext-keyed"]);
|
|
14
|
+
function stageContext(ctx, env) {
|
|
15
|
+
const assets = Reflect.get(env, "ASSETS");
|
|
16
|
+
return Object.assign(Object.create(Object.getPrototypeOf(ctx)), ctx, {
|
|
17
|
+
...assets && typeof assets === "object" && typeof Reflect.get(assets, "fetch") === "function" ? { assets } : {},
|
|
18
|
+
...ctx.exports ? { exports: ctx.exports } : {},
|
|
19
|
+
hostRuntime: "worker",
|
|
20
|
+
passThroughOnException: () => ctx.passThroughOnException(),
|
|
21
|
+
...ctx.props === void 0 ? {} : { props: ctx.props },
|
|
22
|
+
waitUntil: (promise) => ctx.waitUntil(promise)
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function safeProps(props) {
|
|
26
|
+
const copy = JSON.parse(JSON.stringify(props));
|
|
27
|
+
if (copy && typeof copy === "object") {
|
|
28
|
+
if ("draftModeCookie" in copy) copy.draftModeCookie = null;
|
|
29
|
+
if ("middlewareCookieOverlay" in copy) copy.middlewareCookieOverlay = null;
|
|
30
|
+
}
|
|
31
|
+
return copy;
|
|
32
|
+
}
|
|
33
|
+
function replayHeaders(request) {
|
|
34
|
+
return REPLAY_REQUEST_HEADERS.flatMap((name) => {
|
|
35
|
+
const value = request.headers.get(name);
|
|
36
|
+
return value === null ? [] : [[name, value]];
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function isReplayableInvocation(request, props) {
|
|
40
|
+
const method = request.method.toUpperCase();
|
|
41
|
+
if (method !== "GET" && method !== "HEAD") return false;
|
|
42
|
+
if (request.headers.has("authorization") || request.headers.has("cookie")) return false;
|
|
43
|
+
return !(props && typeof props === "object" && ["draftModeCookie", "middlewareCookieOverlay"].some((name) => name in props && Reflect.get(props, name) !== null));
|
|
44
|
+
}
|
|
45
|
+
function prepareInvocation(request, props) {
|
|
46
|
+
return {
|
|
47
|
+
props: safeProps(props),
|
|
48
|
+
request: {
|
|
49
|
+
headers: replayHeaders(request),
|
|
50
|
+
method: request.method,
|
|
51
|
+
url: request.url
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function serializeInvocation(request, props) {
|
|
56
|
+
return JSON.stringify(prepareInvocation(request, props));
|
|
57
|
+
}
|
|
58
|
+
function parseInvocation(value) {
|
|
59
|
+
if (typeof value !== "string") throw new TypeError("Invalid vinext response-store invocation");
|
|
60
|
+
const invocation = JSON.parse(value);
|
|
61
|
+
if (!invocation || typeof invocation !== "object" || !invocation.request || typeof invocation.request.url !== "string" || typeof invocation.request.method !== "string" || !Array.isArray(invocation.request.headers)) throw new TypeError("Invalid vinext response-store invocation");
|
|
62
|
+
return invocation;
|
|
63
|
+
}
|
|
64
|
+
function restoreRequest(invocation) {
|
|
65
|
+
return new Request(invocation.request.url, {
|
|
66
|
+
headers: invocation.request.headers,
|
|
67
|
+
method: invocation.request.method
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function invokeRequestStage(request, env, ctx) {
|
|
71
|
+
const { handleRequestStage } = await loadVinextRequestStage();
|
|
72
|
+
return handleRequestStage(request, env, stageContext(ctx, env), (request, props, options) => invokeResponseStage(request, props, env, ctx, options.cache));
|
|
73
|
+
}
|
|
74
|
+
async function invokeResponseStage(request, props, env, ctx, cache, capture, invocation) {
|
|
75
|
+
const context = stageContext(ctx, env);
|
|
76
|
+
const dispatchRequestStage = (request) => invokeRequestStage(request, env, ctx);
|
|
77
|
+
const { handleResponseStage } = await loadVinextResponseStage();
|
|
78
|
+
const storedInvocation = invocation ?? {
|
|
79
|
+
replayable: isReplayableInvocation(request, props),
|
|
80
|
+
serialized: serializeInvocation(request, props)
|
|
81
|
+
};
|
|
82
|
+
return runWithResponseStoreInvocation(storedInvocation.serialized, storedInvocation.replayable, () => handleResponseStage(request, env, context, props, dispatchRequestStage, { cache }), capture);
|
|
83
|
+
}
|
|
84
|
+
function createVinextResponseStoreOptions(configuration) {
|
|
85
|
+
const locationHint = configuration?.locationHint;
|
|
86
|
+
if (locationHint !== void 0 && typeof locationHint !== "string") throw new TypeError("Workers Response Store locationHint must be a string");
|
|
87
|
+
const shards = configuration?.shards;
|
|
88
|
+
if (shards !== void 0 && typeof shards !== "number") throw new TypeError("Workers Response Store shards must be a number");
|
|
89
|
+
return {
|
|
90
|
+
...locationHint === void 0 ? {} : { locationHint },
|
|
91
|
+
...shards === void 0 ? {} : { shards },
|
|
92
|
+
async regenerate(input, { env, ctx }) {
|
|
93
|
+
if (input.id === ROUTE_REVALIDATOR_ID) {
|
|
94
|
+
const invocation = parseInvocation(input.args.at(-1));
|
|
95
|
+
const response = await invokeResponseStage(restoreRequest(invocation), invocation.props, env, ctx, "shared");
|
|
96
|
+
if (!isCacheable(response)) {
|
|
97
|
+
await response.body?.cancel().catch(() => {});
|
|
98
|
+
throw new Error("Vinext response-stage regeneration was not cacheable");
|
|
99
|
+
}
|
|
100
|
+
return response;
|
|
101
|
+
}
|
|
102
|
+
if (input.id === "vinext:cache-function" && typeof input.args[0] === "string" && typeof input.args[1] === "string") {
|
|
103
|
+
const invocation = JSON.parse(input.args[1]);
|
|
104
|
+
if (!invocation || typeof invocation.referenceId !== "string" || typeof invocation.encryptedArgs !== "string" || !invocation.rootParams || typeof invocation.rootParams !== "object" || !Array.isArray(invocation.softTags)) throw new TypeError("Invalid vinext cache function invocation");
|
|
105
|
+
return captureResponseStoreDataRegeneration(input.args[0], async () => {
|
|
106
|
+
const responseStage = await loadVinextResponseStage();
|
|
107
|
+
if (!responseStage.invokeCacheFunction) throw new Error("The vinext response stage cannot invoke cache functions");
|
|
108
|
+
await responseStage.invokeCacheFunction(invocation, env, stageContext(ctx, env), (request) => invokeRequestStage(request, env, ctx));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (input.id === "vinext:data" && typeof input.args[0] === "string") {
|
|
112
|
+
const invocation = parseInvocation(input.args.at(-1));
|
|
113
|
+
return captureResponseStoreDataRegeneration(input.args[0], async () => {
|
|
114
|
+
await (await invokeResponseStage(restoreRequest(invocation), invocation.props, env, ctx, "bypass")).body?.pipeTo(new WritableStream());
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`Unknown vinext response-store revalidator ${input.id}`);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function cacheRequest(invocation) {
|
|
122
|
+
const identity = JSON.stringify([
|
|
123
|
+
invocation.request.method,
|
|
124
|
+
invocation.request.url,
|
|
125
|
+
invocation.props,
|
|
126
|
+
invocation.request.headers
|
|
127
|
+
]);
|
|
128
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(identity)));
|
|
129
|
+
const url = new URL(invocation.request.url);
|
|
130
|
+
url.searchParams.set(RESPONSE_STORE_KEY_PARAM, `v1.${[...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`);
|
|
131
|
+
return new Request(url, {
|
|
132
|
+
headers: CACHE_REQUEST_VARY_HEADERS,
|
|
133
|
+
method: "GET"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function isCacheable(response) {
|
|
137
|
+
const policy = response.headers.get("Cloudflare-CDN-Cache-Control") ?? response.headers.get("CDN-Cache-Control") ?? response.headers.get("Cache-Control");
|
|
138
|
+
return response.status >= 200 && response.status < 400 && policy !== null && !isNonCacheableCacheControl(policy);
|
|
139
|
+
}
|
|
140
|
+
function isResponseStoreMiss(response) {
|
|
141
|
+
return response.status === 404 && response.headers.get("X-Workers-Response-Store") === "MISS";
|
|
142
|
+
}
|
|
143
|
+
function publicResponse(response, cacheStatus, responseStageProps) {
|
|
144
|
+
const headers = new Headers(response.headers);
|
|
145
|
+
const publicCacheStatus = cacheStatus === "HIT" && headers.get("CF-Cache-Status") === "UPDATING" ? "UPDATING" : cacheStatus;
|
|
146
|
+
const ageBasis = /^(\d+):(\d+)$/.exec(headers.get(AGE_BASIS_HEADER) ?? "");
|
|
147
|
+
if (cacheStatus === "HIT" && ageBasis) {
|
|
148
|
+
const elapsed = (BigInt(Date.now()) - BigInt(ageBasis[1])) / 1000n;
|
|
149
|
+
headers.set("Age", String(BigInt(ageBasis[2]) + (elapsed > 0n ? elapsed : 0n)));
|
|
150
|
+
}
|
|
151
|
+
for (const name of [
|
|
152
|
+
"Cache-Tag",
|
|
153
|
+
"CDN-Cache-Control",
|
|
154
|
+
"CF-Cache-Status",
|
|
155
|
+
"Cloudflare-CDN-Cache-Control",
|
|
156
|
+
"X-Workers-Response-Store",
|
|
157
|
+
AGE_BASIS_HEADER,
|
|
158
|
+
"X-Workers-Response-Store-Binding-Invocation",
|
|
159
|
+
"X-Workers-Response-Store-Revision"
|
|
160
|
+
]) headers.delete(name);
|
|
161
|
+
if (publicCacheStatus) {
|
|
162
|
+
headers.set("X-Nextjs-Cache", publicCacheStatus);
|
|
163
|
+
headers.set("X-Vinext-Cache", publicCacheStatus);
|
|
164
|
+
}
|
|
165
|
+
const cacheControl = headers.get("Cache-Control");
|
|
166
|
+
if (!cacheControl || !isNonCacheableCacheControl(cacheControl)) headers.set("Cache-Control", "private, max-age=0, must-revalidate");
|
|
167
|
+
return traceCachedResponseStart(new Response(response.body, {
|
|
168
|
+
headers,
|
|
169
|
+
status: response.status,
|
|
170
|
+
statusText: response.statusText
|
|
171
|
+
}), publicCacheStatus ?? null, responseStageProps);
|
|
172
|
+
}
|
|
173
|
+
let responseStore;
|
|
174
|
+
function createVinextResponseStoreHandler(store) {
|
|
175
|
+
responseStore = store;
|
|
176
|
+
setResponseStore(store);
|
|
177
|
+
return handler;
|
|
178
|
+
}
|
|
179
|
+
const handler = { async fetch(request, env, ctx) {
|
|
180
|
+
const context = stageContext(ctx, env);
|
|
181
|
+
const dispatchResponseStage = async (stageRequest, props, options) => {
|
|
182
|
+
if (options.cache === "bypass" || stageRequest.method !== "GET" && stageRequest.method !== "HEAD") return publicResponse(await invokeResponseStage(stageRequest, props, env, ctx, "bypass"), "BYPASS", props);
|
|
183
|
+
const isWarmup = request.headers.get("user-agent") === WARMUP_USER_AGENT;
|
|
184
|
+
const rscSeed = isWarmup && props !== null && typeof props === "object" && Reflect.get(props, "kind") === "app-page" && Reflect.get(props, "isRscRequest") === false && Reflect.get(props, "matchKind") === "request" && Reflect.get(props, "interceptionContext") === null && Reflect.get(props, "interceptionId") === null && Reflect.get(props, "mountedSlotsHeader") === null ? {
|
|
185
|
+
props: {
|
|
186
|
+
...props,
|
|
187
|
+
isRscRequest: true,
|
|
188
|
+
renderMode: "navigation"
|
|
189
|
+
},
|
|
190
|
+
request: new Request(new URL(createCanonicalRscRequestUrl(stageRequest.url), stageRequest.url), { headers: createCanonicalRscRequestHeaders() })
|
|
191
|
+
} : void 0;
|
|
192
|
+
const invocation = prepareInvocation(stageRequest, props);
|
|
193
|
+
const rscInvocation = rscSeed ? prepareInvocation(rscSeed.request, rscSeed.props) : void 0;
|
|
194
|
+
const rscKey = rscInvocation ? await cacheRequest(rscInvocation) : void 0;
|
|
195
|
+
const key = await cacheRequest(invocation);
|
|
196
|
+
const stored = await responseStore.fetch(key);
|
|
197
|
+
if (!isResponseStoreMiss(stored)) {
|
|
198
|
+
if (!rscKey) return publicResponse(stored, "HIT", props);
|
|
199
|
+
const storedRsc = await responseStore.fetch(rscKey);
|
|
200
|
+
if (!isResponseStoreMiss(storedRsc)) {
|
|
201
|
+
await storedRsc.body?.cancel();
|
|
202
|
+
return publicResponse(stored, "HIT", props);
|
|
203
|
+
}
|
|
204
|
+
await Promise.all([stored.body?.cancel(), storedRsc.body?.cancel()]);
|
|
205
|
+
}
|
|
206
|
+
const capture = rscSeed ? { captureRscData: true } : isWarmup ? {} : { streamResponse: true };
|
|
207
|
+
const serializedInvocation = JSON.stringify(invocation);
|
|
208
|
+
const rendered = await invokeResponseStage(stageRequest, props, env, ctx, "shared", capture, {
|
|
209
|
+
replayable: isReplayableInvocation(stageRequest, props),
|
|
210
|
+
serialized: serializedInvocation
|
|
211
|
+
});
|
|
212
|
+
if (capture.admittedResponse) {
|
|
213
|
+
ctx.waitUntil(capture.admittedResponse.then(async (admitted) => {
|
|
214
|
+
if (!isCacheable(admitted)) {
|
|
215
|
+
await admitted.body?.cancel().catch(() => {});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
await responseStore.put(key, admitted, {
|
|
219
|
+
coalesce: true,
|
|
220
|
+
revalidator: {
|
|
221
|
+
id: ROUTE_REVALIDATOR_ID,
|
|
222
|
+
args: [serializedInvocation]
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}).catch((error) => {
|
|
226
|
+
console.error(JSON.stringify({
|
|
227
|
+
message: "Vinext response-store admission failed",
|
|
228
|
+
error: error instanceof Error ? error.message : String(error)
|
|
229
|
+
}));
|
|
230
|
+
}));
|
|
231
|
+
return publicResponse(rendered, "MISS", props);
|
|
232
|
+
}
|
|
233
|
+
if (!isCacheable(rendered)) {
|
|
234
|
+
capture?.rscData?.catch(() => {});
|
|
235
|
+
return publicResponse(rendered, "BYPASS", props);
|
|
236
|
+
}
|
|
237
|
+
if (rscSeed && !capture?.rscData) {
|
|
238
|
+
await rendered.body?.cancel();
|
|
239
|
+
throw new Error("Vinext response-store warmup did not capture the App page RSC payload");
|
|
240
|
+
}
|
|
241
|
+
const [foreground, cacheBody] = rendered.body ? rendered.body.tee() : [null, null];
|
|
242
|
+
const cacheResponse = new Response(cacheBody, rendered);
|
|
243
|
+
await responseStore.put(key, cacheResponse, {
|
|
244
|
+
coalesce: true,
|
|
245
|
+
revalidator: {
|
|
246
|
+
id: ROUTE_REVALIDATOR_ID,
|
|
247
|
+
args: [serializedInvocation]
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
if (rscSeed && rscInvocation && rscKey && capture?.rscData) {
|
|
251
|
+
const rscData = await capture.rscData;
|
|
252
|
+
const rscHeaders = new Headers(rendered.headers);
|
|
253
|
+
rscHeaders.delete("Content-Length");
|
|
254
|
+
rscHeaders.delete("Link");
|
|
255
|
+
rscHeaders.delete("X-Vinext-Response-Store-Replayable");
|
|
256
|
+
rscHeaders.set("Content-Type", VINEXT_RSC_CONTENT_TYPE);
|
|
257
|
+
rscHeaders.set("Vary", VINEXT_RSC_VARY_HEADER);
|
|
258
|
+
applyRscCompatibilityIdHeader(rscHeaders);
|
|
259
|
+
applyRscDeploymentIdHeader(rscHeaders);
|
|
260
|
+
const serializedRscInvocation = JSON.stringify(rscInvocation);
|
|
261
|
+
await responseStore.put(rscKey, new Response(rscData, {
|
|
262
|
+
headers: rscHeaders,
|
|
263
|
+
status: 200
|
|
264
|
+
}), {
|
|
265
|
+
coalesce: true,
|
|
266
|
+
revalidator: {
|
|
267
|
+
id: ROUTE_REVALIDATOR_ID,
|
|
268
|
+
args: [serializedRscInvocation]
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return publicResponse(new Response(foreground, rendered), "MISS", props);
|
|
273
|
+
};
|
|
274
|
+
const { handleRequestStage } = await loadVinextRequestStage();
|
|
275
|
+
return handleRequestStage(request, env, context, dispatchResponseStage);
|
|
276
|
+
} };
|
|
277
|
+
//#endregion
|
|
278
|
+
export { createVinextResponseStoreHandler, createVinextResponseStoreOptions };
|
|
@@ -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,300 @@
|
|
|
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 softTags = [...new Set(readStringArrayField(context, "softTags").map(encodeCloudflareCacheTag))].sort();
|
|
199
|
+
const getTagExpiration = () => {
|
|
200
|
+
const key = softTags.join(",");
|
|
201
|
+
const expirations = this.tagExpirations();
|
|
202
|
+
let expiration = expirations.get(key);
|
|
203
|
+
if (!expiration) {
|
|
204
|
+
expiration = Promise.resolve().then(() => this.store.getTagExpiration(softTags));
|
|
205
|
+
expirations.set(key, expiration);
|
|
206
|
+
expiration.catch(() => {
|
|
207
|
+
if (expirations.get(key) === expiration) expirations.delete(key);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return expiration;
|
|
211
|
+
};
|
|
212
|
+
const storeStatus = response.headers.get("X-Workers-Response-Store");
|
|
213
|
+
const body = response.text();
|
|
214
|
+
const eagerExpiration = softTags.length && (storeStatus === "BLOB-FRESH" || storeStatus === "BLOB-STALE") ? getTagExpiration().then((value) => ({ value }), (error) => ({ error })) : void 0;
|
|
215
|
+
const entry = deserialize(await body);
|
|
216
|
+
if (!entry) {
|
|
217
|
+
await this.store.purge({ pathPrefixes: [new URL(request.url).pathname] });
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
if (softTags.length) {
|
|
221
|
+
const expiration = eagerExpiration ? await eagerExpiration : { value: await getTagExpiration() };
|
|
222
|
+
if ("error" in expiration) throw expiration.error;
|
|
223
|
+
if (expiration.value >= entry.lastModified) return null;
|
|
224
|
+
}
|
|
225
|
+
const age = Date.now() - entry.lastModified;
|
|
226
|
+
const requestedRevalidate = readCacheControlField(context, "revalidate");
|
|
227
|
+
const requestedStale = typeof requestedRevalidate === "number" && requestedRevalidate > 0 && age > requestedRevalidate * 1e3;
|
|
228
|
+
let cacheState;
|
|
229
|
+
if (response.headers.get(REPLAYABLE_HEADER) === "1") {
|
|
230
|
+
if (requestedStale) cacheState = "stale";
|
|
231
|
+
} else if (typeof entry.cacheControl?.expire === "number" && age > entry.cacheControl.expire * 1e3) cacheState = "expired";
|
|
232
|
+
else if (requestedStale || typeof entry.cacheControl?.revalidate === "number" && entry.cacheControl.revalidate > 0 && age > entry.cacheControl.revalidate * 1e3) cacheState = "stale";
|
|
233
|
+
return {
|
|
234
|
+
lastModified: entry.lastModified,
|
|
235
|
+
value: entry.value,
|
|
236
|
+
...cacheState ? { cacheState } : {},
|
|
237
|
+
...entry.cacheControl ? { cacheControl: entry.cacheControl } : {}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async set(key, value, context) {
|
|
241
|
+
let revalidate = readCacheControlField(context, "revalidate");
|
|
242
|
+
if (value && "revalidate" in value) revalidate = value.revalidate;
|
|
243
|
+
if (revalidate === 0) return;
|
|
244
|
+
const rawExpire = readCacheControlField(context, "expire");
|
|
245
|
+
const expire = typeof rawExpire === "number" ? rawExpire : void 0;
|
|
246
|
+
const stale = readCacheControlField(context, "stale");
|
|
247
|
+
const cacheControl = typeof revalidate === "number" || revalidate === false ? {
|
|
248
|
+
revalidate,
|
|
249
|
+
...typeof expire === "number" ? { expire } : {},
|
|
250
|
+
...typeof stale === "number" ? { stale } : {}
|
|
251
|
+
} : void 0;
|
|
252
|
+
const tags = /* @__PURE__ */ new Set();
|
|
253
|
+
if (value && "tags" in value && Array.isArray(value.tags)) for (const tag of value.tags) tags.add(tag);
|
|
254
|
+
if (Array.isArray(context?.tags)) {
|
|
255
|
+
for (const tag of context.tags) if (typeof tag === "string") tags.add(tag);
|
|
256
|
+
}
|
|
257
|
+
const tagHeader = [...tags].map(encodeCloudflareCacheTag).join(",");
|
|
258
|
+
if (tagHeader.length > MAX_CACHE_TAG_HEADER_BYTES) throw new Error("Workers Response Store cache tags exceed the Workers Cache header limit");
|
|
259
|
+
const invocation = invocationStorage.getStore();
|
|
260
|
+
const cacheFunctionInvocation = context?.cacheFunctionInvocation;
|
|
261
|
+
const revalidator = cacheFunctionInvocation && typeof cacheFunctionInvocation === "object" && typeof Reflect.get(cacheFunctionInvocation, "referenceId") === "string" && typeof Reflect.get(cacheFunctionInvocation, "encryptedArgs") === "string" ? {
|
|
262
|
+
id: CACHE_FUNCTION_REVALIDATOR_ID,
|
|
263
|
+
args: [key, JSON.stringify(cacheFunctionInvocation)]
|
|
264
|
+
} : invocation?.replayable ? {
|
|
265
|
+
id: DATA_REVALIDATOR_ID,
|
|
266
|
+
args: [key, invocation.serialized]
|
|
267
|
+
} : void 0;
|
|
268
|
+
const response = new Response(serialize({
|
|
269
|
+
...cacheControl ? { cacheControl } : {},
|
|
270
|
+
lastModified: Date.now(),
|
|
271
|
+
value
|
|
272
|
+
}), { headers: {
|
|
273
|
+
"Cache-Control": revalidator ? cachePolicy(revalidate, expire) : CACHE_MAX_AGE,
|
|
274
|
+
...tagHeader ? { "Cache-Tag": tagHeader } : {},
|
|
275
|
+
"Content-Type": "application/json",
|
|
276
|
+
...revalidator ? { [REPLAYABLE_HEADER]: "1" } : {}
|
|
277
|
+
} });
|
|
278
|
+
const regeneration = regenerationStorage.getStore();
|
|
279
|
+
if (regeneration) {
|
|
280
|
+
if (key === regeneration.targetKey) regeneration.captured = response;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
await this.store.put(await cacheRequest(key), response, {
|
|
284
|
+
...revalidator ? { revalidator } : {},
|
|
285
|
+
coalesce: true,
|
|
286
|
+
purgeExisting: true
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
async revalidateTag(tags, durations) {
|
|
290
|
+
const encodedTags = (Array.isArray(tags) ? tags : [tags]).map(encodeCloudflareCacheTag);
|
|
291
|
+
if (!encodedTags.length) return;
|
|
292
|
+
if (durations?.expire && durations.expire > 0) await this.store.refresh({ tags: encodedTags });
|
|
293
|
+
else await this.store.purge({ tags: encodedTags });
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
function createResponseStoreDataCacheAdapter() {
|
|
297
|
+
return new WorkersResponseStoreCacheHandler();
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
export { CACHE_FUNCTION_REVALIDATOR_ID, DATA_REVALIDATOR_ID, WorkersResponseStoreCacheHandler, captureResponseStoreDataRegeneration, captureResponseStoreRscData, createResponseStoreDataCacheAdapter as default, deferResponseStoreAdmission, runWithResponseStoreInvocation, setResponseStore };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CacheabilityManifest } from "vinext/internal/server/cacheability-manifest";
|
|
2
|
+
//#region src/cacheability-artifact.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Write the version-specific manifest into the built Worker artifact.
|
|
5
|
+
* The application build already imports this stable module asset, so the
|
|
6
|
+
* completed dist directory remains the exact input to the final upload.
|
|
7
|
+
*/
|
|
8
|
+
export declare function writeCacheabilityManifestArtifact(root: string, configuredPath: string | undefined, manifest: CacheabilityManifest): string;
|
|
9
|
+
//#endregion
|