@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.
Files changed (66) hide show
  1. package/README.md +193 -1
  2. package/dist/cache/cdn-adapter-config.d.ts +31 -0
  3. package/dist/cache/cdn-adapter-config.js +95 -0
  4. package/dist/cache/cdn-adapter.d.ts +48 -15
  5. package/dist/cache/cdn-adapter.js +50 -13
  6. package/dist/cache/cdn-adapter.runtime.d.ts +26 -4
  7. package/dist/cache/cdn-adapter.runtime.js +150 -37
  8. package/dist/cache/cdn-adapter.worker.d.ts +31 -0
  9. package/dist/cache/cdn-adapter.worker.js +373 -0
  10. package/dist/cache/cdn-build-id.d.ts +6 -0
  11. package/dist/cache/cdn-build-id.js +9 -0
  12. package/dist/cache/config.d.ts +137 -0
  13. package/dist/cache/config.js +87 -0
  14. package/dist/cache/kv-data-adapter.d.ts +32 -8
  15. package/dist/cache/kv-data-adapter.js +5 -1
  16. package/dist/cache/kv-data-adapter.runtime.d.ts +45 -13
  17. package/dist/cache/kv-data-adapter.runtime.js +132 -52
  18. package/dist/cache/kv-key.d.ts +15 -0
  19. package/dist/cache/kv-key.js +43 -0
  20. package/dist/cache/response-store-adapter.d.ts +73 -0
  21. package/dist/cache/response-store-adapter.js +64 -0
  22. package/dist/cache/response-store-adapter.self-contained.worker.d.ts +18 -0
  23. package/dist/cache/response-store-adapter.self-contained.worker.js +12 -0
  24. package/dist/cache/response-store-adapter.service-binding.worker.d.ts +16 -0
  25. package/dist/cache/response-store-adapter.service-binding.worker.js +11 -0
  26. package/dist/cache/response-store-adapter.worker.d.ts +14 -0
  27. package/dist/cache/response-store-adapter.worker.js +278 -0
  28. package/dist/cache/response-store-cdn.runtime.d.ts +5 -0
  29. package/dist/cache/response-store-cdn.runtime.js +41 -0
  30. package/dist/cache/response-store-data.runtime.d.ts +34 -0
  31. package/dist/cache/response-store-data.runtime.js +300 -0
  32. package/dist/cacheability-artifact.d.ts +10 -0
  33. package/dist/cacheability-artifact.js +215 -0
  34. package/dist/cacheability-manifest-limits.d.ts +4 -0
  35. package/dist/cacheability-manifest-limits.js +7 -0
  36. package/dist/cacheability-probe.d.ts +49 -0
  37. package/dist/cacheability-probe.js +660 -0
  38. package/dist/cdn-warm.d.ts +110 -12
  39. package/dist/cdn-warm.js +880 -82
  40. package/dist/cli.d.ts +1 -1
  41. package/dist/cli.js +15 -1
  42. package/dist/deploy-config.d.ts +14 -11
  43. package/dist/deploy-config.js +24 -5
  44. package/dist/deploy-help.d.ts +3 -4
  45. package/dist/deploy-help.js +65 -22
  46. package/dist/deploy.d.ts +139 -42
  47. package/dist/deploy.js +946 -132
  48. package/dist/images/images-optimizer.d.ts +5 -5
  49. package/dist/images/images-optimizer.runtime.d.ts +1 -5
  50. package/dist/prerender-kv-populate.d.ts +3 -4
  51. package/dist/prerender-kv-populate.js +51 -18
  52. package/dist/tpr.d.ts +34 -70
  53. package/dist/tpr.js +50 -603
  54. package/dist/utils/cache-control-metadata.d.ts +3 -4
  55. package/dist/version-deploy.d.ts +46 -17
  56. package/dist/version-deploy.js +151 -14
  57. package/dist/version-headers.d.ts +2 -0
  58. package/dist/version-headers.js +2 -0
  59. package/dist/worker-deployment-url.d.ts +9 -0
  60. package/dist/worker-deployment-url.js +45 -0
  61. package/dist/workers-dev-url.d.ts +2 -3
  62. package/dist/wrangler-config.d.ts +19 -0
  63. package/dist/wrangler-config.js +294 -0
  64. package/dist/wrangler-version-metadata.d.ts +7 -0
  65. package/dist/wrangler-version-metadata.js +9 -0
  66. package/package.json +21 -11
@@ -1,3 +1,4 @@
1
+ import { ENTRY_PREFIX, createKvKeySpace } from "./kv-key.js";
1
2
  import { isUnknownRecord, readCacheControlNumberField } from "../utils/cache-control-metadata.js";
2
3
  import { Buffer } from "node:buffer";
3
4
  import { getRequestExecutionContext } from "vinext/shims/request-context";
@@ -23,14 +24,14 @@ import { getRequestExecutionContext } from "vinext/shims/request-context";
23
24
  */
24
25
  /** Default KV namespace binding name read from the Worker `env`. */
25
26
  const DEFAULT_BINDING = "VINEXT_KV_CACHE";
26
- /** Key prefix for tag invalidation timestamps. */
27
- const TAG_PREFIX = "__tag:";
28
- /** Key prefix for cache entries. */
29
- const ENTRY_PREFIX = "cache:";
30
27
  /** Prefix used by revalidatePath for path-based tags. */
31
28
  const PATH_TAG_PREFIX = "_N_T_";
32
29
  /** Max tag length to prevent KV key abuse. */
33
30
  const MAX_TAG_LENGTH = 256;
31
+ /** The runtime rejects a lower `cacheTtl` with "Cache TTL must be at least 30". */
32
+ const MIN_KV_CACHE_TTL_SECONDS = 30;
33
+ /** Cloudflare caps a bulk `get()` at 100 keys per call. */
34
+ const KV_BULK_GET_LIMIT = 100;
34
35
  /** Matches a valid base64 string (standard alphabet with optional padding). */
35
36
  const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
36
37
  /**
@@ -96,53 +97,92 @@ function isPathChildOf(path, prefix) {
96
97
  */
97
98
  var KVCacheHandler = class {
98
99
  kv;
99
- prefix;
100
+ keySpace;
100
101
  ctx;
101
102
  ttlSeconds;
102
103
  /** Local in-memory cache for tag invalidation timestamps. Avoids redundant KV reads. */
103
104
  _tagCache = /* @__PURE__ */ new Map();
105
+ /** Monotonic ordering for concurrent tag-cache fills and local invalidations. */
106
+ _tagCacheOrder = 0;
104
107
  /** TTL (ms) for local tag cache entries. After this, re-fetch from KV. */
105
108
  _tagCacheTtl;
109
+ /** Read options for entry keys only. `undefined` keeps the KV default cacheTtl. */
110
+ _entryReadOptions;
106
111
  constructor(kvNamespace, options) {
107
112
  this.kv = kvNamespace;
108
- this.prefix = options?.appPrefix ? `${options.appPrefix}:` : "";
113
+ this.keySpace = createKvKeySpace(options?.appPrefix);
109
114
  this.ctx = options?.ctx;
110
- this.ttlSeconds = options?.ttlSeconds ?? 720 * 3600;
115
+ this.ttlSeconds = options?.ttlSeconds ?? 2592e3;
111
116
  this._tagCacheTtl = options?.tagCacheTtlMs ?? 5e3;
117
+ const entryCacheTtl = options?.entryCacheTtlSeconds;
118
+ this._entryReadOptions = typeof entryCacheTtl === "number" && Number.isFinite(entryCacheTtl) ? { cacheTtl: Math.max(MIN_KV_CACHE_TTL_SECONDS, Math.floor(entryCacheTtl)) } : void 0;
119
+ }
120
+ _entryKey(key) {
121
+ return this.keySpace.entryKey(key);
122
+ }
123
+ _tagKey(tag) {
124
+ return this.keySpace.tagKey(tag);
112
125
  }
113
126
  async get(key, _ctx) {
114
- const kvKey = this.prefix + ENTRY_PREFIX + key;
115
- const raw = await this.kv.get(kvKey);
127
+ const kvKey = this._entryKey(key);
128
+ const softTags = validUniqueTags(readStringArrayField(_ctx, "softTags"));
129
+ const entryRead = this._entryReadOptions ? this.kv.get(kvKey, this._entryReadOptions) : this.kv.get(kvKey);
130
+ let softTagCache = this._tagCache;
131
+ const softTagPrime = this._primeTagCache(softTags);
132
+ if (softTags.length > 0) {
133
+ const backgroundPrime = softTagPrime.catch(() => {});
134
+ const ctx = getRequestExecutionContext() ?? this.ctx;
135
+ if (ctx) ctx.waitUntil(backgroundPrime);
136
+ }
137
+ const raw = await entryRead;
116
138
  if (!raw) return null;
117
139
  let parsed;
118
140
  try {
119
141
  parsed = JSON.parse(raw);
120
142
  } catch {
121
- this._deleteInBackground(kvKey);
143
+ this._deleteEntryReadInBackground(kvKey);
122
144
  return null;
123
145
  }
124
146
  const entry = validateCacheEntry(parsed);
125
147
  if (!entry) {
126
148
  console.error("[vinext] Invalid cache entry shape for key:", key);
127
- this._deleteInBackground(kvKey);
149
+ this._deleteEntryReadInBackground(kvKey);
128
150
  return null;
129
151
  }
130
152
  let restoredValue = null;
131
153
  if (entry.value) {
132
154
  restoredValue = restoreArrayBuffers(entry.value);
133
155
  if (!restoredValue) {
134
- this._deleteInBackground(kvKey);
156
+ this._deleteEntryReadInBackground(kvKey);
135
157
  return null;
136
158
  }
137
159
  }
138
- if (await this._hasRevalidatedTag(validUniqueTags(entry.tags), entry.lastModified)) {
139
- this._deleteInBackground(kvKey);
160
+ const entryTags = validUniqueTags(entry.tags);
161
+ if (this._hasRevalidatedTag(entryTags, entry.lastModified, true)) {
162
+ this._deleteEntryReadInBackground(kvKey);
140
163
  return null;
141
164
  }
142
- const softTags = validUniqueTags(readStringArrayField(_ctx, "softTags"));
143
- if (await this._hasRevalidatedTag(softTags, entry.lastModified)) return null;
165
+ await softTagPrime;
166
+ while (softTagCache !== this._tagCache) {
167
+ softTagCache = this._tagCache;
168
+ await this._primeTagCache(softTags);
169
+ }
170
+ let invalidated = this._hasRevalidatedTag(entryTags, entry.lastModified, true);
171
+ if (!invalidated) {
172
+ await this._primeTagCache(entryTags);
173
+ while (softTagCache !== this._tagCache) {
174
+ softTagCache = this._tagCache;
175
+ await this._primeTagCache([.../* @__PURE__ */ new Set([...softTags, ...entryTags])]);
176
+ }
177
+ invalidated = this._hasRevalidatedTag(entryTags, entry.lastModified);
178
+ }
179
+ if (invalidated) {
180
+ this._deleteEntryReadInBackground(kvKey);
181
+ return null;
182
+ }
183
+ if (this._hasRevalidatedTag(softTags, entry.lastModified)) return null;
144
184
  if (entry.expireAt !== void 0 && entry.expireAt !== null && Date.now() > entry.expireAt) {
145
- this._deleteInBackground(kvKey);
185
+ this._deleteEntryReadInBackground(kvKey);
146
186
  return null;
147
187
  }
148
188
  const now = Date.now();
@@ -161,37 +201,63 @@ var KVCacheHandler = class {
161
201
  };
162
202
  }
163
203
  /**
164
- * Check tag invalidation markers for stored tags or read-time soft tags.
165
- * Uses a local in-memory cache to avoid redundant KV reads for recently-seen tags.
204
+ * Load the invalidation markers these tags still need into the local cache.
205
+ * Tags a recent read already cached cost nothing, so the entry-tag batch only
206
+ * pays for what the soft-tag batch did not already fetch.
166
207
  */
167
- async _hasRevalidatedTag(tags, lastModified) {
168
- if (tags.length === 0) return false;
208
+ async _primeTagCache(tags) {
209
+ if (tags.length === 0) return;
210
+ const tagCache = this._tagCache;
169
211
  const now = Date.now();
170
- const uncachedTags = [];
212
+ const missing = tags.filter((tag) => {
213
+ const cached = tagCache.get(tag);
214
+ if (cached && now - cached.fetchedAt < this._tagCacheTtl) return false;
215
+ if (cached) tagCache.delete(tag);
216
+ return true;
217
+ });
218
+ if (missing.length === 0) return;
219
+ const order = ++this._tagCacheOrder;
220
+ const markers = await this._readTagMarkers(missing.map((tag) => this._tagKey(tag)));
221
+ for (const tag of missing) {
222
+ const current = tagCache.get(tag);
223
+ if (current && current.order >= order) continue;
224
+ const marker = markers.get(this._tagKey(tag));
225
+ tagCache.set(tag, {
226
+ timestamp: marker ? Number(marker) : 0,
227
+ fetchedAt: now,
228
+ order
229
+ });
230
+ }
231
+ }
232
+ /**
233
+ * Read every marker key, one round trip per 100-key chunk.
234
+ *
235
+ * Markers never take the entry `cacheTtl`: `revalidateTag()` writes them, so
236
+ * a longer colo cache would hide a publish from that colo for its span. They
237
+ * keep KV's own default cacheTtl of 60 s.
238
+ */
239
+ async _readTagMarkers(keys) {
240
+ if (keys.length === 1) return /* @__PURE__ */ new Map([[keys[0], await this.kv.get(keys[0])]]);
241
+ const chunks = [];
242
+ for (let i = 0; i < keys.length; i += KV_BULK_GET_LIMIT) chunks.push(keys.slice(i, i + KV_BULK_GET_LIMIT));
243
+ const results = await Promise.all(chunks.map((chunk) => this.kv.get(chunk)));
244
+ const markers = /* @__PURE__ */ new Map();
245
+ for (const result of results) for (const [key, value] of result) markers.set(key, value);
246
+ return markers;
247
+ }
248
+ /**
249
+ * Report whether any tag has an invalidation marker at or after `lastModified`.
250
+ * Call `_primeTagCache` for the same tags first — a tag with no cache entry
251
+ * counts as never invalidated. Pass `requireFresh` to call it before a prime,
252
+ * so an entry past `tagCacheTtlMs` does not answer for a tag it never re-read.
253
+ */
254
+ _hasRevalidatedTag(tags, lastModified, requireFresh = false) {
255
+ const now = requireFresh ? Date.now() : 0;
171
256
  for (const tag of tags) {
172
257
  const cached = this._tagCache.get(tag);
173
- if (cached && now - cached.fetchedAt < this._tagCacheTtl) {
174
- if (Number.isNaN(cached.timestamp) || cached.timestamp >= lastModified) return true;
175
- } else {
176
- if (cached) this._tagCache.delete(tag);
177
- uncachedTags.push(tag);
178
- }
179
- }
180
- if (uncachedTags.length > 0) {
181
- const tagResults = await Promise.all(uncachedTags.map((tag) => this.kv.get(this.prefix + TAG_PREFIX + tag)));
182
- for (let i = 0; i < uncachedTags.length; i++) {
183
- const tagTime = tagResults[i];
184
- const tagTimestamp = tagTime ? Number(tagTime) : 0;
185
- this._tagCache.set(uncachedTags[i], {
186
- timestamp: tagTimestamp,
187
- fetchedAt: now
188
- });
189
- }
190
- for (const tag of uncachedTags) {
191
- const cached = this._tagCache.get(tag);
192
- if (!cached || cached.timestamp === 0) continue;
193
- if (Number.isNaN(cached.timestamp) || cached.timestamp >= lastModified) return true;
194
- }
258
+ if (!cached || cached.timestamp === 0) continue;
259
+ if (requireFresh && now - cached.fetchedAt >= this._tagCacheTtl) continue;
260
+ if (Number.isNaN(cached.timestamp) || cached.timestamp >= lastModified) return true;
195
261
  }
196
262
  return false;
197
263
  }
@@ -210,14 +276,16 @@ var KVCacheHandler = class {
210
276
  let effectiveExpire;
211
277
  effectiveRevalidate = readCacheControlNumberField(ctx, "revalidate");
212
278
  effectiveExpire = readCacheControlNumberField(ctx, "expire");
279
+ const effectiveStale = readCacheControlNumberField(ctx, "stale");
213
280
  if (data && "revalidate" in data && typeof data.revalidate === "number") effectiveRevalidate = data.revalidate;
214
281
  if (effectiveRevalidate === 0) return Promise.resolve();
215
282
  const now = Date.now();
216
283
  const revalidateAt = typeof effectiveRevalidate === "number" && effectiveRevalidate > 0 ? now + effectiveRevalidate * 1e3 : null;
217
284
  const expireAt = typeof effectiveExpire === "number" && effectiveExpire > 0 ? now + effectiveExpire * 1e3 : null;
218
- const cacheControl = typeof effectiveRevalidate === "number" ? effectiveExpire === void 0 ? { revalidate: effectiveRevalidate } : {
285
+ const cacheControl = typeof effectiveRevalidate === "number" ? {
219
286
  revalidate: effectiveRevalidate,
220
- expire: effectiveExpire
287
+ ...effectiveExpire === void 0 ? {} : { expire: effectiveExpire },
288
+ ...effectiveStale === void 0 ? {} : { stale: effectiveStale }
221
289
  } : void 0;
222
290
  const entry = {
223
291
  value: data ? serializeForJSON(data) : null,
@@ -229,7 +297,7 @@ var KVCacheHandler = class {
229
297
  };
230
298
  const expirationTtl = revalidateAt !== null ? this.ttlSeconds : void 0;
231
299
  const metadata = JSON.stringify({ tags }).length <= 1024 ? { tags } : void 0;
232
- return this._put(this.prefix + ENTRY_PREFIX + key, JSON.stringify(entry), {
300
+ return this._put(this._entryKey(key), JSON.stringify(entry), {
233
301
  expirationTtl,
234
302
  metadata
235
303
  });
@@ -238,10 +306,12 @@ var KVCacheHandler = class {
238
306
  const tagList = Array.isArray(tags) ? tags : [tags];
239
307
  const now = Date.now();
240
308
  const validTags = tagList.filter((t) => validateTag(t) !== null);
241
- await Promise.all(validTags.map((tag) => this.kv.put(this.prefix + TAG_PREFIX + tag, String(now), { expirationTtl: 720 * 3600 })));
309
+ await Promise.all(validTags.map((tag) => this.kv.put(this._tagKey(tag), String(now), { expirationTtl: 2592e3 })));
310
+ const order = ++this._tagCacheOrder;
242
311
  for (const tag of validTags) this._tagCache.set(tag, {
243
312
  timestamp: now,
244
- fetchedAt: now
313
+ fetchedAt: now,
314
+ order
245
315
  });
246
316
  }
247
317
  /**
@@ -263,7 +333,7 @@ var KVCacheHandler = class {
263
333
  async revalidateByPathPrefix(pathPrefix) {
264
334
  const tagsToInvalidate = /* @__PURE__ */ new Set();
265
335
  let cursor;
266
- const listPrefix = this.prefix + ENTRY_PREFIX;
336
+ const listPrefix = this.keySpace.entryPrefix;
267
337
  do {
268
338
  const page = await this.kv.list({
269
339
  prefix: listPrefix,
@@ -297,7 +367,15 @@ var KVCacheHandler = class {
297
367
  * fresh KVCacheHandler per request or invoke this method explicitly.
298
368
  */
299
369
  resetRequestCache() {
300
- this._tagCache.clear();
370
+ this._tagCache = /* @__PURE__ */ new Map();
371
+ }
372
+ /**
373
+ * Clean up a bad entry only when this read cannot be an explicitly cached
374
+ * older version of the shared KV value. KV has no conditional delete, so a
375
+ * cached read must remain a non-destructive miss.
376
+ */
377
+ _deleteEntryReadInBackground(kvKey) {
378
+ if (!this._entryReadOptions) this._deleteInBackground(kvKey);
301
379
  }
302
380
  /**
303
381
  * Fire a KV delete in the background.
@@ -347,6 +425,7 @@ function validateCacheEntry(raw) {
347
425
  if (!isUnknownRecord(obj.cacheControl)) return null;
348
426
  if (typeof obj.cacheControl.revalidate !== "number") return null;
349
427
  if (obj.cacheControl.expire !== void 0 && typeof obj.cacheControl.expire !== "number") return null;
428
+ if (obj.cacheControl.stale !== void 0 && typeof obj.cacheControl.stale !== "number") return null;
350
429
  }
351
430
  if (obj.value !== null) {
352
431
  if (!obj.value || typeof obj.value !== "object") return null;
@@ -447,7 +526,8 @@ const createKvDataCacheAdapter = ({ env, options }) => {
447
526
  return new KVCacheHandler(namespace, {
448
527
  appPrefix: options?.appPrefix,
449
528
  ttlSeconds: options?.ttlSeconds,
450
- tagCacheTtlMs: options?.tagCacheTtlMs
529
+ tagCacheTtlMs: options?.tagCacheTtlMs,
530
+ entryCacheTtlSeconds: options?.entryCacheTtlSeconds
451
531
  });
452
532
  };
453
533
  //#endregion
@@ -0,0 +1,15 @@
1
+ //#region src/cache/kv-key.d.ts
2
+ /** Key prefix for cache entries. */
3
+ export declare const ENTRY_PREFIX = "cache:";
4
+ export type KvKeySpace = {
5
+ /** Prefix shared by every cache entry, including entries with hashed logical keys. */
6
+ entryPrefix: string;
7
+ entryKey(logicalKey: string): string;
8
+ tagKey(tag: string): string;
9
+ };
10
+ /**
11
+ * Create the deterministic key namespace shared by runtime cache operations
12
+ * and deploy-time prerender population.
13
+ */
14
+ export declare function createKvKeySpace(appPrefix: string | undefined): KvKeySpace;
15
+ //#endregion
@@ -0,0 +1,43 @@
1
+ import { fnv1a64 } from "vinext/internal/utils/hash";
2
+ //#region src/cache/kv-key.ts
3
+ /** Key prefix for tag invalidation timestamps. */
4
+ const TAG_PREFIX = "__tag:";
5
+ /** Key prefix for cache entries. */
6
+ const ENTRY_PREFIX = "cache:";
7
+ /** Cloudflare KV's maximum UTF-8 encoded key length. */
8
+ const KV_KEY_MAX_BYTES = 512;
9
+ /** Marker for logical keys that must be hashed to fit in KV. */
10
+ const HASHED_KEY_PREFIX = "__hash:";
11
+ const KV_KEY_ENCODER = new TextEncoder();
12
+ function kvKeyByteLength(key) {
13
+ return KV_KEY_ENCODER.encode(key).length;
14
+ }
15
+ /**
16
+ * Keep short app prefixes readable, but bound the namespace portion so a
17
+ * hashed entry or tag key is always able to fit within Cloudflare KV's limit.
18
+ */
19
+ function normalizeAppPrefix(appPrefix) {
20
+ if (!appPrefix) return "";
21
+ const prefix = `${appPrefix}:`;
22
+ if (kvKeyByteLength(`${prefix}cache:${HASHED_KEY_PREFIX}${fnv1a64("")}`) <= KV_KEY_MAX_BYTES) return prefix;
23
+ return `__app:${fnv1a64(appPrefix)}:`;
24
+ }
25
+ function buildStorageKey(prefix, categoryPrefix, logicalKey) {
26
+ const key = `${prefix}${categoryPrefix}${logicalKey}`;
27
+ if (kvKeyByteLength(key) <= KV_KEY_MAX_BYTES) return key;
28
+ return `${prefix}${categoryPrefix}${HASHED_KEY_PREFIX}${fnv1a64(logicalKey)}`;
29
+ }
30
+ /**
31
+ * Create the deterministic key namespace shared by runtime cache operations
32
+ * and deploy-time prerender population.
33
+ */
34
+ function createKvKeySpace(appPrefix) {
35
+ const prefix = normalizeAppPrefix(appPrefix);
36
+ return {
37
+ entryPrefix: `${prefix}${ENTRY_PREFIX}`,
38
+ entryKey: (logicalKey) => buildStorageKey(prefix, ENTRY_PREFIX, logicalKey),
39
+ tagKey: (tag) => buildStorageKey(prefix, TAG_PREFIX, tag)
40
+ };
41
+ }
42
+ //#endregion
43
+ export { ENTRY_PREFIX, createKvKeySpace };
@@ -0,0 +1,73 @@
1
+ import { ResponseStoreLocationHint } from "@cloudflare/workers-response-store";
2
+ //#region src/cache/response-store-adapter.d.ts
3
+ export type ResponseStoreAdapterOptions = {
4
+ /**
5
+ * Best-effort location for metadata Durable Objects on first creation.
6
+ * Changing this does not move existing objects and should be treated as a
7
+ * cache-cold deployment change.
8
+ */
9
+ locationHint?: ResponseStoreLocationHint;
10
+ mode?: "self-contained" | "service-binding";
11
+ /** Split version-scoped metadata across this many Durable Objects. */
12
+ shards?: number;
13
+ };
14
+ /**
15
+ * Use Workers Response Store for both vinext response-stage and data caching.
16
+ * Service-binding mode keeps storage in a separate cache Worker. Self-contained
17
+ * mode keeps the same API and loopback in the application Worker.
18
+ */
19
+ export declare function responseStoreAdapter(options?: ResponseStoreAdapterOptions): {
20
+ cdn: {
21
+ options?: undefined;
22
+ adapter: string;
23
+ output: {
24
+ entry: string;
25
+ matchesBuild({ plugins }: {
26
+ plugins: readonly {
27
+ name?: string;
28
+ }[];
29
+ }): boolean;
30
+ transformHostEntry({ code, id }: {
31
+ code: string;
32
+ id: string;
33
+ }): string | null;
34
+ type: "multi-stage";
35
+ };
36
+ capabilities: {
37
+ buildIdentity: "response-header";
38
+ isResponsePolicyHeader: (name: string) => boolean;
39
+ requestRouting: "uncached-stage";
40
+ warmup: "response-store";
41
+ };
42
+ } | {
43
+ options: Pick<ResponseStoreAdapterOptions, "locationHint" | "shards">;
44
+ adapter: string;
45
+ output: {
46
+ entry: string;
47
+ matchesBuild({ plugins }: {
48
+ plugins: readonly {
49
+ name?: string;
50
+ }[];
51
+ }): boolean;
52
+ transformHostEntry({ code, id }: {
53
+ code: string;
54
+ id: string;
55
+ }): string | null;
56
+ type: "multi-stage";
57
+ };
58
+ capabilities: {
59
+ buildIdentity: "response-header";
60
+ isResponsePolicyHeader: (name: string) => boolean;
61
+ requestRouting: "uncached-stage";
62
+ warmup: "response-store";
63
+ };
64
+ };
65
+ data: {
66
+ options?: undefined;
67
+ adapter: string;
68
+ } | {
69
+ options: Pick<ResponseStoreAdapterOptions, "locationHint" | "shards">;
70
+ adapter: string;
71
+ };
72
+ };
73
+ //#endregion
@@ -0,0 +1,64 @@
1
+ import { fileURLToPath } from "node:url";
2
+ //#region src/cache/response-store-adapter.ts
3
+ const CLOUDFLARE_WORKER_ENTRY_ID = "virtual:cloudflare/worker-entry";
4
+ const RESPONSE_STORE_LOCATION_HINTS = {
5
+ afr: true,
6
+ apac: true,
7
+ "apac-ne": true,
8
+ "apac-se": true,
9
+ eeur: true,
10
+ enam: true,
11
+ me: true,
12
+ oc: true,
13
+ sam: true,
14
+ weur: true,
15
+ wnam: true
16
+ };
17
+ /**
18
+ * Use Workers Response Store for both vinext response-stage and data caching.
19
+ * Service-binding mode keeps storage in a separate cache Worker. Self-contained
20
+ * mode keeps the same API and loopback in the application Worker.
21
+ */
22
+ function responseStoreAdapter(options = {}) {
23
+ const locationHint = options.locationHint;
24
+ if (locationHint !== void 0 && (typeof locationHint !== "string" || !Object.hasOwn(RESPONSE_STORE_LOCATION_HINTS, locationHint))) throw new TypeError("Workers Response Store locationHint is not supported by Cloudflare");
25
+ const mode = options.mode ?? "service-binding";
26
+ if (mode !== "service-binding" && mode !== "self-contained") throw new Error(`Unknown Workers Response Store mode: ${String(mode)}`);
27
+ if (options.shards !== void 0 && (!Number.isSafeInteger(options.shards) || options.shards <= 1)) throw new TypeError("Workers Response Store shards must be an integer greater than 1");
28
+ const configuredOptions = {
29
+ ...locationHint === void 0 ? {} : { locationHint },
30
+ ...options.shards === void 0 ? {} : { shards: options.shards }
31
+ };
32
+ const runtimeOptions = Object.keys(configuredOptions).length === 0 ? {} : { options: configuredOptions };
33
+ const workerEntry = fileURLToPath(import.meta.resolve(mode === "self-contained" ? "./response-store-adapter.self-contained.worker.js" : "./response-store-adapter.service-binding.worker.js"));
34
+ const entrypoints = mode === "self-contained" ? "CacheMetadata, ResponseStoreBinding, ResponseStoreRevalidator" : "ResponseStoreClient, ResponseStoreRevalidator";
35
+ return {
36
+ cdn: {
37
+ adapter: fileURLToPath(import.meta.resolve("./response-store-cdn.runtime.js")),
38
+ ...runtimeOptions,
39
+ output: {
40
+ entry: workerEntry,
41
+ matchesBuild({ plugins }) {
42
+ return plugins.some(({ name }) => name === "vite-plugin-cloudflare" || name?.startsWith("vite-plugin-cloudflare:"));
43
+ },
44
+ transformHostEntry({ code, id }) {
45
+ if ((id.charCodeAt(0) === 0 ? id.slice(1) : id) !== CLOUDFLARE_WORKER_ENTRY_ID) return null;
46
+ return `${code}\nexport { ${entrypoints} } from ${JSON.stringify(workerEntry)};\n`;
47
+ },
48
+ type: "multi-stage"
49
+ },
50
+ capabilities: {
51
+ buildIdentity: "response-header",
52
+ isResponsePolicyHeader: (name) => name.toLowerCase() === "cdn-cache-control" || name.toLowerCase() === "cloudflare-cdn-cache-control",
53
+ requestRouting: "uncached-stage",
54
+ warmup: "response-store"
55
+ }
56
+ },
57
+ data: {
58
+ adapter: fileURLToPath(import.meta.resolve("./response-store-data.runtime.js")),
59
+ ...runtimeOptions
60
+ }
61
+ };
62
+ }
63
+ //#endregion
64
+ export { responseStoreAdapter };
@@ -0,0 +1,18 @@
1
+ import { VinextResponseStoreEnv } from "./response-store-adapter.worker.js";
2
+ import "vinext/internal/server/cloudflare-workers-tracing";
3
+ import { ResponseStoreRevalidatorEntrypoint, WorkersResponseStoreEnv } from "@cloudflare/workers-response-store";
4
+ import { CacheMetadata as CacheMetadata$1, ResponseStoreBinding as ResponseStoreBinding$1 } from "@cloudflare/workers-response-store/service";
5
+ //#region src/cache/response-store-adapter.self-contained.worker.d.ts
6
+ export declare const CacheMetadata: typeof CacheMetadata$1;
7
+ export declare const ResponseStoreBinding: typeof ResponseStoreBinding$1;
8
+ export declare const ResponseStoreRevalidator: ResponseStoreRevalidatorEntrypoint<WorkersResponseStoreEnv>;
9
+ declare const _default: {
10
+ fetch(request: Request, env: VinextResponseStoreEnv, ctx: {
11
+ exports?: Record<string, unknown>;
12
+ passThroughOnException(): void;
13
+ props?: unknown;
14
+ waitUntil(promise: Promise<unknown>): void;
15
+ }): Promise<Response>;
16
+ };
17
+ //#endregion
18
+ export { _default as default };
@@ -0,0 +1,12 @@
1
+ import { createVinextResponseStoreHandler, createVinextResponseStoreOptions } from "./response-store-adapter.worker.js";
2
+ import "vinext/internal/server/cloudflare-workers-tracing";
3
+ import { createWorkersResponseStore } from "@cloudflare/workers-response-store";
4
+ import { configuredCdnCacheAdapterOptions } from "virtual:vinext-cdn-cache-adapter";
5
+ //#region src/cache/response-store-adapter.self-contained.worker.ts
6
+ const responseStore = createWorkersResponseStore(createVinextResponseStoreOptions(configuredCdnCacheAdapterOptions));
7
+ const CacheMetadata = responseStore.entrypoints.CacheMetadata;
8
+ const ResponseStoreBinding = responseStore.entrypoints.ResponseStoreBinding;
9
+ const ResponseStoreRevalidator = responseStore.entrypoints.ResponseStoreRevalidator;
10
+ var response_store_adapter_self_contained_worker_default = createVinextResponseStoreHandler(responseStore);
11
+ //#endregion
12
+ export { CacheMetadata, ResponseStoreBinding, ResponseStoreRevalidator, response_store_adapter_self_contained_worker_default as default };
@@ -0,0 +1,16 @@
1
+ import { VinextResponseStoreEnv } from "./response-store-adapter.worker.js";
2
+ import "vinext/internal/server/cloudflare-workers-tracing";
3
+ import { ResponseStoreClientEntrypoint, ResponseStoreRevalidatorEntrypoint, WorkersResponseStoreClientEnv } from "@cloudflare/workers-response-store";
4
+ //#region src/cache/response-store-adapter.service-binding.worker.d.ts
5
+ export declare const ResponseStoreClient: ResponseStoreClientEntrypoint;
6
+ export declare const ResponseStoreRevalidator: ResponseStoreRevalidatorEntrypoint<WorkersResponseStoreClientEnv>;
7
+ declare const _default: {
8
+ fetch(request: Request, env: VinextResponseStoreEnv, ctx: {
9
+ exports?: Record<string, unknown>;
10
+ passThroughOnException(): void;
11
+ props?: unknown;
12
+ waitUntil(promise: Promise<unknown>): void;
13
+ }): Promise<Response>;
14
+ };
15
+ //#endregion
16
+ export { _default as default };
@@ -0,0 +1,11 @@
1
+ import { createVinextResponseStoreHandler, createVinextResponseStoreOptions } from "./response-store-adapter.worker.js";
2
+ import "vinext/internal/server/cloudflare-workers-tracing";
3
+ import { createWorkersResponseStoreClient } from "@cloudflare/workers-response-store";
4
+ import { configuredCdnCacheAdapterOptions } from "virtual:vinext-cdn-cache-adapter";
5
+ //#region src/cache/response-store-adapter.service-binding.worker.ts
6
+ const responseStore = createWorkersResponseStoreClient(createVinextResponseStoreOptions(configuredCdnCacheAdapterOptions));
7
+ const ResponseStoreClient = responseStore.entrypoints.ResponseStoreClient;
8
+ const ResponseStoreRevalidator = responseStore.entrypoints.ResponseStoreRevalidator;
9
+ var response_store_adapter_service_binding_worker_default = createVinextResponseStoreHandler(responseStore);
10
+ //#endregion
11
+ export { ResponseStoreClient, ResponseStoreRevalidator, response_store_adapter_service_binding_worker_default as default };
@@ -0,0 +1,14 @@
1
+ import { WorkersResponseStore, WorkersResponseStoreClientEnv, WorkersResponseStoreEnv, WorkersResponseStoreOptions } from "@cloudflare/workers-response-store";
2
+ //#region src/cache/response-store-adapter.worker.d.ts
3
+ type WorkerExecutionContext = {
4
+ exports?: Record<string, unknown>;
5
+ passThroughOnException(): void;
6
+ props?: unknown;
7
+ waitUntil(promise: Promise<unknown>): void;
8
+ };
9
+ export type VinextResponseStoreEnv = WorkersResponseStoreClientEnv | WorkersResponseStoreEnv;
10
+ export declare function createVinextResponseStoreOptions<Env extends VinextResponseStoreEnv>(configuration?: Record<string, unknown>): WorkersResponseStoreOptions<Env>;
11
+ export declare function createVinextResponseStoreHandler(store: WorkersResponseStore): {
12
+ fetch(request: Request, env: VinextResponseStoreEnv, ctx: WorkerExecutionContext): Promise<Response>;
13
+ };
14
+ //#endregion