@vinext/cloudflare 1.0.0-beta.1 → 1.0.0-beta.11
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 +193 -1
- package/dist/cache/cdn-adapter-config.d.ts +31 -0
- package/dist/cache/cdn-adapter-config.js +95 -0
- package/dist/cache/cdn-adapter.d.ts +48 -15
- package/dist/cache/cdn-adapter.js +50 -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/config.d.ts +137 -0
- package/dist/cache/config.js +87 -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 +132 -52
- 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 +10 -0
- package/dist/cacheability-artifact.js +215 -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 +14 -11
- package/dist/deploy-config.js +24 -5
- package/dist/deploy-help.d.ts +3 -4
- package/dist/deploy-help.js +65 -22
- package/dist/deploy.d.ts +139 -42
- package/dist/deploy.js +946 -132
- 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 +34 -70
- package/dist/tpr.js +50 -603
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +46 -17
- package/dist/version-deploy.js +151 -14
- 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 +21 -11
package/README.md
CHANGED
|
@@ -7,7 +7,13 @@ This package provides Cloudflare-specific cache and image backends for vinext:
|
|
|
7
7
|
|
|
8
8
|
- **`kvDataAdapter()`** (`@vinext/cloudflare/cache/kv-data-adapter`) — backs the
|
|
9
9
|
data cache (`fetch`, `"use cache"`, `unstable_cache`) with a Workers KV
|
|
10
|
-
namespace.
|
|
10
|
+
namespace.
|
|
11
|
+
- **`cdnAdapter()`** (`@vinext/cloudflare/cache/cdn-adapter`) — delegates
|
|
12
|
+
page-level ISR serving and revalidation to Cloudflare Workers Cache through
|
|
13
|
+
an automatically generated cache-enabled response entrypoint.
|
|
14
|
+
- **`responseStoreAdapter()`** (`@vinext/cloudflare/cache/response-store-adapter`) —
|
|
15
|
+
uses Workers Response Store for both response and data caching, either in a
|
|
16
|
+
separate cache Worker or inside the application Worker.
|
|
11
17
|
- **`imagesOptimizer()`** (`@vinext/cloudflare/images/images-optimizer`) — backs
|
|
12
18
|
`next/image` transformations with a Cloudflare Images binding.
|
|
13
19
|
|
|
@@ -32,6 +38,170 @@ export default defineConfig({
|
|
|
32
38
|
});
|
|
33
39
|
```
|
|
34
40
|
|
|
41
|
+
### Workers Cache
|
|
42
|
+
|
|
43
|
+
`cdnAdapter()` is optional. Configuring it asks the Cloudflare build for two
|
|
44
|
+
Worker entrypoints: the default entrypoint runs middleware and request-time
|
|
45
|
+
routing with caching disabled, while `VinextCachedResponse` lazily loads the
|
|
46
|
+
render stage with Workers Cache enabled. Legacy Cloudflare Vite plugin builds
|
|
47
|
+
write these settings and the version metadata binding to the generated
|
|
48
|
+
`dist/server/wrangler.json`.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { cdnAdapter } from "@vinext/cloudflare/cache/cdn-adapter";
|
|
52
|
+
|
|
53
|
+
vinext({ cache: { cdn: cdnAdapter() } });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Cloudflare Vite plugin v2 uses Build Output and treats `cloudflare.config.ts`
|
|
57
|
+
as the deployment source of truth. Declare the equivalent policies there:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { defineWorker } from "@cloudflare/vite-plugin/experimental-config";
|
|
61
|
+
import { createWorkersCacheConfig } from "@vinext/cloudflare/cache/config";
|
|
62
|
+
|
|
63
|
+
const workersCache = await createWorkersCacheConfig();
|
|
64
|
+
|
|
65
|
+
export default defineWorker({
|
|
66
|
+
// ...
|
|
67
|
+
...workersCache,
|
|
68
|
+
env: {
|
|
69
|
+
...workersCache.env,
|
|
70
|
+
// Other application bindings...
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The generated version metadata binding lets staged warmup prove that every
|
|
76
|
+
discovery, probe, and fill request reached the uploaded Worker version. Pass
|
|
77
|
+
the same `versionMetadataBinding` to `cdnAdapter()` and
|
|
78
|
+
`createWorkersCacheConfig()` only when the deployment needs a custom binding
|
|
79
|
+
name.
|
|
80
|
+
|
|
81
|
+
Use `--experimental-warm-cdn-cache` for the two-stage deploy. The default flow
|
|
82
|
+
makes one final fill request per admitted identity. Add `--warm-cdn-certify`
|
|
83
|
+
only when you want an opt-in second, header-only request that must prove every
|
|
84
|
+
planned entry reusable before promotion.
|
|
85
|
+
|
|
86
|
+
The response entrypoint hashes the complete transport identity into its
|
|
87
|
+
Workers Cache URL, independently of zone Cache Rules, so distinct query and
|
|
88
|
+
representation variants cannot collide.
|
|
89
|
+
|
|
90
|
+
### Workers Response Store
|
|
91
|
+
|
|
92
|
+
`responseStoreAdapter()` replaces both `cdnAdapter()` and `kvDataAdapter()`.
|
|
93
|
+
It defaults to a separate cache Worker reached through the `RESPONSE_STORE`
|
|
94
|
+
service binding. `vinext init` writes two collocated source configs:
|
|
95
|
+
`wrangler.jsonc` for the application and `wrangler.response-store.jsonc` for the
|
|
96
|
+
cache Worker. The latter points directly at the installed
|
|
97
|
+
`@cloudflare/workers-response-store` implementation and owns its R2 bucket,
|
|
98
|
+
SQLite Durable Object, Worker name, and cache settings. Edit those configs to
|
|
99
|
+
choose or reuse names, keeping the application service binding aligned with the
|
|
100
|
+
cache Worker name.
|
|
101
|
+
|
|
102
|
+
The two Workers are deliberately deployed separately. Deploy the Response Store
|
|
103
|
+
when its package or config changes, then deploy the application normally:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
npx wrangler deploy --config wrangler.response-store.jsonc
|
|
107
|
+
npx @vinext/cloudflare deploy
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`vinext-cloudflare deploy` never creates, rewrites, or deploys the Response
|
|
111
|
+
Store Worker.
|
|
112
|
+
|
|
113
|
+
Cloudflare Vite plugin v2 projects can define the same service-binding setup in
|
|
114
|
+
`cloudflare.config.ts` without a second Wrangler config:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { defineConfig, defineWorker } from "@cloudflare/vite-plugin/experimental-config";
|
|
118
|
+
import { createWorkersResponseStoreServiceBindingConfig } from "@vinext/cloudflare/cache/config";
|
|
119
|
+
|
|
120
|
+
const responseStore = await createWorkersResponseStoreServiceBindingConfig({
|
|
121
|
+
accountId: "<your-account-id>",
|
|
122
|
+
worker: {
|
|
123
|
+
name: "example-response-store",
|
|
124
|
+
compatibilityDate: "2026-09-15",
|
|
125
|
+
compatibilityFlags: ["nodejs_compat"],
|
|
126
|
+
observability: {
|
|
127
|
+
enabled: true,
|
|
128
|
+
headSamplingRate: 1,
|
|
129
|
+
logs: { enabled: true, invocationLogs: true },
|
|
130
|
+
traces: { enabled: true, headSamplingRate: 0.1 },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
bucket: "example-response-store-cache-bodies",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const responseStoreServiceBinding = responseStore.serviceBindingWorker;
|
|
137
|
+
|
|
138
|
+
export default defineConfig({
|
|
139
|
+
accountId: responseStore.accountId,
|
|
140
|
+
worker: defineWorker({
|
|
141
|
+
...responseStore.applicationWorker,
|
|
142
|
+
name: "example",
|
|
143
|
+
entrypoint: "./worker.ts",
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`worker.observability` accepts the full observability configuration supported by
|
|
149
|
+
your installed Cloudflare Vite plugin, including logs and traces settings.
|
|
150
|
+
The optional `accountId` is returned at the top level: pass it to `defineConfig`,
|
|
151
|
+
not to either Worker. It applies to both Workers in the project.
|
|
152
|
+
|
|
153
|
+
The async config helpers import bindings and exports from your installed
|
|
154
|
+
`@cloudflare/vite-plugin/experimental-config`. The plugin is an optional peer
|
|
155
|
+
dependency of `@vinext/cloudflare`; these helpers require v2, but Wrangler-only
|
|
156
|
+
projects do not need it.
|
|
157
|
+
|
|
158
|
+
Register the exported auxiliary Worker with the Cloudflare Vite plugin so it
|
|
159
|
+
is built alongside the application:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
163
|
+
import { responseStoreServiceBinding } from "./cloudflare.config.ts";
|
|
164
|
+
|
|
165
|
+
cloudflare({ auxiliaryWorkers: [{ config: responseStoreServiceBinding }] });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Metadata sharding is opt-in and works in either deployment mode:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
vinext({ cache: responseStoreAdapter({ shards: 16 }) });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Keys remain pinned to one shard while tag/path mutations fan out across every
|
|
175
|
+
shard. Omit `shards` to retain the original single metadata Durable Object.
|
|
176
|
+
|
|
177
|
+
To place newly created metadata Durable Objects near a stable traffic and R2
|
|
178
|
+
region, pass a Cloudflare location hint:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
vinext({ cache: responseStoreAdapter({ locationHint: "weur" }) });
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The hint works in both deployment modes, is best-effort, and only affects each
|
|
185
|
+
Durable Object's first creation. Changing it does not relocate existing
|
|
186
|
+
objects; treat the change as a cache-cold deployment and align it with the R2
|
|
187
|
+
bucket's location.
|
|
188
|
+
|
|
189
|
+
To deploy storage and cache entrypoints with the application instead, select
|
|
190
|
+
self-contained mode:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { responseStoreAdapter } from "@vinext/cloudflare/cache/response-store-adapter";
|
|
194
|
+
|
|
195
|
+
vinext({ cache: responseStoreAdapter({ mode: "self-contained" }) });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The corresponding typed config helper is
|
|
199
|
+
`createWorkersResponseStoreSelfContainedConfig`.
|
|
200
|
+
|
|
201
|
+
In this mode `vinext init` places the required R2, SQLite Durable Object,
|
|
202
|
+
Workers Cache entrypoint, and version-metadata configuration in
|
|
203
|
+
`wrangler.jsonc`; no second Wrangler config is required.
|
|
204
|
+
|
|
35
205
|
## Deploy
|
|
36
206
|
|
|
37
207
|
Deploy Cloudflare Workers projects with the package CLI:
|
|
@@ -40,5 +210,27 @@ Deploy Cloudflare Workers projects with the package CLI:
|
|
|
40
210
|
npx @vinext/cloudflare deploy
|
|
41
211
|
```
|
|
42
212
|
|
|
213
|
+
Projects with `cloudflare.config.ts` opt into the experimental Cloudflare Vite
|
|
214
|
+
plugin v2 path and deploy their generated Build Output with `cf`. Existing
|
|
215
|
+
Wrangler-configured projects continue to use Wrangler. A normal typed-config
|
|
216
|
+
deploy needs neither `wrangler.jsonc` nor the Wrangler package.
|
|
217
|
+
|
|
218
|
+
`vinext-cloudflare deploy` only deploys the entry Worker. It never deploys named
|
|
219
|
+
auxiliary Workers, including during staged warming or `--no-promote` uploads.
|
|
220
|
+
Deploy a configured Response Store Worker explicitly after building, when first
|
|
221
|
+
setting it up or when its code/config changes:
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
cf deploy --prebuilt --mode production --worker example-response-store
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Use the mode and Worker name from your build. `cloudflare.config.ts` remains the
|
|
228
|
+
source of truth; no auxiliary Wrangler config is required.
|
|
229
|
+
|
|
230
|
+
Experimental staged CDN warming uses `cf` to upload a version, read deployment
|
|
231
|
+
status, stage and promote traffic, and apply triggers. Typed-config projects do
|
|
232
|
+
not need an equivalent Wrangler config. Existing Wrangler-configured projects
|
|
233
|
+
continue to use Wrangler for this flow.
|
|
234
|
+
|
|
43
235
|
With Vite+, use `vpx @vinext/cloudflare deploy`, or
|
|
44
236
|
`vp exec vinext-cloudflare deploy` when running the locally installed bin.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/cache/cdn-adapter-config.d.ts
|
|
2
|
+
type WranglerWorkerExport = {
|
|
3
|
+
cache?: {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
};
|
|
6
|
+
type?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
type WranglerOutputConfig = {
|
|
10
|
+
compatibility_date?: string;
|
|
11
|
+
compatibility_flags?: string[];
|
|
12
|
+
exports?: Record<string, WranglerWorkerExport>;
|
|
13
|
+
version_metadata?: unknown;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
type VersionMetadataOptions = {
|
|
17
|
+
binding: string;
|
|
18
|
+
bindingIsExplicit: boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare function configureCdnVersionMetadata(config: WranglerOutputConfig, options: VersionMetadataOptions): WranglerOutputConfig;
|
|
21
|
+
/** Add the per-entrypoint Workers Cache policy to an emitted Wrangler config. */
|
|
22
|
+
export declare function configureWorkersCacheEntrypoints(config: WranglerOutputConfig): WranglerOutputConfig;
|
|
23
|
+
/** Apply the complete CDN adapter policy to the primary generated config. */
|
|
24
|
+
export declare function finalizeCdnAdapterBuildOutput({ root, outDir, isPrimaryServerOutput, binding, bindingIsExplicit }: {
|
|
25
|
+
root: string;
|
|
26
|
+
outDir: string;
|
|
27
|
+
isPrimaryServerOutput: boolean;
|
|
28
|
+
binding: string;
|
|
29
|
+
bindingIsExplicit: boolean;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
//#endregion
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
//#region src/cache/cdn-adapter-config.ts
|
|
4
|
+
const CACHED_RESPONSE_STAGE_EXPORT = "VinextCachedResponse";
|
|
5
|
+
const UNCACHED_RESPONSE_STAGE_EXPORT = "VinextUncachedResponse";
|
|
6
|
+
const CTX_EXPORTS_DEFAULT_DATE = "2025-11-17";
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function configureCdnVersionMetadata(config, options) {
|
|
11
|
+
if (!Object.hasOwn(config, "version_metadata")) return {
|
|
12
|
+
...config,
|
|
13
|
+
version_metadata: { binding: options.binding }
|
|
14
|
+
};
|
|
15
|
+
if (!isRecord(config.version_metadata)) throw new Error("[vinext] The generated Wrangler config has an invalid version_metadata value.");
|
|
16
|
+
const existingBinding = config.version_metadata.binding;
|
|
17
|
+
if (typeof existingBinding !== "string" || existingBinding.length === 0) throw new Error("[vinext] The generated Wrangler config has an invalid version_metadata.binding value.");
|
|
18
|
+
if (existingBinding === options.binding) return config;
|
|
19
|
+
if (!options.bindingIsExplicit) throw new Error(`[vinext] cdnAdapter() uses the default version metadata binding ${JSON.stringify(options.binding)}, but the generated Wrangler config already declares ${JSON.stringify(existingBinding)}. Align the Wrangler binding or configure cdnAdapter({ versionMetadataBinding: ${JSON.stringify(existingBinding)} }).`);
|
|
20
|
+
return {
|
|
21
|
+
...config,
|
|
22
|
+
version_metadata: { binding: options.binding }
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Add the per-entrypoint Workers Cache policy to an emitted Wrangler config. */
|
|
26
|
+
function configureWorkersCacheEntrypoints(config) {
|
|
27
|
+
const configuredExports = config.exports ?? {};
|
|
28
|
+
const compatibilityFlags = config.compatibility_flags ?? [];
|
|
29
|
+
if (compatibilityFlags.includes("disable_ctx_exports")) throw new Error("[vinext] cdnAdapter() requires ctx.exports, but the generated Wrangler config explicitly disables it with disable_ctx_exports.");
|
|
30
|
+
const configuredCompatibilityFlags = config.compatibility_date === void 0 || config.compatibility_date < CTX_EXPORTS_DEFAULT_DATE ? compatibilityFlags.includes("enable_ctx_exports") ? compatibilityFlags : [...compatibilityFlags, "enable_ctx_exports"] : compatibilityFlags.filter((flag) => flag !== "enable_ctx_exports");
|
|
31
|
+
for (const name of [
|
|
32
|
+
"default",
|
|
33
|
+
CACHED_RESPONSE_STAGE_EXPORT,
|
|
34
|
+
UNCACHED_RESPONSE_STAGE_EXPORT
|
|
35
|
+
]) {
|
|
36
|
+
const existing = configuredExports[name];
|
|
37
|
+
if (existing?.type !== void 0 && existing.type !== "worker") throw new Error(`[vinext] cdnAdapter() cannot configure the reserved Worker export ${JSON.stringify(name)} because it is already declared as ${JSON.stringify(existing.type)}.`);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...config,
|
|
41
|
+
compatibility_flags: configuredCompatibilityFlags,
|
|
42
|
+
exports: {
|
|
43
|
+
...configuredExports,
|
|
44
|
+
default: {
|
|
45
|
+
...configuredExports.default,
|
|
46
|
+
type: "worker",
|
|
47
|
+
cache: { enabled: false }
|
|
48
|
+
},
|
|
49
|
+
[CACHED_RESPONSE_STAGE_EXPORT]: {
|
|
50
|
+
...configuredExports[CACHED_RESPONSE_STAGE_EXPORT],
|
|
51
|
+
type: "worker",
|
|
52
|
+
cache: { enabled: true }
|
|
53
|
+
},
|
|
54
|
+
[UNCACHED_RESPONSE_STAGE_EXPORT]: {
|
|
55
|
+
...configuredExports[UNCACHED_RESPONSE_STAGE_EXPORT],
|
|
56
|
+
type: "worker",
|
|
57
|
+
cache: { enabled: false }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function readGeneratedConfig(generatedConfigPath, allowMissing) {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(await fs.readFile(generatedConfigPath, "utf8"));
|
|
65
|
+
if (!isRecord(parsed)) throw new TypeError("the root value must be an object");
|
|
66
|
+
return parsed;
|
|
67
|
+
} catch (cause) {
|
|
68
|
+
if (allowMissing && cause !== null && typeof cause === "object" && "code" in cause && cause.code === "ENOENT") return null;
|
|
69
|
+
throw new Error(`[vinext] Could not read the generated Wrangler config at ${generatedConfigPath}.`, { cause });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function writeGeneratedConfig(generatedConfigPath, config) {
|
|
73
|
+
await fs.writeFile(generatedConfigPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
74
|
+
}
|
|
75
|
+
async function usesCloudflareBuildOutput(root) {
|
|
76
|
+
try {
|
|
77
|
+
return (await fs.stat(path.resolve(root, "cloudflare.config.ts"))).isFile();
|
|
78
|
+
} catch (cause) {
|
|
79
|
+
if (cause !== null && typeof cause === "object" && "code" in cause && cause.code === "ENOENT") return false;
|
|
80
|
+
throw cause;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Apply the complete CDN adapter policy to the primary generated config. */
|
|
84
|
+
async function finalizeCdnAdapterBuildOutput({ root, outDir, isPrimaryServerOutput, binding, bindingIsExplicit }) {
|
|
85
|
+
if (!isPrimaryServerOutput) return;
|
|
86
|
+
const generatedConfigPath = path.resolve(outDir, "wrangler.json");
|
|
87
|
+
const generatedConfig = await readGeneratedConfig(generatedConfigPath, await usesCloudflareBuildOutput(root));
|
|
88
|
+
if (!generatedConfig) return;
|
|
89
|
+
await writeGeneratedConfig(generatedConfigPath, configureWorkersCacheEntrypoints(configureCdnVersionMetadata(generatedConfig, {
|
|
90
|
+
binding,
|
|
91
|
+
bindingIsExplicit
|
|
92
|
+
})));
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { configureCdnVersionMetadata, configureWorkersCacheEntrypoints, finalizeCdnAdapterBuildOutput };
|
|
@@ -1,25 +1,58 @@
|
|
|
1
1
|
//#region src/cache/cdn-adapter.d.ts
|
|
2
|
+
export declare const DEFAULT_CDN_VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
|
|
3
|
+
/** Options accepted by {@link cdnAdapter}, forwarded to the runtime factory. */
|
|
4
|
+
export type CdnAdapterOptions = {
|
|
5
|
+
/** Version metadata binding used to verify version-overridden requests. */
|
|
6
|
+
versionMetadataBinding?: string;
|
|
7
|
+
};
|
|
2
8
|
/**
|
|
3
9
|
* Cloudflare CDN cache adapter - edge-managed page-level ISR backed by the
|
|
4
10
|
* Cloudflare Workers Cache.
|
|
5
11
|
*
|
|
6
12
|
* Unlike the data adapter (which stores cache entries in a durable store and
|
|
7
|
-
* serves HIT/STALE itself), this adapter delegates serving to
|
|
8
|
-
*
|
|
13
|
+
* serves HIT/STALE itself), this adapter delegates serving to Workers Cache on
|
|
14
|
+
* a named Worker entrypoint. The default entrypoint always runs middleware and
|
|
15
|
+
* request-time routing before dispatching to cached or uncached response-stage
|
|
16
|
+
* entrypoints.
|
|
17
|
+
*
|
|
18
|
+
* The deployment configuration enables Workers Cache only for that response-stage
|
|
19
|
+
* export, so cache hits do not start the application stage. Legacy Cloudflare
|
|
20
|
+
* builds receive this policy in their generated Wrangler config. Cloudflare Vite
|
|
21
|
+
* plugin v2 builds declare the same entrypoint policy in `cloudflare.config.ts`.
|
|
22
|
+
* The uncached response entrypoint keeps bypass and probe renders out of the
|
|
23
|
+
* gateway without enabling Workers Cache for them.
|
|
9
24
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* {
|
|
14
|
-
* "cache": {
|
|
15
|
-
* "enabled": true,
|
|
16
|
-
* },
|
|
17
|
-
* }
|
|
18
|
-
* ```
|
|
25
|
+
* The adapter adds a transport-only URL digest so distinct response-stage
|
|
26
|
+
* identities cannot collide. Workers Cache owns this key independently of
|
|
27
|
+
* zone Cache Rules.
|
|
19
28
|
*/
|
|
20
|
-
declare function cdnAdapter(options?:
|
|
29
|
+
export declare function cdnAdapter(options?: CdnAdapterOptions): {
|
|
21
30
|
adapter: string;
|
|
22
|
-
options:
|
|
31
|
+
options: CdnAdapterOptions | undefined;
|
|
32
|
+
output: {
|
|
33
|
+
entry: string;
|
|
34
|
+
matchesBuild({ plugins }: {
|
|
35
|
+
plugins: readonly {
|
|
36
|
+
name?: string;
|
|
37
|
+
}[];
|
|
38
|
+
}): boolean;
|
|
39
|
+
transformHostEntry({ code, id }: {
|
|
40
|
+
code: string;
|
|
41
|
+
id: string;
|
|
42
|
+
}): string | null;
|
|
43
|
+
finalizeBuildOutput({ root, outDir, isPrimaryServerOutput }: {
|
|
44
|
+
root: string;
|
|
45
|
+
outDir: string;
|
|
46
|
+
isPrimaryServerOutput: boolean;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
type: "multi-stage";
|
|
49
|
+
};
|
|
50
|
+
capabilities: {
|
|
51
|
+
buildIdentity: "response-header";
|
|
52
|
+
isResponsePolicyHeader: (name: string) => boolean;
|
|
53
|
+
requestRouting: "uncached-stage";
|
|
54
|
+
responseVary: "verbatim";
|
|
55
|
+
routeCacheability: "probe-manifest";
|
|
56
|
+
};
|
|
23
57
|
};
|
|
24
|
-
//#endregion
|
|
25
|
-
export { cdnAdapter };
|
|
58
|
+
//#endregion
|
|
@@ -1,28 +1,65 @@
|
|
|
1
|
+
import { finalizeCdnAdapterBuildOutput } from "./cdn-adapter-config.js";
|
|
1
2
|
import { fileURLToPath } from "node:url";
|
|
2
3
|
//#region src/cache/cdn-adapter.ts
|
|
4
|
+
const DEFAULT_CDN_VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
|
|
5
|
+
const CLOUDFLARE_WORKER_ENTRY_ID = "virtual:cloudflare/worker-entry";
|
|
3
6
|
/**
|
|
4
7
|
* Cloudflare CDN cache adapter - edge-managed page-level ISR backed by the
|
|
5
8
|
* Cloudflare Workers Cache.
|
|
6
9
|
*
|
|
7
10
|
* Unlike the data adapter (which stores cache entries in a durable store and
|
|
8
|
-
* serves HIT/STALE itself), this adapter delegates serving to
|
|
9
|
-
*
|
|
11
|
+
* serves HIT/STALE itself), this adapter delegates serving to Workers Cache on
|
|
12
|
+
* a named Worker entrypoint. The default entrypoint always runs middleware and
|
|
13
|
+
* request-time routing before dispatching to cached or uncached response-stage
|
|
14
|
+
* entrypoints.
|
|
10
15
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* The deployment configuration enables Workers Cache only for that response-stage
|
|
17
|
+
* export, so cache hits do not start the application stage. Legacy Cloudflare
|
|
18
|
+
* builds receive this policy in their generated Wrangler config. Cloudflare Vite
|
|
19
|
+
* plugin v2 builds declare the same entrypoint policy in `cloudflare.config.ts`.
|
|
20
|
+
* The uncached response entrypoint keeps bypass and probe renders out of the
|
|
21
|
+
* gateway without enabling Workers Cache for them.
|
|
22
|
+
*
|
|
23
|
+
* The adapter adds a transport-only URL digest so distinct response-stage
|
|
24
|
+
* identities cannot collide. Workers Cache owns this key independently of
|
|
25
|
+
* zone Cache Rules.
|
|
20
26
|
*/
|
|
21
27
|
function cdnAdapter(options) {
|
|
28
|
+
if (options?.versionMetadataBinding !== void 0 && (typeof options.versionMetadataBinding !== "string" || options.versionMetadataBinding.length === 0)) throw new TypeError("[vinext] cdnAdapter({ versionMetadataBinding }) must be a non-empty string binding name.");
|
|
29
|
+
const versionMetadataBinding = options?.versionMetadataBinding ?? "CF_VERSION_METADATA";
|
|
30
|
+
const bindingIsExplicit = options?.versionMetadataBinding !== void 0;
|
|
31
|
+
const workerEntry = fileURLToPath(import.meta.resolve("./cdn-adapter.worker.js"));
|
|
22
32
|
return {
|
|
23
33
|
adapter: fileURLToPath(import.meta.resolve("./cdn-adapter.runtime.js")),
|
|
24
|
-
options
|
|
34
|
+
options,
|
|
35
|
+
output: {
|
|
36
|
+
entry: workerEntry,
|
|
37
|
+
matchesBuild({ plugins }) {
|
|
38
|
+
return plugins.some(({ name }) => name === "vite-plugin-cloudflare" || name?.startsWith("vite-plugin-cloudflare:"));
|
|
39
|
+
},
|
|
40
|
+
transformHostEntry({ code, id }) {
|
|
41
|
+
if ((id.charCodeAt(0) === 0 ? id.slice(1) : id) !== CLOUDFLARE_WORKER_ENTRY_ID) return null;
|
|
42
|
+
return `${code}\nexport { VinextCachedResponse, VinextUncachedResponse } from ${JSON.stringify(workerEntry)};\n`;
|
|
43
|
+
},
|
|
44
|
+
finalizeBuildOutput({ root, outDir, isPrimaryServerOutput }) {
|
|
45
|
+
return finalizeCdnAdapterBuildOutput({
|
|
46
|
+
root,
|
|
47
|
+
outDir,
|
|
48
|
+
isPrimaryServerOutput,
|
|
49
|
+
binding: versionMetadataBinding,
|
|
50
|
+
bindingIsExplicit
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
type: "multi-stage"
|
|
54
|
+
},
|
|
55
|
+
capabilities: {
|
|
56
|
+
buildIdentity: "response-header",
|
|
57
|
+
isResponsePolicyHeader: (name) => name.toLowerCase() === "cdn-cache-control" || name.toLowerCase() === "cloudflare-cdn-cache-control",
|
|
58
|
+
requestRouting: "uncached-stage",
|
|
59
|
+
responseVary: "verbatim",
|
|
60
|
+
routeCacheability: "probe-manifest"
|
|
61
|
+
}
|
|
25
62
|
};
|
|
26
63
|
}
|
|
27
64
|
//#endregion
|
|
28
|
-
export { cdnAdapter };
|
|
65
|
+
export { DEFAULT_CDN_VERSION_METADATA_BINDING, cdnAdapter };
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import { CdnCacheAdapter, CdnCacheableHeaderInput, CdnResponseHeaders } from "vinext/shims/cdn-cache";
|
|
2
2
|
import { CacheHandlerValue, IncrementalCacheValue } from "vinext/shims/cache";
|
|
3
|
-
|
|
4
3
|
//#region src/cache/cdn-adapter.runtime.d.ts
|
|
5
|
-
|
|
4
|
+
type WorkerVersionMetadata = {
|
|
5
|
+
id: string;
|
|
6
|
+
};
|
|
7
|
+
type CdnAdapterOptions = {
|
|
8
|
+
versionMetadataBinding?: string;
|
|
9
|
+
};
|
|
10
|
+
/** Encode a case-sensitive Next.js tag into a fixed Workers Cache tag. */
|
|
11
|
+
export declare function encodeCloudflareCacheTag(tag: string): string;
|
|
12
|
+
export declare class CloudflareCdnCacheAdapter implements CdnCacheAdapter {
|
|
13
|
+
private readonly versionMetadata?;
|
|
14
|
+
private readonly versionMetadataBinding;
|
|
15
|
+
readonly requiresCompletedResponseAdmission = true;
|
|
16
|
+
readonly responseVary: "verbatim";
|
|
17
|
+
readonly responsePolicy: {
|
|
18
|
+
isHeader(name: string): boolean;
|
|
19
|
+
readCacheControl(headers: Headers): string | null;
|
|
20
|
+
hasExplicitNonCacheablePolicy(headers: Headers, baseline?: Headers): boolean;
|
|
21
|
+
};
|
|
22
|
+
constructor(versionMetadata?: WorkerVersionMetadata | undefined, versionMetadataBinding?: string);
|
|
23
|
+
validateRequest(request: Request): Response | null;
|
|
6
24
|
readonly ownsBackgroundRevalidation = false;
|
|
7
25
|
/**
|
|
8
26
|
* The origin keeps no page store — return null so the request renders fresh.
|
|
@@ -11,12 +29,16 @@ declare class CloudflareCdnCacheAdapter implements CdnCacheAdapter {
|
|
|
11
29
|
get(): Promise<CacheHandlerValue | null>;
|
|
12
30
|
/** No-op: the platform caches the response via its headers, not an origin store. */
|
|
13
31
|
set(_key: string, _data: IncrementalCacheValue | null, _ctx?: Record<string, unknown>): Promise<void>;
|
|
32
|
+
buildResponseIdentityHeaders(): CdnResponseHeaders;
|
|
14
33
|
buildResponseHeaders(input: CdnCacheableHeaderInput): CdnResponseHeaders;
|
|
15
34
|
/** Purge edge-cached responses by tag via the request context's `cache.purge`. */
|
|
16
35
|
revalidateTag(tags: string | string[], _durations?: {
|
|
17
36
|
expire?: number;
|
|
18
37
|
}): Promise<void>;
|
|
19
38
|
}
|
|
20
|
-
declare const createCloudflareCdnCacheAdapter: (
|
|
39
|
+
declare const createCloudflareCdnCacheAdapter: ({ env, options }?: {
|
|
40
|
+
env?: Record<string, unknown>;
|
|
41
|
+
options?: CdnAdapterOptions;
|
|
42
|
+
}) => CdnCacheAdapter;
|
|
21
43
|
//#endregion
|
|
22
|
-
export {
|
|
44
|
+
export { createCloudflareCdnCacheAdapter as default };
|