From 28279cb40a80606404f7733b25f9ef07389bd45b Mon Sep 17 00:00:00 2001 From: James Date: Sun, 9 Feb 2025 18:37:33 +0000 Subject: [PATCH 01/11] feat: basic in-memory de-duping revalidation queue --- .changeset/witty-baboons-smile.md | 5 + examples/e2e/app-router/open-next.config.ts | 3 +- packages/cloudflare/src/api/kv-cache.ts | 3 + packages/cloudflare/src/api/memory-queue.ts | 52 +++++++ .../src/cli/build/utils/ensure-cf-config.ts | 6 +- packages/cloudflare/tsconfig.json | 2 +- pnpm-lock.yaml | 145 ++++++++---------- 7 files changed, 127 insertions(+), 89 deletions(-) create mode 100644 .changeset/witty-baboons-smile.md create mode 100644 packages/cloudflare/src/api/memory-queue.ts diff --git a/.changeset/witty-baboons-smile.md b/.changeset/witty-baboons-smile.md new file mode 100644 index 00000000..afe26013 --- /dev/null +++ b/.changeset/witty-baboons-smile.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/cloudflare": minor +--- + +feat: basic in-memory de-duping revalidation queue diff --git a/examples/e2e/app-router/open-next.config.ts b/examples/e2e/app-router/open-next.config.ts index ef2e20a9..4f8b67cb 100644 --- a/examples/e2e/app-router/open-next.config.ts +++ b/examples/e2e/app-router/open-next.config.ts @@ -1,5 +1,6 @@ import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; import cache from "@opennextjs/cloudflare/kv-cache"; +import memoryQueue from "@opennextjs/cloudflare/memory-queue"; const config: OpenNextConfig = { default: { @@ -7,7 +8,7 @@ const config: OpenNextConfig = { wrapper: "cloudflare-node", converter: "edge", incrementalCache: async () => cache, - queue: "direct", + queue: () => memoryQueue, // Unused implementation tagCache: "dummy", }, diff --git a/packages/cloudflare/src/api/kv-cache.ts b/packages/cloudflare/src/api/kv-cache.ts index 4064fc4e..228dbac1 100644 --- a/packages/cloudflare/src/api/kv-cache.ts +++ b/packages/cloudflare/src/api/kv-cache.ts @@ -2,6 +2,7 @@ import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js"; import { getCloudflareContext } from "./cloudflare-context.js"; +import memoryQueue from "./memory-queue.js"; export const CACHE_ASSET_DIR = "cdn-cgi/_next_cache"; @@ -116,6 +117,8 @@ class Cache implements IncrementalCache { ); } catch { throw new RecoverableError(`Failed to set cache [${key}]`); + } finally { + memoryQueue.remove(key); } } diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/memory-queue.ts new file mode 100644 index 00000000..31b2677c --- /dev/null +++ b/packages/cloudflare/src/api/memory-queue.ts @@ -0,0 +1,52 @@ +import { generateMessageGroupId } from "@opennextjs/aws/core/routing/queue.js"; +import logger from "@opennextjs/aws/logger.js"; +import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js"; + +/** + * The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route. + * + * It offers basic support for in-memory de-duping per isolate. + */ +class MemoryQueue implements Queue { + readonly name = "memory-queue"; + + public revalidatedPaths = new Map>(); + + public async send({ MessageBody: { host, url }, MessageGroupId }: QueueMessage): Promise { + this.revalidatedPaths.set( + MessageGroupId, + // force remove to allow new revalidations incase something went wrong + setTimeout(() => this.removeId(MessageGroupId), 10_000) + ); + + try { + // @ts-expect-error + const manifest = await import("./.next/prerender-manifest.json"); + const protocol = host.includes("localhost") ? "http" : "https"; + + await globalThis.internalFetch(`${protocol}://${host}${url}`, { + method: "HEAD", + headers: { + "x-prerender-revalidate": manifest.preview.previewModeId, + "x-isr": "1", + }, + }); + } catch (e) { + logger.error(e); + this.revalidatedPaths.delete(MessageGroupId); + } + } + + private removeId(id: string) { + clearTimeout(this.revalidatedPaths.get(id)); + this.revalidatedPaths.delete(id); + } + + public remove(path: string) { + if (this.revalidatedPaths.size > 0) { + this.removeId(generateMessageGroupId(path)); + } + } +} + +export default new MemoryQueue(); diff --git a/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts index 84b7668e..14f2d1ec 100644 --- a/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts +++ b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts @@ -15,7 +15,9 @@ export function ensureCloudflareConfig(config: OpenNextConfig) { typeof config.default?.override?.incrementalCache === "function", dftUseDummyTagCache: config.default?.override?.tagCache === "dummy", dftMaybeUseQueue: - config.default?.override?.queue === "dummy" || config.default?.override?.queue === "direct", + config.default?.override?.queue === "dummy" || + config.default?.override?.queue === "direct" || + typeof config.default?.override?.incrementalCache === "function", disableCacheInterception: config.dangerous?.enableCacheInterception !== true, mwIsMiddlewareExternal: config.middleware?.external == true, mwUseCloudflareWrapper: config.middleware?.override?.wrapper === "cloudflare-edge", @@ -37,7 +39,7 @@ export function ensureCloudflareConfig(config: OpenNextConfig) { converter: "edge", incrementalCache: "dummy" | function, tagCache: "dummy", - queue: "dummy" | "direct", + queue: "dummy" | "direct" | function, }, }, diff --git a/packages/cloudflare/tsconfig.json b/packages/cloudflare/tsconfig.json index 7dc2dcd4..b98cd59c 100644 --- a/packages/cloudflare/tsconfig.json +++ b/packages/cloudflare/tsconfig.json @@ -13,7 +13,7 @@ "noPropertyAccessFromIndexSignature": false, "outDir": "./dist", "target": "ES2022", - "types": ["@cloudflare/workers-types"] + "types": ["@cloudflare/workers-types", "@opennextjs/aws/types/global.d.ts"] }, "include": ["src/**/*.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 726bdd06..47138325 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5643,10 +5643,6 @@ packages: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -7636,10 +7632,6 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} - engines: {node: '>= 0.4'} - object-inspect@1.13.3: resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} engines: {node: '>= 0.4'} @@ -8464,10 +8456,6 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - side-channel@1.1.0: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} @@ -9460,13 +9448,13 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.723.0 + '@aws-sdk/types': 3.734.0 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.723.0 + '@aws-sdk/types': 3.734.0 tslib: 2.8.1 '@aws-crypto/ie11-detection@3.0.0': @@ -9488,7 +9476,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-crypto/supports-web-crypto': 3.0.0 '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.723.0 + '@aws-sdk/types': 3.734.0 '@aws-sdk/util-locate-window': 3.693.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 @@ -9506,7 +9494,7 @@ snapshots: '@aws-crypto/sha256-js@3.0.0': dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.723.0 + '@aws-sdk/types': 3.734.0 tslib: 1.14.1 '@aws-crypto/sha256-js@5.2.0': @@ -9525,13 +9513,13 @@ snapshots: '@aws-crypto/util@3.0.0': dependencies: - '@aws-sdk/types': 3.723.0 + '@aws-sdk/types': 3.734.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.723.0 + '@aws-sdk/types': 3.734.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -9978,19 +9966,19 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.723.0 '@aws-sdk/util-user-agent-node': 3.726.0 '@smithy/config-resolver': 4.0.1 - '@smithy/core': 3.1.0 + '@smithy/core': 3.1.2 '@smithy/fetch-http-handler': 5.0.1 '@smithy/hash-node': 4.0.1 '@smithy/invalid-dependency': 4.0.1 '@smithy/middleware-content-length': 4.0.1 - '@smithy/middleware-endpoint': 4.0.1 + '@smithy/middleware-endpoint': 4.0.3 '@smithy/middleware-retry': 4.0.1 - '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-serde': 4.0.2 '@smithy/middleware-stack': 4.0.1 '@smithy/node-config-provider': 4.0.1 - '@smithy/node-http-handler': 4.0.1 + '@smithy/node-http-handler': 4.0.2 '@smithy/protocol-http': 5.0.1 - '@smithy/smithy-client': 4.1.0 + '@smithy/smithy-client': 4.1.3 '@smithy/types': 4.1.0 '@smithy/url-parser': 4.0.1 '@smithy/util-base64': 4.0.0 @@ -10221,12 +10209,12 @@ snapshots: '@aws-sdk/core': 3.723.0 '@aws-sdk/types': 3.723.0 '@smithy/fetch-http-handler': 5.0.1 - '@smithy/node-http-handler': 4.0.1 + '@smithy/node-http-handler': 4.0.2 '@smithy/property-provider': 4.0.1 '@smithy/protocol-http': 5.0.1 - '@smithy/smithy-client': 4.1.0 + '@smithy/smithy-client': 4.1.3 '@smithy/types': 4.1.0 - '@smithy/util-stream': 4.0.1 + '@smithy/util-stream': 4.0.2 tslib: 2.8.1 '@aws-sdk/credential-provider-ini@3.398.0': @@ -14616,7 +14604,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 @@ -14797,17 +14785,17 @@ snapshots: call-bind@1.0.7: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 set-function-length: 1.2.2 call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.1 - es-define-property: 1.0.0 - get-intrinsic: 1.2.4 + es-define-property: 1.0.1 + get-intrinsic: 1.2.7 set-function-length: 1.2.2 call-bound@1.0.3: @@ -15088,7 +15076,7 @@ snapshots: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 es-get-iterator: 1.1.3 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 is-arguments: 1.1.1 is-array-buffer: 3.0.4 is-date-object: 1.0.5 @@ -15099,7 +15087,7 @@ snapshots: object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 - side-channel: 1.0.6 + side-channel: 1.1.0 which-boxed-primitive: 1.0.2 which-collection: 1.0.2 which-typed-array: 1.1.15 @@ -15110,9 +15098,9 @@ snapshots: define-data-property@1.1.4: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 - gopd: 1.0.1 + gopd: 1.2.0 define-properties@1.2.1: dependencies: @@ -15172,7 +15160,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 dotenv@16.4.7: {} @@ -15281,19 +15269,19 @@ snapshots: data-view-buffer: 1.0.1 data-view-byte-length: 1.0.1 data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.0.0 es-set-tostringtag: 2.0.3 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 get-symbol-description: 1.0.2 globalthis: 1.0.4 - gopd: 1.0.1 + gopd: 1.2.0 has-property-descriptors: 1.0.2 has-proto: 1.0.3 - has-symbols: 1.0.3 + has-symbols: 1.1.0 hasown: 2.0.2 internal-slot: 1.0.7 is-array-buffer: 3.0.4 @@ -15305,7 +15293,7 @@ snapshots: is-string: 1.0.7 is-typed-array: 1.1.13 is-weakref: 1.0.2 - object-inspect: 1.13.2 + object-inspect: 1.13.3 object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 @@ -15375,10 +15363,6 @@ snapshots: unbox-primitive: 1.1.0 which-typed-array: 1.1.18 - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.2.4 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -15386,8 +15370,8 @@ snapshots: es-get-iterator@1.1.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 + get-intrinsic: 1.2.7 + has-symbols: 1.1.0 is-arguments: 1.1.1 is-map: 2.0.3 is-set: 2.0.3 @@ -15439,7 +15423,7 @@ snapshots: es-set-tostringtag@2.0.3: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -16813,7 +16797,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 has-proto: 1.0.3 - has-symbols: 1.0.3 + has-symbols: 1.1.0 hasown: 2.0.2 get-intrinsic@1.2.7: @@ -16849,7 +16833,7 @@ snapshots: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 get-symbol-description@1.1.0: dependencies: @@ -16926,7 +16910,7 @@ snapshots: globalthis@1.0.4: dependencies: define-properties: 1.2.1 - gopd: 1.0.1 + gopd: 1.2.0 globby@11.1.0: dependencies: @@ -16970,7 +16954,7 @@ snapshots: gopd@1.0.1: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 gopd@1.2.0: {} @@ -17005,7 +16989,7 @@ snapshots: has-property-descriptors@1.0.2: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 has-proto@1.0.3: {} @@ -17019,7 +17003,7 @@ snapshots: has-tostringtag@1.0.2: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 hasown@2.0.2: dependencies: @@ -17151,7 +17135,7 @@ snapshots: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.0.6 + side-channel: 1.1.0 internal-slot@1.1.0: dependencies: @@ -17169,7 +17153,7 @@ snapshots: is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 is-array-buffer@3.0.5: dependencies: @@ -17324,7 +17308,7 @@ snapshots: is-symbol@1.0.4: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 is-symbol@1.1.1: dependencies: @@ -17357,7 +17341,7 @@ snapshots: is-weakset@2.0.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 is-windows@1.0.2: {} @@ -17374,8 +17358,8 @@ snapshots: iterator.prototype@1.1.2: dependencies: define-properties: 1.2.1 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 + get-intrinsic: 1.2.7 + has-symbols: 1.1.0 reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 @@ -17606,7 +17590,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 lru-cache@10.4.3: {} @@ -18223,7 +18207,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.3 + tslib: 2.8.1 node-abi@3.73.0: dependencies: @@ -18282,8 +18266,6 @@ snapshots: object-hash@3.0.0: {} - object-inspect@1.13.2: {} - object-inspect@1.13.3: {} object-is@1.1.6: @@ -18299,7 +18281,7 @@ snapshots: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - has-symbols: 1.0.3 + has-symbols: 1.1.0 object-keys: 1.1.1 object.assign@4.1.7: @@ -18766,7 +18748,7 @@ snapshots: qs@6.13.0: dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 query-registry@3.0.1: dependencies: @@ -18904,7 +18886,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 globalthis: 1.0.4 which-builtin-type: 1.1.4 @@ -19072,8 +19054,8 @@ snapshots: safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 + get-intrinsic: 1.2.7 + has-symbols: 1.1.0 isarray: 2.0.5 safe-array-concat@1.1.3: @@ -19163,8 +19145,8 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 - gopd: 1.0.1 + get-intrinsic: 1.2.7 + gopd: 1.2.0 has-property-descriptors: 1.0.2 set-function-name@2.0.2: @@ -19242,13 +19224,6 @@ snapshots: object-inspect: 1.13.3 side-channel-map: 1.0.1 - side-channel@1.0.6: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.2 - side-channel@1.1.0: dependencies: es-errors: 1.3.0 @@ -19282,7 +19257,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 snakecase-keys@5.4.4: dependencies: @@ -19419,7 +19394,7 @@ snapshots: internal-slot: 1.0.7 regexp.prototype.flags: 1.5.2 set-function-name: 2.0.2 - side-channel: 1.0.6 + side-channel: 1.1.0 string.prototype.matchall@4.0.12: dependencies: @@ -19993,7 +19968,7 @@ snapshots: dependencies: call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-proto: 1.0.3 is-typed-array: 1.1.13 @@ -20010,7 +19985,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-proto: 1.0.3 is-typed-array: 1.1.13 @@ -20028,7 +20003,7 @@ snapshots: dependencies: call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-proto: 1.0.3 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 @@ -20067,7 +20042,7 @@ snapshots: dependencies: call-bind: 1.0.7 has-bigints: 1.0.2 - has-symbols: 1.0.3 + has-symbols: 1.1.0 which-boxed-primitive: 1.0.2 unbox-primitive@1.1.0: @@ -20352,7 +20327,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-tostringtag: 1.0.2 which-typed-array@1.1.18: From 73599ad51b2e7b3e6c8540fc632175ddcae11282 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 10 Feb 2025 07:33:30 +0000 Subject: [PATCH 02/11] change to pr for package --- packages/cloudflare/src/api/memory-queue.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/memory-queue.ts index 31b2677c..8e61627e 100644 --- a/packages/cloudflare/src/api/memory-queue.ts +++ b/packages/cloudflare/src/api/memory-queue.ts @@ -20,6 +20,7 @@ class MemoryQueue implements Queue { ); try { + // TODO: Drop the import - https://github.com/opennextjs/opennextjs-cloudflare/issues/361 // @ts-expect-error const manifest = await import("./.next/prerender-manifest.json"); const protocol = host.includes("localhost") ? "http" : "https"; From ea2c7a6fdeb73656eecc8c01b351cacfa7622aed Mon Sep 17 00:00:00 2001 From: James Date: Mon, 10 Feb 2025 07:49:40 +0000 Subject: [PATCH 03/11] add tests and fix mistake --- .../cloudflare/src/api/memory-queue.spec.ts | 43 +++++++++++++++++++ packages/cloudflare/src/api/memory-queue.ts | 2 + 2 files changed, 45 insertions(+) create mode 100644 packages/cloudflare/src/api/memory-queue.spec.ts diff --git a/packages/cloudflare/src/api/memory-queue.spec.ts b/packages/cloudflare/src/api/memory-queue.spec.ts new file mode 100644 index 00000000..ccd277db --- /dev/null +++ b/packages/cloudflare/src/api/memory-queue.spec.ts @@ -0,0 +1,43 @@ +import { generateMessageGroupId } from "@opennextjs/aws/core/routing/queue.js"; +import { beforeAll, describe, expect, it, vi } from "vitest"; + +import cache from "./memory-queue"; + +vi.mock("./.next/prerender-manifest.json", () => ({ + preview: { previewModeId: "id" }, +})); + +const defaultOpts = { + MessageBody: { host: "test.local", url: "/test" }, + MessageGroupId: generateMessageGroupId("/test"), + MessageDeduplicationId: "", +}; + +describe("MemoryQueue", () => { + beforeAll(() => { + vi.useFakeTimers(); + globalThis.internalFetch = vi.fn(); + }); + + it("should de-dupe revalidations", async () => { + await cache.send(defaultOpts); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); + await cache.send(defaultOpts); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); + + cache.remove("/test"); + + await cache.send(defaultOpts); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); + await cache.send(defaultOpts); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); + + vi.advanceTimersByTime(10_000); + + await cache.send(defaultOpts); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(3); + + await cache.send({ ...defaultOpts, MessageGroupId: generateMessageGroupId("/other") }); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(4); + }); +}); diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/memory-queue.ts index 8e61627e..c64c4d2f 100644 --- a/packages/cloudflare/src/api/memory-queue.ts +++ b/packages/cloudflare/src/api/memory-queue.ts @@ -13,6 +13,8 @@ class MemoryQueue implements Queue { public revalidatedPaths = new Map>(); public async send({ MessageBody: { host, url }, MessageGroupId }: QueueMessage): Promise { + if (this.revalidatedPaths.has(MessageGroupId)) return; + this.revalidatedPaths.set( MessageGroupId, // force remove to allow new revalidations incase something went wrong From 93673968e4f078900e9d720154c7fc8f7759f97a Mon Sep 17 00:00:00 2001 From: James Date: Mon, 10 Feb 2025 08:16:53 +0000 Subject: [PATCH 04/11] remove dependency on memory queue from kv cache --- packages/cloudflare/src/api/kv-cache.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/cloudflare/src/api/kv-cache.ts b/packages/cloudflare/src/api/kv-cache.ts index 228dbac1..4064fc4e 100644 --- a/packages/cloudflare/src/api/kv-cache.ts +++ b/packages/cloudflare/src/api/kv-cache.ts @@ -2,7 +2,6 @@ import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js"; import { getCloudflareContext } from "./cloudflare-context.js"; -import memoryQueue from "./memory-queue.js"; export const CACHE_ASSET_DIR = "cdn-cgi/_next_cache"; @@ -117,8 +116,6 @@ class Cache implements IncrementalCache { ); } catch { throw new RecoverableError(`Failed to set cache [${key}]`); - } finally { - memoryQueue.remove(key); } } From 670ce55dc2980ce12ba5fb446b8aec7f1eaefba4 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 10 Feb 2025 08:47:29 +0000 Subject: [PATCH 05/11] Revert "remove dependency on memory queue from kv cache" This reverts commit 42739aef99b7cefd2ee3db62a7e0992a8b1645ca. --- packages/cloudflare/src/api/kv-cache.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cloudflare/src/api/kv-cache.ts b/packages/cloudflare/src/api/kv-cache.ts index 4064fc4e..228dbac1 100644 --- a/packages/cloudflare/src/api/kv-cache.ts +++ b/packages/cloudflare/src/api/kv-cache.ts @@ -2,6 +2,7 @@ import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js"; import { getCloudflareContext } from "./cloudflare-context.js"; +import memoryQueue from "./memory-queue.js"; export const CACHE_ASSET_DIR = "cdn-cgi/_next_cache"; @@ -116,6 +117,8 @@ class Cache implements IncrementalCache { ); } catch { throw new RecoverableError(`Failed to set cache [${key}]`); + } finally { + memoryQueue.remove(key); } } From 224d42c547003b6c2329b8b2aa26568bffc4223f Mon Sep 17 00:00:00 2001 From: James Date: Mon, 10 Feb 2025 08:49:57 +0000 Subject: [PATCH 06/11] remove dependency on memory queue from kv cache again --- packages/cloudflare/src/api/kv-cache.ts | 3 -- .../cloudflare/src/api/memory-queue.spec.ts | 28 +++++++++---------- packages/cloudflare/src/api/memory-queue.ts | 16 ++--------- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/packages/cloudflare/src/api/kv-cache.ts b/packages/cloudflare/src/api/kv-cache.ts index 228dbac1..4064fc4e 100644 --- a/packages/cloudflare/src/api/kv-cache.ts +++ b/packages/cloudflare/src/api/kv-cache.ts @@ -2,7 +2,6 @@ import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js"; import { getCloudflareContext } from "./cloudflare-context.js"; -import memoryQueue from "./memory-queue.js"; export const CACHE_ASSET_DIR = "cdn-cgi/_next_cache"; @@ -117,8 +116,6 @@ class Cache implements IncrementalCache { ); } catch { throw new RecoverableError(`Failed to set cache [${key}]`); - } finally { - memoryQueue.remove(key); } } diff --git a/packages/cloudflare/src/api/memory-queue.spec.ts b/packages/cloudflare/src/api/memory-queue.spec.ts index ccd277db..bfe3a5c0 100644 --- a/packages/cloudflare/src/api/memory-queue.spec.ts +++ b/packages/cloudflare/src/api/memory-queue.spec.ts @@ -16,28 +16,26 @@ const defaultOpts = { describe("MemoryQueue", () => { beforeAll(() => { vi.useFakeTimers(); - globalThis.internalFetch = vi.fn(); + globalThis.internalFetch = vi.fn().mockReturnValue(new Promise((res) => setTimeout(() => res(true), 1))); }); it("should de-dupe revalidations", async () => { - await cache.send(defaultOpts); + const firstBatch = [cache.send(defaultOpts), cache.send(defaultOpts)]; + vi.advanceTimersByTime(1); + await Promise.all(firstBatch); expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); - await cache.send(defaultOpts); - expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); - - cache.remove("/test"); - - await cache.send(defaultOpts); - expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); - await cache.send(defaultOpts); - expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); + const secondBatch = [cache.send(defaultOpts)]; vi.advanceTimersByTime(10_000); + await Promise.all(secondBatch); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); - await cache.send(defaultOpts); - expect(globalThis.internalFetch).toHaveBeenCalledTimes(3); - - await cache.send({ ...defaultOpts, MessageGroupId: generateMessageGroupId("/other") }); + const thirdBatch = [ + cache.send(defaultOpts), + cache.send({ ...defaultOpts, MessageGroupId: generateMessageGroupId("/other") }), + ]; + vi.advanceTimersByTime(1); + await Promise.all(thirdBatch); expect(globalThis.internalFetch).toHaveBeenCalledTimes(4); }); }); diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/memory-queue.ts index c64c4d2f..691a1c3d 100644 --- a/packages/cloudflare/src/api/memory-queue.ts +++ b/packages/cloudflare/src/api/memory-queue.ts @@ -1,4 +1,3 @@ -import { generateMessageGroupId } from "@opennextjs/aws/core/routing/queue.js"; import logger from "@opennextjs/aws/logger.js"; import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js"; @@ -18,7 +17,7 @@ class MemoryQueue implements Queue { this.revalidatedPaths.set( MessageGroupId, // force remove to allow new revalidations incase something went wrong - setTimeout(() => this.removeId(MessageGroupId), 10_000) + setTimeout(() => this.revalidatedPaths.delete(MessageGroupId), 10_000) ); try { @@ -36,20 +35,11 @@ class MemoryQueue implements Queue { }); } catch (e) { logger.error(e); + } finally { + clearTimeout(this.revalidatedPaths.get(MessageGroupId)); this.revalidatedPaths.delete(MessageGroupId); } } - - private removeId(id: string) { - clearTimeout(this.revalidatedPaths.get(id)); - this.revalidatedPaths.delete(id); - } - - public remove(path: string) { - if (this.revalidatedPaths.size > 0) { - this.removeId(generateMessageGroupId(path)); - } - } } export default new MemoryQueue(); From cb23b5789a0b315c0788db5911e0cf008c1da069 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 10 Feb 2025 09:00:46 +0000 Subject: [PATCH 07/11] move manifest retrievel to own util for mocking --- packages/cloudflare/src/api/internal/manifest.ts | 5 +++++ packages/cloudflare/src/api/memory-queue.spec.ts | 4 ++-- packages/cloudflare/src/api/memory-queue.ts | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 packages/cloudflare/src/api/internal/manifest.ts diff --git a/packages/cloudflare/src/api/internal/manifest.ts b/packages/cloudflare/src/api/internal/manifest.ts new file mode 100644 index 00000000..26ae4859 --- /dev/null +++ b/packages/cloudflare/src/api/internal/manifest.ts @@ -0,0 +1,5 @@ +export function getPrerenderManifest() { + // TODO: Drop the import - https://github.com/opennextjs/opennextjs-cloudflare/issues/361 + // @ts-expect-error + return import("./.next/prerender-manifest.json"); +} diff --git a/packages/cloudflare/src/api/memory-queue.spec.ts b/packages/cloudflare/src/api/memory-queue.spec.ts index bfe3a5c0..abbc2f47 100644 --- a/packages/cloudflare/src/api/memory-queue.spec.ts +++ b/packages/cloudflare/src/api/memory-queue.spec.ts @@ -3,8 +3,8 @@ import { beforeAll, describe, expect, it, vi } from "vitest"; import cache from "./memory-queue"; -vi.mock("./.next/prerender-manifest.json", () => ({ - preview: { previewModeId: "id" }, +vi.mock("./internal/manifest.js", () => ({ + getPrerenderManifest: () => ({ preview: { previewModeId: "id" } }), })); const defaultOpts = { diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/memory-queue.ts index 691a1c3d..c29d6b5c 100644 --- a/packages/cloudflare/src/api/memory-queue.ts +++ b/packages/cloudflare/src/api/memory-queue.ts @@ -1,6 +1,8 @@ import logger from "@opennextjs/aws/logger.js"; import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js"; +import { getPrerenderManifest } from "./internal/manifest.js"; + /** * The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route. * @@ -21,9 +23,7 @@ class MemoryQueue implements Queue { ); try { - // TODO: Drop the import - https://github.com/opennextjs/opennextjs-cloudflare/issues/361 - // @ts-expect-error - const manifest = await import("./.next/prerender-manifest.json"); + const manifest = await getPrerenderManifest(); const protocol = host.includes("localhost") ? "http" : "https"; await globalThis.internalFetch(`${protocol}://${host}${url}`, { From 62561502db07070bd7e23b3c1ce91ed3d9db4047 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Feb 2025 07:31:45 +0000 Subject: [PATCH 08/11] get test to be reliable --- .../cloudflare/src/api/internal/manifest.ts | 5 - .../cloudflare/src/api/memory-queue.spec.ts | 11 +- packages/cloudflare/src/api/memory-queue.ts | 6 +- pnpm-lock.yaml | 145 ++++++++++-------- 4 files changed, 91 insertions(+), 76 deletions(-) delete mode 100644 packages/cloudflare/src/api/internal/manifest.ts diff --git a/packages/cloudflare/src/api/internal/manifest.ts b/packages/cloudflare/src/api/internal/manifest.ts deleted file mode 100644 index 26ae4859..00000000 --- a/packages/cloudflare/src/api/internal/manifest.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function getPrerenderManifest() { - // TODO: Drop the import - https://github.com/opennextjs/opennextjs-cloudflare/issues/361 - // @ts-expect-error - return import("./.next/prerender-manifest.json"); -} diff --git a/packages/cloudflare/src/api/memory-queue.spec.ts b/packages/cloudflare/src/api/memory-queue.spec.ts index abbc2f47..75b654d1 100644 --- a/packages/cloudflare/src/api/memory-queue.spec.ts +++ b/packages/cloudflare/src/api/memory-queue.spec.ts @@ -3,9 +3,7 @@ import { beforeAll, describe, expect, it, vi } from "vitest"; import cache from "./memory-queue"; -vi.mock("./internal/manifest.js", () => ({ - getPrerenderManifest: () => ({ preview: { previewModeId: "id" } }), -})); +vi.mock("./.next/prerender-manifest.json", () => Promise.resolve({ preview: { previewModeId: "id" } })); const defaultOpts = { MessageBody: { host: "test.local", url: "/test" }, @@ -30,12 +28,9 @@ describe("MemoryQueue", () => { await Promise.all(secondBatch); expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); - const thirdBatch = [ - cache.send(defaultOpts), - cache.send({ ...defaultOpts, MessageGroupId: generateMessageGroupId("/other") }), - ]; + const thirdBatch = [cache.send({ ...defaultOpts, MessageGroupId: generateMessageGroupId("/other") })]; vi.advanceTimersByTime(1); await Promise.all(thirdBatch); - expect(globalThis.internalFetch).toHaveBeenCalledTimes(4); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(3); }); }); diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/memory-queue.ts index c29d6b5c..e44fc1df 100644 --- a/packages/cloudflare/src/api/memory-queue.ts +++ b/packages/cloudflare/src/api/memory-queue.ts @@ -1,8 +1,6 @@ import logger from "@opennextjs/aws/logger.js"; import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js"; -import { getPrerenderManifest } from "./internal/manifest.js"; - /** * The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route. * @@ -23,9 +21,11 @@ class MemoryQueue implements Queue { ); try { - const manifest = await getPrerenderManifest(); const protocol = host.includes("localhost") ? "http" : "https"; + // TODO: Drop the import - https://github.com/opennextjs/opennextjs-cloudflare/issues/361 + // @ts-ignore + const manifest = await import("./.next/prerender-manifest.json"); await globalThis.internalFetch(`${protocol}://${host}${url}`, { method: "HEAD", headers: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47138325..726bdd06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5643,6 +5643,10 @@ packages: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -7632,6 +7636,10 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + object-inspect@1.13.3: resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} engines: {node: '>= 0.4'} @@ -8456,6 +8464,10 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + side-channel@1.1.0: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} @@ -9448,13 +9460,13 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.734.0 + '@aws-sdk/types': 3.723.0 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.734.0 + '@aws-sdk/types': 3.723.0 tslib: 2.8.1 '@aws-crypto/ie11-detection@3.0.0': @@ -9476,7 +9488,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-crypto/supports-web-crypto': 3.0.0 '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.734.0 + '@aws-sdk/types': 3.723.0 '@aws-sdk/util-locate-window': 3.693.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 @@ -9494,7 +9506,7 @@ snapshots: '@aws-crypto/sha256-js@3.0.0': dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.734.0 + '@aws-sdk/types': 3.723.0 tslib: 1.14.1 '@aws-crypto/sha256-js@5.2.0': @@ -9513,13 +9525,13 @@ snapshots: '@aws-crypto/util@3.0.0': dependencies: - '@aws-sdk/types': 3.734.0 + '@aws-sdk/types': 3.723.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.734.0 + '@aws-sdk/types': 3.723.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -9966,19 +9978,19 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.723.0 '@aws-sdk/util-user-agent-node': 3.726.0 '@smithy/config-resolver': 4.0.1 - '@smithy/core': 3.1.2 + '@smithy/core': 3.1.0 '@smithy/fetch-http-handler': 5.0.1 '@smithy/hash-node': 4.0.1 '@smithy/invalid-dependency': 4.0.1 '@smithy/middleware-content-length': 4.0.1 - '@smithy/middleware-endpoint': 4.0.3 + '@smithy/middleware-endpoint': 4.0.1 '@smithy/middleware-retry': 4.0.1 - '@smithy/middleware-serde': 4.0.2 + '@smithy/middleware-serde': 4.0.1 '@smithy/middleware-stack': 4.0.1 '@smithy/node-config-provider': 4.0.1 - '@smithy/node-http-handler': 4.0.2 + '@smithy/node-http-handler': 4.0.1 '@smithy/protocol-http': 5.0.1 - '@smithy/smithy-client': 4.1.3 + '@smithy/smithy-client': 4.1.0 '@smithy/types': 4.1.0 '@smithy/url-parser': 4.0.1 '@smithy/util-base64': 4.0.0 @@ -10209,12 +10221,12 @@ snapshots: '@aws-sdk/core': 3.723.0 '@aws-sdk/types': 3.723.0 '@smithy/fetch-http-handler': 5.0.1 - '@smithy/node-http-handler': 4.0.2 + '@smithy/node-http-handler': 4.0.1 '@smithy/property-provider': 4.0.1 '@smithy/protocol-http': 5.0.1 - '@smithy/smithy-client': 4.1.3 + '@smithy/smithy-client': 4.1.0 '@smithy/types': 4.1.0 - '@smithy/util-stream': 4.0.2 + '@smithy/util-stream': 4.0.1 tslib: 2.8.1 '@aws-sdk/credential-provider-ini@3.398.0': @@ -14604,7 +14616,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 @@ -14785,17 +14797,17 @@ snapshots: call-bind@1.0.7: dependencies: - es-define-property: 1.0.1 + es-define-property: 1.0.0 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 set-function-length: 1.2.2 call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.1 - es-define-property: 1.0.1 - get-intrinsic: 1.2.7 + es-define-property: 1.0.0 + get-intrinsic: 1.2.4 set-function-length: 1.2.2 call-bound@1.0.3: @@ -15076,7 +15088,7 @@ snapshots: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 es-get-iterator: 1.1.3 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 is-arguments: 1.1.1 is-array-buffer: 3.0.4 is-date-object: 1.0.5 @@ -15087,7 +15099,7 @@ snapshots: object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 - side-channel: 1.1.0 + side-channel: 1.0.6 which-boxed-primitive: 1.0.2 which-collection: 1.0.2 which-typed-array: 1.1.15 @@ -15098,9 +15110,9 @@ snapshots: define-data-property@1.1.4: dependencies: - es-define-property: 1.0.1 + es-define-property: 1.0.0 es-errors: 1.3.0 - gopd: 1.2.0 + gopd: 1.0.1 define-properties@1.2.1: dependencies: @@ -15160,7 +15172,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.8.1 + tslib: 2.6.3 dotenv@16.4.7: {} @@ -15269,19 +15281,19 @@ snapshots: data-view-buffer: 1.0.1 data-view-byte-length: 1.0.1 data-view-byte-offset: 1.0.0 - es-define-property: 1.0.1 + es-define-property: 1.0.0 es-errors: 1.3.0 es-object-atoms: 1.0.0 es-set-tostringtag: 2.0.3 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 get-symbol-description: 1.0.2 globalthis: 1.0.4 - gopd: 1.2.0 + gopd: 1.0.1 has-property-descriptors: 1.0.2 has-proto: 1.0.3 - has-symbols: 1.1.0 + has-symbols: 1.0.3 hasown: 2.0.2 internal-slot: 1.0.7 is-array-buffer: 3.0.4 @@ -15293,7 +15305,7 @@ snapshots: is-string: 1.0.7 is-typed-array: 1.1.13 is-weakref: 1.0.2 - object-inspect: 1.13.3 + object-inspect: 1.13.2 object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 @@ -15363,6 +15375,10 @@ snapshots: unbox-primitive: 1.1.0 which-typed-array: 1.1.18 + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -15370,8 +15386,8 @@ snapshots: es-get-iterator@1.1.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.7 - has-symbols: 1.1.0 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 is-arguments: 1.1.1 is-map: 2.0.3 is-set: 2.0.3 @@ -15423,7 +15439,7 @@ snapshots: es-set-tostringtag@2.0.3: dependencies: - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -16797,7 +16813,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 has-proto: 1.0.3 - has-symbols: 1.1.0 + has-symbols: 1.0.3 hasown: 2.0.2 get-intrinsic@1.2.7: @@ -16833,7 +16849,7 @@ snapshots: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 get-symbol-description@1.1.0: dependencies: @@ -16910,7 +16926,7 @@ snapshots: globalthis@1.0.4: dependencies: define-properties: 1.2.1 - gopd: 1.2.0 + gopd: 1.0.1 globby@11.1.0: dependencies: @@ -16954,7 +16970,7 @@ snapshots: gopd@1.0.1: dependencies: - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 gopd@1.2.0: {} @@ -16989,7 +17005,7 @@ snapshots: has-property-descriptors@1.0.2: dependencies: - es-define-property: 1.0.1 + es-define-property: 1.0.0 has-proto@1.0.3: {} @@ -17003,7 +17019,7 @@ snapshots: has-tostringtag@1.0.2: dependencies: - has-symbols: 1.1.0 + has-symbols: 1.0.3 hasown@2.0.2: dependencies: @@ -17135,7 +17151,7 @@ snapshots: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.1.0 + side-channel: 1.0.6 internal-slot@1.1.0: dependencies: @@ -17153,7 +17169,7 @@ snapshots: is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 is-array-buffer@3.0.5: dependencies: @@ -17308,7 +17324,7 @@ snapshots: is-symbol@1.0.4: dependencies: - has-symbols: 1.1.0 + has-symbols: 1.0.3 is-symbol@1.1.1: dependencies: @@ -17341,7 +17357,7 @@ snapshots: is-weakset@2.0.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 is-windows@1.0.2: {} @@ -17358,8 +17374,8 @@ snapshots: iterator.prototype@1.1.2: dependencies: define-properties: 1.2.1 - get-intrinsic: 1.2.7 - has-symbols: 1.1.0 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 @@ -17590,7 +17606,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.8.1 + tslib: 2.6.3 lru-cache@10.4.3: {} @@ -18207,7 +18223,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.8.1 + tslib: 2.6.3 node-abi@3.73.0: dependencies: @@ -18266,6 +18282,8 @@ snapshots: object-hash@3.0.0: {} + object-inspect@1.13.2: {} + object-inspect@1.13.3: {} object-is@1.1.6: @@ -18281,7 +18299,7 @@ snapshots: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - has-symbols: 1.1.0 + has-symbols: 1.0.3 object-keys: 1.1.1 object.assign@4.1.7: @@ -18748,7 +18766,7 @@ snapshots: qs@6.13.0: dependencies: - side-channel: 1.1.0 + side-channel: 1.0.6 query-registry@3.0.1: dependencies: @@ -18886,7 +18904,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.4 globalthis: 1.0.4 which-builtin-type: 1.1.4 @@ -19054,8 +19072,8 @@ snapshots: safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.7 - has-symbols: 1.1.0 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 isarray: 2.0.5 safe-array-concat@1.1.3: @@ -19145,8 +19163,8 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.7 - gopd: 1.2.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 has-property-descriptors: 1.0.2 set-function-name@2.0.2: @@ -19224,6 +19242,13 @@ snapshots: object-inspect: 1.13.3 side-channel-map: 1.0.1 + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + side-channel@1.1.0: dependencies: es-errors: 1.3.0 @@ -19257,7 +19282,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.8.1 + tslib: 2.6.3 snakecase-keys@5.4.4: dependencies: @@ -19394,7 +19419,7 @@ snapshots: internal-slot: 1.0.7 regexp.prototype.flags: 1.5.2 set-function-name: 2.0.2 - side-channel: 1.1.0 + side-channel: 1.0.6 string.prototype.matchall@4.0.12: dependencies: @@ -19968,7 +19993,7 @@ snapshots: dependencies: call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.2.0 + gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 @@ -19985,7 +20010,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.2.0 + gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 @@ -20003,7 +20028,7 @@ snapshots: dependencies: call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.2.0 + gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 @@ -20042,7 +20067,7 @@ snapshots: dependencies: call-bind: 1.0.7 has-bigints: 1.0.2 - has-symbols: 1.1.0 + has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 unbox-primitive@1.1.0: @@ -20327,7 +20352,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.2.0 + gopd: 1.0.1 has-tostringtag: 1.0.2 which-typed-array@1.1.18: From 534704e98f3a7e372ebdd3fbaf4c7c2373ef4613 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Feb 2025 07:59:34 +0000 Subject: [PATCH 09/11] configurable revalidation timeout --- packages/cloudflare/src/api/memory-queue.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/memory-queue.ts index e44fc1df..42367577 100644 --- a/packages/cloudflare/src/api/memory-queue.ts +++ b/packages/cloudflare/src/api/memory-queue.ts @@ -1,23 +1,29 @@ import logger from "@opennextjs/aws/logger.js"; import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js"; +type Options = { revalidationTimeoutMs: number }; + +export const DEFAULT_REVALIDATION_TIMEOUT = 10_000; + /** * The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route. * * It offers basic support for in-memory de-duping per isolate. */ -class MemoryQueue implements Queue { +export class MemoryQueue implements Queue { readonly name = "memory-queue"; public revalidatedPaths = new Map>(); + constructor(private opts: Options = { revalidationTimeoutMs: DEFAULT_REVALIDATION_TIMEOUT }) {} + public async send({ MessageBody: { host, url }, MessageGroupId }: QueueMessage): Promise { if (this.revalidatedPaths.has(MessageGroupId)) return; this.revalidatedPaths.set( MessageGroupId, // force remove to allow new revalidations incase something went wrong - setTimeout(() => this.revalidatedPaths.delete(MessageGroupId), 10_000) + setTimeout(() => this.revalidatedPaths.delete(MessageGroupId), this.opts.revalidationTimeoutMs) ); try { From 27c6b556f142cd71b86290b0168d00454040b957 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Feb 2025 07:59:53 +0000 Subject: [PATCH 10/11] split up tests --- .../cloudflare/src/api/memory-queue.spec.ts | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/cloudflare/src/api/memory-queue.spec.ts b/packages/cloudflare/src/api/memory-queue.spec.ts index 75b654d1..b97b4e19 100644 --- a/packages/cloudflare/src/api/memory-queue.spec.ts +++ b/packages/cloudflare/src/api/memory-queue.spec.ts @@ -1,7 +1,7 @@ import { generateMessageGroupId } from "@opennextjs/aws/core/routing/queue.js"; -import { beforeAll, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; -import cache from "./memory-queue"; +import cache, { DEFAULT_REVALIDATION_TIMEOUT } from "./memory-queue"; vi.mock("./.next/prerender-manifest.json", () => Promise.resolve({ preview: { previewModeId: "id" } })); @@ -17,20 +17,36 @@ describe("MemoryQueue", () => { globalThis.internalFetch = vi.fn().mockReturnValue(new Promise((res) => setTimeout(() => res(true), 1))); }); - it("should de-dupe revalidations", async () => { - const firstBatch = [cache.send(defaultOpts), cache.send(defaultOpts)]; + afterEach(() => vi.clearAllMocks()); + + it("should process revalidations for a path", async () => { + const firstRequest = cache.send(defaultOpts); + vi.advanceTimersByTime(DEFAULT_REVALIDATION_TIMEOUT); + await firstRequest; + expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); + + const secondRequest = cache.send(defaultOpts); + vi.advanceTimersByTime(1); + await secondRequest; + expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); + }); + + it("should process revalidations for multiple paths", async () => { + const firstRequest = cache.send(defaultOpts); vi.advanceTimersByTime(1); - await Promise.all(firstBatch); + await firstRequest; expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); - const secondBatch = [cache.send(defaultOpts)]; - vi.advanceTimersByTime(10_000); - await Promise.all(secondBatch); + const secondRequest = cache.send({ ...defaultOpts, MessageGroupId: generateMessageGroupId("/other") }); + vi.advanceTimersByTime(1); + await secondRequest; expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); + }); - const thirdBatch = [cache.send({ ...defaultOpts, MessageGroupId: generateMessageGroupId("/other") })]; + it("should de-dupe revalidations", async () => { + const requests = [cache.send(defaultOpts), cache.send(defaultOpts)]; vi.advanceTimersByTime(1); - await Promise.all(thirdBatch); - expect(globalThis.internalFetch).toHaveBeenCalledTimes(3); + await Promise.all(requests); + expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); }); }); From e134601f8a4433eaada04565239882caa74fbae1 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 12 Feb 2025 07:54:52 +0000 Subject: [PATCH 11/11] review comments --- .../cloudflare/src/api/memory-queue.spec.ts | 47 ++++++++++++++----- packages/cloudflare/src/api/memory-queue.ts | 10 ++-- .../src/cli/build/utils/ensure-cf-config.ts | 2 +- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/packages/cloudflare/src/api/memory-queue.spec.ts b/packages/cloudflare/src/api/memory-queue.spec.ts index b97b4e19..5e8e2310 100644 --- a/packages/cloudflare/src/api/memory-queue.spec.ts +++ b/packages/cloudflare/src/api/memory-queue.spec.ts @@ -1,16 +1,10 @@ import { generateMessageGroupId } from "@opennextjs/aws/core/routing/queue.js"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; -import cache, { DEFAULT_REVALIDATION_TIMEOUT } from "./memory-queue"; +import cache, { DEFAULT_REVALIDATION_TIMEOUT_MS } from "./memory-queue"; vi.mock("./.next/prerender-manifest.json", () => Promise.resolve({ preview: { previewModeId: "id" } })); -const defaultOpts = { - MessageBody: { host: "test.local", url: "/test" }, - MessageGroupId: generateMessageGroupId("/test"), - MessageDeduplicationId: "", -}; - describe("MemoryQueue", () => { beforeAll(() => { vi.useFakeTimers(); @@ -20,31 +14,58 @@ describe("MemoryQueue", () => { afterEach(() => vi.clearAllMocks()); it("should process revalidations for a path", async () => { - const firstRequest = cache.send(defaultOpts); - vi.advanceTimersByTime(DEFAULT_REVALIDATION_TIMEOUT); + const firstRequest = cache.send({ + MessageBody: { host: "test.local", url: "/test" }, + MessageGroupId: generateMessageGroupId("/test"), + MessageDeduplicationId: "", + }); + vi.advanceTimersByTime(DEFAULT_REVALIDATION_TIMEOUT_MS); await firstRequest; expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); - const secondRequest = cache.send(defaultOpts); + const secondRequest = cache.send({ + MessageBody: { host: "test.local", url: "/test" }, + MessageGroupId: generateMessageGroupId("/test"), + MessageDeduplicationId: "", + }); vi.advanceTimersByTime(1); await secondRequest; expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); }); it("should process revalidations for multiple paths", async () => { - const firstRequest = cache.send(defaultOpts); + const firstRequest = cache.send({ + MessageBody: { host: "test.local", url: "/test" }, + MessageGroupId: generateMessageGroupId("/test"), + MessageDeduplicationId: "", + }); vi.advanceTimersByTime(1); await firstRequest; expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); - const secondRequest = cache.send({ ...defaultOpts, MessageGroupId: generateMessageGroupId("/other") }); + const secondRequest = cache.send({ + MessageBody: { host: "test.local", url: "/test" }, + MessageGroupId: generateMessageGroupId("/other"), + MessageDeduplicationId: "", + }); vi.advanceTimersByTime(1); await secondRequest; expect(globalThis.internalFetch).toHaveBeenCalledTimes(2); }); it("should de-dupe revalidations", async () => { - const requests = [cache.send(defaultOpts), cache.send(defaultOpts)]; + const requests = [ + cache.send({ + MessageBody: { host: "test.local", url: "/test" }, + MessageGroupId: generateMessageGroupId("/test"), + MessageDeduplicationId: "", + }), + cache.send({ + MessageBody: { host: "test.local", url: "/test" }, + MessageGroupId: generateMessageGroupId("/test"), + MessageDeduplicationId: "", + }), + ]; vi.advanceTimersByTime(1); await Promise.all(requests); expect(globalThis.internalFetch).toHaveBeenCalledTimes(1); diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/memory-queue.ts index 42367577..2a0b6adf 100644 --- a/packages/cloudflare/src/api/memory-queue.ts +++ b/packages/cloudflare/src/api/memory-queue.ts @@ -1,9 +1,7 @@ import logger from "@opennextjs/aws/logger.js"; import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js"; -type Options = { revalidationTimeoutMs: number }; - -export const DEFAULT_REVALIDATION_TIMEOUT = 10_000; +export const DEFAULT_REVALIDATION_TIMEOUT_MS = 10_000; /** * The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route. @@ -13,11 +11,11 @@ export const DEFAULT_REVALIDATION_TIMEOUT = 10_000; export class MemoryQueue implements Queue { readonly name = "memory-queue"; - public revalidatedPaths = new Map>(); + revalidatedPaths = new Map>(); - constructor(private opts: Options = { revalidationTimeoutMs: DEFAULT_REVALIDATION_TIMEOUT }) {} + constructor(private opts = { revalidationTimeoutMs: DEFAULT_REVALIDATION_TIMEOUT_MS }) {} - public async send({ MessageBody: { host, url }, MessageGroupId }: QueueMessage): Promise { + async send({ MessageBody: { host, url }, MessageGroupId }: QueueMessage): Promise { if (this.revalidatedPaths.has(MessageGroupId)) return; this.revalidatedPaths.set( diff --git a/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts index 14f2d1ec..507161ff 100644 --- a/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts +++ b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts @@ -17,7 +17,7 @@ export function ensureCloudflareConfig(config: OpenNextConfig) { dftMaybeUseQueue: config.default?.override?.queue === "dummy" || config.default?.override?.queue === "direct" || - typeof config.default?.override?.incrementalCache === "function", + typeof config.default?.override?.queue === "function", disableCacheInterception: config.dangerous?.enableCacheInterception !== true, mwIsMiddlewareExternal: config.middleware?.external == true, mwUseCloudflareWrapper: config.middleware?.override?.wrapper === "cloudflare-edge",