vinext 0.0.30 → 0.0.31
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 +12 -6
- package/dist/build/prerender.d.ts +188 -0
- package/dist/build/prerender.js +675 -0
- package/dist/build/prerender.js.map +1 -0
- package/dist/build/report.d.ts +45 -46
- package/dist/build/report.js +247 -276
- package/dist/build/report.js.map +1 -1
- package/dist/build/run-prerender.d.ts +62 -0
- package/dist/build/run-prerender.js +183 -0
- package/dist/build/run-prerender.js.map +1 -0
- package/dist/build/server-manifest.d.ts +19 -0
- package/dist/build/server-manifest.js +29 -0
- package/dist/build/server-manifest.js.map +1 -0
- package/dist/build/static-export.d.ts +51 -66
- package/dist/build/static-export.js +51 -545
- package/dist/build/static-export.js.map +1 -1
- package/dist/check.d.ts +26 -24
- package/dist/check.js +591 -571
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +1 -15
- package/dist/cli.js +430 -491
- package/dist/cli.js.map +1 -1
- package/dist/client/entry.d.ts +1 -2
- package/dist/client/entry.js +49 -62
- package/dist/client/entry.js.map +1 -1
- package/dist/client/validate-module-path.d.ts +4 -1
- package/dist/client/validate-module-path.js +23 -28
- package/dist/client/validate-module-path.js.map +1 -1
- package/dist/client/vinext-next-data.d.ts +15 -20
- package/dist/client/vinext-next-data.js +0 -1
- package/dist/cloudflare/index.d.ts +3 -8
- package/dist/cloudflare/index.js +3 -8
- package/dist/cloudflare/kv-cache-handler.d.ts +95 -105
- package/dist/cloudflare/kv-cache-handler.js +354 -380
- package/dist/cloudflare/kv-cache-handler.js.map +1 -1
- package/dist/cloudflare/tpr.d.ts +36 -34
- package/dist/cloudflare/tpr.js +460 -603
- package/dist/cloudflare/tpr.js.map +1 -1
- package/dist/config/config-matchers.d.ts +31 -40
- package/dist/config/config-matchers.js +727 -936
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/dotenv.d.ts +18 -11
- package/dist/config/dotenv.js +79 -84
- package/dist/config/dotenv.js.map +1 -1
- package/dist/config/next-config.d.ts +156 -146
- package/dist/config/next-config.js +374 -464
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts +87 -96
- package/dist/deploy.js +490 -628
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.d.ts +4 -1
- package/dist/entries/app-browser-entry.js +12 -8
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +33 -20
- package/dist/entries/app-rsc-entry.js +442 -211
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-ssr-entry.d.ts +9 -1
- package/dist/entries/app-ssr-entry.js +61 -28
- package/dist/entries/app-ssr-entry.js.map +1 -1
- package/dist/entries/pages-client-entry.d.ts +6 -2
- package/dist/entries/pages-client-entry.js +30 -33
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-entry-helpers.d.ts +5 -1
- package/dist/entries/pages-entry-helpers.js +17 -14
- package/dist/entries/pages-entry-helpers.js.map +1 -1
- package/dist/entries/pages-server-entry.d.ts +6 -2
- package/dist/entries/pages-server-entry.js +84 -113
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.d.ts +82 -62
- package/dist/index.js +2172 -3133
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +40 -37
- package/dist/init.js +201 -258
- package/dist/init.js.map +1 -1
- package/dist/plugins/async-hooks-stub.d.ts +7 -3
- package/dist/plugins/async-hooks-stub.js +39 -42
- package/dist/plugins/async-hooks-stub.js.map +1 -1
- package/dist/plugins/client-reference-dedup.d.ts +7 -3
- package/dist/plugins/client-reference-dedup.js +63 -88
- package/dist/plugins/client-reference-dedup.js.map +1 -1
- package/dist/routing/app-router.d.ts +100 -96
- package/dist/routing/app-router.js +560 -670
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +18 -15
- package/dist/routing/file-matcher.js +65 -65
- package/dist/routing/file-matcher.js.map +1 -1
- package/dist/routing/pages-router.d.ts +23 -24
- package/dist/routing/pages-router.js +147 -172
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/route-trie.d.ts +23 -20
- package/dist/routing/route-trie.js +131 -151
- package/dist/routing/route-trie.js.map +1 -1
- package/dist/routing/route-validation.d.ts +5 -2
- package/dist/routing/route-validation.js +98 -130
- package/dist/routing/route-validation.js.map +1 -1
- package/dist/routing/utils.d.ts +10 -7
- package/dist/routing/utils.js +75 -111
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/api-handler.d.ts +8 -13
- package/dist/server/api-handler.js +161 -193
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-router-entry.d.ts +6 -16
- package/dist/server/app-router-entry.js +26 -54
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/dev-module-runner.d.ts +11 -64
- package/dist/server/dev-module-runner.js +89 -101
- package/dist/server/dev-module-runner.js.map +1 -1
- package/dist/server/dev-origin-check.d.ts +12 -10
- package/dist/server/dev-origin-check.js +98 -108
- package/dist/server/dev-origin-check.js.map +1 -1
- package/dist/server/dev-server.d.ts +17 -14
- package/dist/server/dev-server.js +542 -869
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/html.d.ts +4 -1
- package/dist/server/html.js +25 -26
- package/dist/server/html.js.map +1 -1
- package/dist/server/image-optimization.d.ts +31 -28
- package/dist/server/image-optimization.js +181 -210
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/instrumentation.d.ts +25 -22
- package/dist/server/instrumentation.js +110 -122
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/isr-cache.d.ts +16 -26
- package/dist/server/isr-cache.js +106 -128
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/metadata-routes.d.ts +85 -88
- package/dist/server/metadata-routes.js +270 -317
- package/dist/server/metadata-routes.js.map +1 -1
- package/dist/server/middleware-codegen.d.ts +7 -4
- package/dist/server/middleware-codegen.js +61 -61
- package/dist/server/middleware-codegen.js.map +1 -1
- package/dist/server/middleware-request-headers.d.ts +8 -6
- package/dist/server/middleware-request-headers.js +47 -65
- package/dist/server/middleware-request-headers.js.map +1 -1
- package/dist/server/middleware.d.ts +31 -47
- package/dist/server/middleware.js +273 -404
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/normalize-path.d.ts +4 -1
- package/dist/server/normalize-path.js +33 -47
- package/dist/server/normalize-path.js.map +1 -1
- package/dist/server/pages-i18n.d.ts +38 -30
- package/dist/server/pages-i18n.js +112 -139
- package/dist/server/pages-i18n.js.map +1 -1
- package/dist/server/prod-server.d.ts +19 -31
- package/dist/server/prod-server.js +714 -945
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/request-log.d.ts +18 -12
- package/dist/server/request-log.js +45 -52
- package/dist/server/request-log.js.map +1 -1
- package/dist/server/request-pipeline.d.ts +9 -17
- package/dist/server/request-pipeline.js +133 -184
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/server/worker-utils.d.ts +4 -1
- package/dist/server/worker-utils.js +31 -37
- package/dist/server/worker-utils.js.map +1 -1
- package/dist/shims/amp.d.ts +5 -2
- package/dist/shims/amp.js +19 -15
- package/dist/shims/amp.js.map +1 -1
- package/dist/shims/app.d.ts +8 -10
- package/dist/shims/app.js +0 -1
- package/dist/shims/cache-runtime.d.ts +20 -45
- package/dist/shims/cache-runtime.js +271 -422
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +130 -121
- package/dist/shims/cache.js +339 -427
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/client-only.d.ts +1 -18
- package/dist/shims/client-only.js +0 -17
- package/dist/shims/compat-router.d.ts +4 -1
- package/dist/shims/compat-router.js +23 -19
- package/dist/shims/compat-router.js.map +1 -1
- package/dist/shims/config.d.ts +7 -5
- package/dist/shims/config.js +16 -23
- package/dist/shims/config.js.map +1 -1
- package/dist/shims/constants.d.ts +119 -118
- package/dist/shims/constants.js +159 -164
- package/dist/shims/constants.js.map +1 -1
- package/dist/shims/document.d.ts +20 -16
- package/dist/shims/document.js +41 -22
- package/dist/shims/document.js.map +1 -1
- package/dist/shims/dynamic.d.ts +13 -22
- package/dist/shims/dynamic.js +122 -136
- package/dist/shims/dynamic.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +22 -15
- package/dist/shims/error-boundary.js +81 -79
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/error.d.ts +11 -12
- package/dist/shims/error.js +35 -39
- package/dist/shims/error.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +16 -14
- package/dist/shims/fetch-cache.js +437 -645
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/font-google-base.d.ts +28 -26
- package/dist/shims/font-google-base.js +238 -325
- package/dist/shims/font-google-base.js.map +1 -1
- package/dist/shims/font-google.d.ts +3 -3
- package/dist/shims/font-google.generated.d.ts +1928 -1924
- package/dist/shims/font-google.generated.js +1928 -2133
- package/dist/shims/font-google.generated.js.map +1 -1
- package/dist/shims/font-google.js +3 -3
- package/dist/shims/font-local.d.ts +28 -26
- package/dist/shims/font-local.js +204 -260
- package/dist/shims/font-local.js.map +1 -1
- package/dist/shims/form.d.ts +13 -27
- package/dist/shims/form.js +128 -180
- package/dist/shims/form.js.map +1 -1
- package/dist/shims/head-state.d.ts +8 -13
- package/dist/shims/head-state.js +25 -42
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/head.d.ts +16 -20
- package/dist/shims/head.js +172 -250
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +84 -78
- package/dist/shims/headers.js +447 -575
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/i18n-context.d.ts +16 -20
- package/dist/shims/i18n-context.js +35 -48
- package/dist/shims/i18n-context.js.map +1 -1
- package/dist/shims/i18n-state.d.ts +8 -14
- package/dist/shims/i18n-state.js +34 -42
- package/dist/shims/i18n-state.js.map +1 -1
- package/dist/shims/image-config.d.ts +11 -8
- package/dist/shims/image-config.js +50 -83
- package/dist/shims/image-config.js.map +1 -1
- package/dist/shims/image.d.ts +37 -46
- package/dist/shims/image.js +283 -308
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/api-utils.d.ts +7 -4
- package/dist/shims/internal/api-utils.js +0 -6
- package/dist/shims/internal/app-router-context.d.ts +22 -17
- package/dist/shims/internal/app-router-context.js +17 -13
- package/dist/shims/internal/app-router-context.js.map +1 -1
- package/dist/shims/internal/cookies.d.ts +2 -9
- package/dist/shims/internal/cookies.js +2 -9
- package/dist/shims/internal/parse-cookie-header.d.ts +4 -1
- package/dist/shims/internal/parse-cookie-header.js +29 -29
- package/dist/shims/internal/parse-cookie-header.js.map +1 -1
- package/dist/shims/internal/router-context.d.ts +6 -1
- package/dist/shims/internal/router-context.js +11 -7
- package/dist/shims/internal/router-context.js.map +1 -1
- package/dist/shims/internal/utils.d.ts +40 -37
- package/dist/shims/internal/utils.js +24 -30
- package/dist/shims/internal/utils.js.map +1 -1
- package/dist/shims/internal/work-unit-async-storage.d.ts +6 -10
- package/dist/shims/internal/work-unit-async-storage.js +14 -11
- package/dist/shims/internal/work-unit-async-storage.js.map +1 -1
- package/dist/shims/layout-segment-context.d.ts +11 -14
- package/dist/shims/layout-segment-context.js +24 -23
- package/dist/shims/layout-segment-context.js.map +1 -1
- package/dist/shims/legacy-image.d.ts +39 -46
- package/dist/shims/legacy-image.js +47 -42
- package/dist/shims/legacy-image.js.map +1 -1
- package/dist/shims/link.d.ts +32 -36
- package/dist/shims/link.js +255 -391
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +210 -202
- package/dist/shims/metadata.js +545 -546
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation-state.d.ts +10 -18
- package/dist/shims/navigation-state.js +66 -74
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.d.ts +59 -63
- package/dist/shims/navigation.js +505 -704
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/og.d.ts +2 -20
- package/dist/shims/og.js +2 -19
- package/dist/shims/readonly-url-search-params.d.ts +8 -5
- package/dist/shims/readonly-url-search-params.js +26 -22
- package/dist/shims/readonly-url-search-params.js.map +1 -1
- package/dist/shims/request-context.d.ts +8 -5
- package/dist/shims/request-context.js +50 -60
- package/dist/shims/request-context.js.map +1 -1
- package/dist/shims/request-state-types.d.ts +11 -11
- package/dist/shims/request-state-types.js +0 -1
- package/dist/shims/router-state.d.ts +13 -10
- package/dist/shims/router-state.js +34 -43
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.d.ts +81 -85
- package/dist/shims/router.js +506 -628
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.d.ts +39 -48
- package/dist/shims/script.js +107 -160
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server-only.d.ts +1 -19
- package/dist/shims/server-only.js +0 -18
- package/dist/shims/server.d.ts +175 -164
- package/dist/shims/server.js +462 -478
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/unified-request-context.d.ts +20 -20
- package/dist/shims/unified-request-context.js +81 -99
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/shims/url-safety.d.ts +4 -1
- package/dist/shims/url-safety.js +15 -11
- package/dist/shims/url-safety.js.map +1 -1
- package/dist/shims/url-utils.d.ts +8 -5
- package/dist/shims/url-utils.js +62 -93
- package/dist/shims/url-utils.js.map +1 -1
- package/dist/shims/web-vitals.d.ts +10 -8
- package/dist/shims/web-vitals.js +9 -15
- package/dist/shims/web-vitals.js.map +1 -1
- package/dist/utils/base-path.d.ts +5 -2
- package/dist/utils/base-path.js +21 -19
- package/dist/utils/base-path.js.map +1 -1
- package/dist/utils/domain-locale.d.ts +17 -9
- package/dist/utils/domain-locale.js +36 -56
- package/dist/utils/domain-locale.js.map +1 -1
- package/dist/utils/hash.d.ts +4 -1
- package/dist/utils/hash.js +19 -17
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/manifest-paths.d.ts +6 -3
- package/dist/utils/manifest-paths.js +15 -16
- package/dist/utils/manifest-paths.js.map +1 -1
- package/dist/utils/project.d.ts +13 -11
- package/dist/utils/project.js +169 -216
- package/dist/utils/project.js.map +1 -1
- package/dist/utils/query.d.ts +8 -6
- package/dist/utils/query.js +57 -67
- package/dist/utils/query.js.map +1 -1
- package/package.json +10 -9
- package/dist/build/report.d.ts.map +0 -1
- package/dist/build/static-export.d.ts.map +0 -1
- package/dist/check.d.ts.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/client/entry.d.ts.map +0 -1
- package/dist/client/validate-module-path.d.ts.map +0 -1
- package/dist/client/vinext-next-data.d.ts.map +0 -1
- package/dist/client/vinext-next-data.js.map +0 -1
- package/dist/cloudflare/index.d.ts.map +0 -1
- package/dist/cloudflare/index.js.map +0 -1
- package/dist/cloudflare/kv-cache-handler.d.ts.map +0 -1
- package/dist/cloudflare/tpr.d.ts.map +0 -1
- package/dist/config/config-matchers.d.ts.map +0 -1
- package/dist/config/dotenv.d.ts.map +0 -1
- package/dist/config/next-config.d.ts.map +0 -1
- package/dist/deploy.d.ts.map +0 -1
- package/dist/entries/app-browser-entry.d.ts.map +0 -1
- package/dist/entries/app-rsc-entry.d.ts.map +0 -1
- package/dist/entries/app-ssr-entry.d.ts.map +0 -1
- package/dist/entries/pages-client-entry.d.ts.map +0 -1
- package/dist/entries/pages-entry-helpers.d.ts.map +0 -1
- package/dist/entries/pages-server-entry.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/init.d.ts.map +0 -1
- package/dist/plugins/async-hooks-stub.d.ts.map +0 -1
- package/dist/plugins/client-reference-dedup.d.ts.map +0 -1
- package/dist/routing/app-router.d.ts.map +0 -1
- package/dist/routing/file-matcher.d.ts.map +0 -1
- package/dist/routing/pages-router.d.ts.map +0 -1
- package/dist/routing/route-trie.d.ts.map +0 -1
- package/dist/routing/route-validation.d.ts.map +0 -1
- package/dist/routing/utils.d.ts.map +0 -1
- package/dist/server/api-handler.d.ts.map +0 -1
- package/dist/server/app-router-entry.d.ts.map +0 -1
- package/dist/server/dev-module-runner.d.ts.map +0 -1
- package/dist/server/dev-origin-check.d.ts.map +0 -1
- package/dist/server/dev-server.d.ts.map +0 -1
- package/dist/server/html.d.ts.map +0 -1
- package/dist/server/image-optimization.d.ts.map +0 -1
- package/dist/server/instrumentation.d.ts.map +0 -1
- package/dist/server/isr-cache.d.ts.map +0 -1
- package/dist/server/metadata-routes.d.ts.map +0 -1
- package/dist/server/middleware-codegen.d.ts.map +0 -1
- package/dist/server/middleware-request-headers.d.ts.map +0 -1
- package/dist/server/middleware.d.ts.map +0 -1
- package/dist/server/normalize-path.d.ts.map +0 -1
- package/dist/server/pages-i18n.d.ts.map +0 -1
- package/dist/server/prod-server.d.ts.map +0 -1
- package/dist/server/request-log.d.ts.map +0 -1
- package/dist/server/request-pipeline.d.ts.map +0 -1
- package/dist/server/worker-utils.d.ts.map +0 -1
- package/dist/shims/amp.d.ts.map +0 -1
- package/dist/shims/app.d.ts.map +0 -1
- package/dist/shims/app.js.map +0 -1
- package/dist/shims/cache-runtime.d.ts.map +0 -1
- package/dist/shims/cache.d.ts.map +0 -1
- package/dist/shims/client-only.d.ts.map +0 -1
- package/dist/shims/client-only.js.map +0 -1
- package/dist/shims/compat-router.d.ts.map +0 -1
- package/dist/shims/config.d.ts.map +0 -1
- package/dist/shims/constants.d.ts.map +0 -1
- package/dist/shims/document.d.ts.map +0 -1
- package/dist/shims/dynamic.d.ts.map +0 -1
- package/dist/shims/error-boundary.d.ts.map +0 -1
- package/dist/shims/error.d.ts.map +0 -1
- package/dist/shims/fetch-cache.d.ts.map +0 -1
- package/dist/shims/font-google-base.d.ts.map +0 -1
- package/dist/shims/font-google.d.ts.map +0 -1
- package/dist/shims/font-google.generated.d.ts.map +0 -1
- package/dist/shims/font-google.js.map +0 -1
- package/dist/shims/font-local.d.ts.map +0 -1
- package/dist/shims/form.d.ts.map +0 -1
- package/dist/shims/head-state.d.ts.map +0 -1
- package/dist/shims/head.d.ts.map +0 -1
- package/dist/shims/headers.d.ts.map +0 -1
- package/dist/shims/i18n-context.d.ts.map +0 -1
- package/dist/shims/i18n-state.d.ts.map +0 -1
- package/dist/shims/image-config.d.ts.map +0 -1
- package/dist/shims/image.d.ts.map +0 -1
- package/dist/shims/internal/api-utils.d.ts.map +0 -1
- package/dist/shims/internal/api-utils.js.map +0 -1
- package/dist/shims/internal/app-router-context.d.ts.map +0 -1
- package/dist/shims/internal/cookies.d.ts.map +0 -1
- package/dist/shims/internal/cookies.js.map +0 -1
- package/dist/shims/internal/parse-cookie-header.d.ts.map +0 -1
- package/dist/shims/internal/router-context.d.ts.map +0 -1
- package/dist/shims/internal/utils.d.ts.map +0 -1
- package/dist/shims/internal/work-unit-async-storage.d.ts.map +0 -1
- package/dist/shims/layout-segment-context.d.ts.map +0 -1
- package/dist/shims/legacy-image.d.ts.map +0 -1
- package/dist/shims/link.d.ts.map +0 -1
- package/dist/shims/metadata.d.ts.map +0 -1
- package/dist/shims/navigation-state.d.ts.map +0 -1
- package/dist/shims/navigation.d.ts.map +0 -1
- package/dist/shims/og.d.ts.map +0 -1
- package/dist/shims/og.js.map +0 -1
- package/dist/shims/readonly-url-search-params.d.ts.map +0 -1
- package/dist/shims/request-context.d.ts.map +0 -1
- package/dist/shims/request-state-types.d.ts.map +0 -1
- package/dist/shims/request-state-types.js.map +0 -1
- package/dist/shims/router-state.d.ts.map +0 -1
- package/dist/shims/router.d.ts.map +0 -1
- package/dist/shims/script.d.ts.map +0 -1
- package/dist/shims/server-only.d.ts.map +0 -1
- package/dist/shims/server-only.js.map +0 -1
- package/dist/shims/server.d.ts.map +0 -1
- package/dist/shims/unified-request-context.d.ts.map +0 -1
- package/dist/shims/url-safety.d.ts.map +0 -1
- package/dist/shims/url-utils.d.ts.map +0 -1
- package/dist/shims/web-vitals.d.ts.map +0 -1
- package/dist/utils/base-path.d.ts.map +0 -1
- package/dist/utils/domain-locale.d.ts.map +0 -1
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/manifest-paths.d.ts.map +0 -1
- package/dist/utils/project.d.ts.map +0 -1
- package/dist/utils/query.d.ts.map +0 -1
|
@@ -1,411 +1,385 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloudflare KV-backed CacheHandler for vinext.
|
|
3
|
-
*
|
|
4
|
-
* Provides persistent ISR caching on Cloudflare Workers using KV as the
|
|
5
|
-
* storage backend. Supports time-based expiry (stale-while-revalidate)
|
|
6
|
-
* and tag-based invalidation.
|
|
7
|
-
*
|
|
8
|
-
* Usage in worker/index.ts:
|
|
9
|
-
*
|
|
10
|
-
* import { KVCacheHandler } from "vinext/cloudflare";
|
|
11
|
-
* import { setCacheHandler } from "vinext/shims/cache";
|
|
12
|
-
*
|
|
13
|
-
* export default {
|
|
14
|
-
* async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
15
|
-
* setCacheHandler(new KVCacheHandler(env.VINEXT_CACHE));
|
|
16
|
-
* // ctx is propagated automatically via runWithExecutionContext in
|
|
17
|
-
* // the vinext handler — no need to pass it to KVCacheHandler.
|
|
18
|
-
* // ... rest of worker handler
|
|
19
|
-
* }
|
|
20
|
-
* };
|
|
21
|
-
*
|
|
22
|
-
* Wrangler config (wrangler.jsonc):
|
|
23
|
-
*
|
|
24
|
-
* {
|
|
25
|
-
* "kv_namespaces": [
|
|
26
|
-
* { "binding": "VINEXT_CACHE", "id": "<your-kv-namespace-id>" }
|
|
27
|
-
* ]
|
|
28
|
-
* }
|
|
29
|
-
*/
|
|
30
|
-
import { Buffer } from "node:buffer";
|
|
31
1
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
//#region src/cloudflare/kv-cache-handler.ts
|
|
4
|
+
/**
|
|
5
|
+
* Cloudflare KV-backed CacheHandler for vinext.
|
|
6
|
+
*
|
|
7
|
+
* Provides persistent ISR caching on Cloudflare Workers using KV as the
|
|
8
|
+
* storage backend. Supports time-based expiry (stale-while-revalidate)
|
|
9
|
+
* and tag-based invalidation.
|
|
10
|
+
*
|
|
11
|
+
* Usage in worker/index.ts:
|
|
12
|
+
*
|
|
13
|
+
* import { KVCacheHandler } from "vinext/cloudflare";
|
|
14
|
+
* import { setCacheHandler } from "vinext/shims/cache";
|
|
15
|
+
*
|
|
16
|
+
* export default {
|
|
17
|
+
* async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
18
|
+
* setCacheHandler(new KVCacheHandler(env.VINEXT_CACHE));
|
|
19
|
+
* // ctx is propagated automatically via runWithExecutionContext in
|
|
20
|
+
* // the vinext handler — no need to pass it to KVCacheHandler.
|
|
21
|
+
* // ... rest of worker handler
|
|
22
|
+
* }
|
|
23
|
+
* };
|
|
24
|
+
*
|
|
25
|
+
* Wrangler config (wrangler.jsonc):
|
|
26
|
+
*
|
|
27
|
+
* {
|
|
28
|
+
* "kv_namespaces": [
|
|
29
|
+
* { "binding": "VINEXT_CACHE", "id": "<your-kv-namespace-id>" }
|
|
30
|
+
* ]
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
32
33
|
/** Key prefix for tag invalidation timestamps. */
|
|
33
34
|
const TAG_PREFIX = "__tag:";
|
|
34
35
|
/** Key prefix for cache entries. */
|
|
35
36
|
const ENTRY_PREFIX = "cache:";
|
|
37
|
+
/** Prefix used by revalidatePath for path-based tags. */
|
|
38
|
+
const PATH_TAG_PREFIX = "_N_T_";
|
|
36
39
|
/** Max tag length to prevent KV key abuse. */
|
|
37
40
|
const MAX_TAG_LENGTH = 256;
|
|
38
41
|
/** Matches a valid base64 string (standard alphabet with optional padding). */
|
|
39
42
|
const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
40
43
|
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
* Validate a cache tag. Returns null if invalid.
|
|
45
|
+
* Note: `:` is rejected because TAG_PREFIX and ENTRY_PREFIX use `:` as a
|
|
46
|
+
* separator — allowing `:` in user tags could cause ambiguous key lookups.
|
|
47
|
+
*/
|
|
45
48
|
function validateTag(tag) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Slash is allowed because revalidatePath() relies on pathname tags like
|
|
50
|
-
// "/posts/hello" and "_N_T_/posts/hello".
|
|
51
|
-
// eslint-disable-next-line no-control-regex -- intentional: reject control chars in tags
|
|
52
|
-
if (/[\x00-\x1f\\:]/.test(tag))
|
|
53
|
-
return null;
|
|
54
|
-
return tag;
|
|
49
|
+
if (typeof tag !== "string" || tag.length === 0 || tag.length > MAX_TAG_LENGTH) return null;
|
|
50
|
+
if (/[\x00-\x1f\\:]/.test(tag)) return null;
|
|
51
|
+
return tag;
|
|
55
52
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
constructor(kvNamespace, options) {
|
|
66
|
-
this.kv = kvNamespace;
|
|
67
|
-
this.prefix = options?.appPrefix ? `${options.appPrefix}:` : "";
|
|
68
|
-
this.ctx = options?.ctx;
|
|
69
|
-
this.ttlSeconds = options?.ttlSeconds ?? 30 * 24 * 3600;
|
|
70
|
-
this._tagCacheTtl = options?.tagCacheTtlMs ?? 5_000;
|
|
71
|
-
}
|
|
72
|
-
async get(key, _ctx) {
|
|
73
|
-
const kvKey = this.prefix + ENTRY_PREFIX + key;
|
|
74
|
-
const raw = await this.kv.get(kvKey);
|
|
75
|
-
if (!raw)
|
|
76
|
-
return null;
|
|
77
|
-
let parsed;
|
|
78
|
-
try {
|
|
79
|
-
parsed = JSON.parse(raw);
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
// Corrupted JSON — fire cleanup delete in the background and treat as miss.
|
|
83
|
-
// Using waitUntil ensures the delete isn't killed when the Response is returned.
|
|
84
|
-
this._deleteInBackground(kvKey);
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
// Validate deserialized shape before using
|
|
88
|
-
const entry = validateCacheEntry(parsed);
|
|
89
|
-
if (!entry) {
|
|
90
|
-
console.error("[vinext] Invalid cache entry shape for key:", key);
|
|
91
|
-
this._deleteInBackground(kvKey);
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
// Restore ArrayBuffer fields that were base64-encoded for JSON storage
|
|
95
|
-
let restoredValue = null;
|
|
96
|
-
if (entry.value) {
|
|
97
|
-
restoredValue = restoreArrayBuffers(entry.value);
|
|
98
|
-
if (!restoredValue) {
|
|
99
|
-
// base64 decode failed — corrupted entry, treat as miss
|
|
100
|
-
this._deleteInBackground(kvKey);
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// Check tag-based invalidation.
|
|
105
|
-
// Uses a local in-memory cache to avoid redundant KV reads for recently-seen tags.
|
|
106
|
-
if (entry.tags.length > 0) {
|
|
107
|
-
const now = Date.now();
|
|
108
|
-
const uncachedTags = [];
|
|
109
|
-
// First pass: check local cache for each tag.
|
|
110
|
-
// Delete expired entries to prevent unbounded Map growth in long-lived isolates.
|
|
111
|
-
for (const tag of entry.tags) {
|
|
112
|
-
const cached = this._tagCache.get(tag);
|
|
113
|
-
if (cached && now - cached.fetchedAt < this._tagCacheTtl) {
|
|
114
|
-
// Local cache hit — check invalidation inline
|
|
115
|
-
if (Number.isNaN(cached.timestamp) || cached.timestamp >= entry.lastModified) {
|
|
116
|
-
this._deleteInBackground(kvKey);
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
// Expired or absent — evict stale entry and re-fetch from KV
|
|
122
|
-
if (cached)
|
|
123
|
-
this._tagCache.delete(tag);
|
|
124
|
-
uncachedTags.push(tag);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Second pass: fetch uncached tags from KV in parallel.
|
|
128
|
-
// Populate the local cache for ALL fetched tags before checking invalidation,
|
|
129
|
-
// so that KV round-trips are not wasted when an earlier tag triggers an
|
|
130
|
-
// early return — subsequent get() calls benefit from the already-fetched results.
|
|
131
|
-
if (uncachedTags.length > 0) {
|
|
132
|
-
const tagResults = await Promise.all(uncachedTags.map((tag) => this.kv.get(this.prefix + TAG_PREFIX + tag)));
|
|
133
|
-
// Populate cache for all results first, then check for invalidation.
|
|
134
|
-
// Two-loop structure ensures all tag results are cached even when an
|
|
135
|
-
// earlier tag would cause an early return — so subsequent get() calls
|
|
136
|
-
// for entries sharing those tags don't redundantly re-fetch from KV.
|
|
137
|
-
for (let i = 0; i < uncachedTags.length; i++) {
|
|
138
|
-
const tagTime = tagResults[i];
|
|
139
|
-
const tagTimestamp = tagTime ? Number(tagTime) : 0;
|
|
140
|
-
this._tagCache.set(uncachedTags[i], { timestamp: tagTimestamp, fetchedAt: now });
|
|
141
|
-
}
|
|
142
|
-
// Then check for invalidation using the now-cached timestamps
|
|
143
|
-
for (const tag of uncachedTags) {
|
|
144
|
-
const cached = this._tagCache.get(tag);
|
|
145
|
-
if (cached.timestamp !== 0) {
|
|
146
|
-
if (Number.isNaN(cached.timestamp) || cached.timestamp >= entry.lastModified) {
|
|
147
|
-
this._deleteInBackground(kvKey);
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
// Check time-based expiry — return stale with cacheState
|
|
155
|
-
if (entry.revalidateAt !== null && Date.now() > entry.revalidateAt) {
|
|
156
|
-
return {
|
|
157
|
-
lastModified: entry.lastModified,
|
|
158
|
-
value: restoredValue,
|
|
159
|
-
cacheState: "stale",
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
lastModified: entry.lastModified,
|
|
164
|
-
value: restoredValue,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
set(key, data, ctx) {
|
|
168
|
-
// Collect, validate, and dedupe tags from data and context
|
|
169
|
-
const tagSet = new Set();
|
|
170
|
-
if (data && "tags" in data && Array.isArray(data.tags)) {
|
|
171
|
-
for (const t of data.tags) {
|
|
172
|
-
const validated = validateTag(t);
|
|
173
|
-
if (validated)
|
|
174
|
-
tagSet.add(validated);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (ctx && "tags" in ctx && Array.isArray(ctx.tags)) {
|
|
178
|
-
for (const t of ctx.tags) {
|
|
179
|
-
const validated = validateTag(t);
|
|
180
|
-
if (validated)
|
|
181
|
-
tagSet.add(validated);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const tags = [...tagSet];
|
|
185
|
-
// Resolve effective revalidate — data overrides ctx.
|
|
186
|
-
// revalidate: 0 means "don't cache", so skip storage entirely.
|
|
187
|
-
let effectiveRevalidate;
|
|
188
|
-
if (ctx) {
|
|
189
|
-
const revalidate = ctx.cacheControl?.revalidate ?? ctx.revalidate;
|
|
190
|
-
if (typeof revalidate === "number") {
|
|
191
|
-
effectiveRevalidate = revalidate;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (data && "revalidate" in data && typeof data.revalidate === "number") {
|
|
195
|
-
effectiveRevalidate = data.revalidate;
|
|
196
|
-
}
|
|
197
|
-
if (effectiveRevalidate === 0)
|
|
198
|
-
return Promise.resolve();
|
|
199
|
-
const revalidateAt = typeof effectiveRevalidate === "number" && effectiveRevalidate > 0
|
|
200
|
-
? Date.now() + effectiveRevalidate * 1000
|
|
201
|
-
: null;
|
|
202
|
-
// Prepare entry — convert ArrayBuffers to base64 for JSON storage
|
|
203
|
-
const serializable = data ? serializeForJSON(data) : null;
|
|
204
|
-
const entry = {
|
|
205
|
-
value: serializable,
|
|
206
|
-
tags,
|
|
207
|
-
lastModified: Date.now(),
|
|
208
|
-
revalidateAt,
|
|
209
|
-
};
|
|
210
|
-
// KV TTL is decoupled from the revalidation period.
|
|
211
|
-
//
|
|
212
|
-
// Staleness (when to trigger background regen) is tracked by `revalidateAt`
|
|
213
|
-
// in the stored JSON — not by KV eviction. KV eviction is purely a storage
|
|
214
|
-
// hygiene mechanism and must never be the reason a stale entry disappears.
|
|
215
|
-
//
|
|
216
|
-
// If KV TTL were tied to the revalidate window (e.g. 10x), a page with
|
|
217
|
-
// revalidate=5 would be evicted after ~50 seconds of no traffic, causing the
|
|
218
|
-
// next request to block on a fresh render instead of serving stale content.
|
|
219
|
-
//
|
|
220
|
-
// Fix: always keep entries for 30 days regardless of revalidate frequency.
|
|
221
|
-
// Background regen overwrites the key with a fresh entry + new revalidateAt,
|
|
222
|
-
// so active pages always have something to serve. Entries only disappear after
|
|
223
|
-
// 30 days of zero traffic, or when explicitly deleted via tag invalidation.
|
|
224
|
-
const expirationTtl = revalidateAt !== null ? this.ttlSeconds : undefined;
|
|
225
|
-
return this._put(this.prefix + ENTRY_PREFIX + key, JSON.stringify(entry), {
|
|
226
|
-
expirationTtl,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
async revalidateTag(tags, _durations) {
|
|
230
|
-
const tagList = Array.isArray(tags) ? tags : [tags];
|
|
231
|
-
const now = Date.now();
|
|
232
|
-
const validTags = tagList.filter((t) => validateTag(t) !== null);
|
|
233
|
-
// Store invalidation timestamp for each tag
|
|
234
|
-
// Use a long TTL (30 days) so recent invalidations are always found
|
|
235
|
-
await Promise.all(validTags.map((tag) => this.kv.put(this.prefix + TAG_PREFIX + tag, String(now), {
|
|
236
|
-
expirationTtl: 30 * 24 * 3600,
|
|
237
|
-
})));
|
|
238
|
-
// Update local tag cache immediately so invalidations are reflected
|
|
239
|
-
// without waiting for the TTL to expire
|
|
240
|
-
for (const tag of validTags) {
|
|
241
|
-
this._tagCache.set(tag, { timestamp: now, fetchedAt: now });
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Clear the in-memory tag cache for this KVCacheHandler instance.
|
|
246
|
-
*
|
|
247
|
-
* Note: KVCacheHandler instances are typically reused across multiple
|
|
248
|
-
* requests in a Cloudflare Worker. The `_tagCache` is intentionally
|
|
249
|
-
* cross-request — it reduces redundant KV reads for recently-seen tags
|
|
250
|
-
* across all requests hitting the same isolate, bounded by `tagCacheTtlMs`
|
|
251
|
-
* (default 5s). vinext does NOT call this method per request.
|
|
252
|
-
*
|
|
253
|
-
* This is an opt-in escape hatch for callers that need stricter isolation
|
|
254
|
-
* (e.g., tests, or environments with custom lifecycle management).
|
|
255
|
-
* Callers that require per-request isolation should either construct a
|
|
256
|
-
* fresh KVCacheHandler per request or invoke this method explicitly.
|
|
257
|
-
*/
|
|
258
|
-
resetRequestCache() {
|
|
259
|
-
this._tagCache.clear();
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Fire a KV delete in the background.
|
|
263
|
-
* Prefers the per-request ExecutionContext from ALS (set by
|
|
264
|
-
* runWithExecutionContext in the worker entry) so that background KV
|
|
265
|
-
* operations are registered with the correct request's waitUntil().
|
|
266
|
-
* Falls back to the constructor-provided ctx for callers that set it
|
|
267
|
-
* explicitly, and to fire-and-forget when neither is available (Node.js dev).
|
|
268
|
-
*/
|
|
269
|
-
_deleteInBackground(kvKey) {
|
|
270
|
-
const promise = this.kv.delete(kvKey);
|
|
271
|
-
const ctx = getRequestExecutionContext() ?? this.ctx;
|
|
272
|
-
if (ctx) {
|
|
273
|
-
ctx.waitUntil(promise);
|
|
274
|
-
}
|
|
275
|
-
// else: fire-and-forget on Node.js
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Execute a KV put and return the promise so callers can await completion.
|
|
279
|
-
* Also registers with ctx.waitUntil() so the Workers runtime keeps the
|
|
280
|
-
* isolate alive even if the caller does not await the returned promise.
|
|
281
|
-
*/
|
|
282
|
-
_put(kvKey, value, options) {
|
|
283
|
-
const promise = this.kv.put(kvKey, value, options);
|
|
284
|
-
const ctx = getRequestExecutionContext() ?? this.ctx;
|
|
285
|
-
if (ctx) {
|
|
286
|
-
ctx.waitUntil(promise);
|
|
287
|
-
}
|
|
288
|
-
return promise;
|
|
289
|
-
}
|
|
53
|
+
/**
|
|
54
|
+
* Segment-aware path prefix check. Returns true if `path` is equal to
|
|
55
|
+
* `prefix` or is a child route (next char after prefix is `/`).
|
|
56
|
+
* Prevents `/dashboard` from matching `/dashboard-admin`.
|
|
57
|
+
*/
|
|
58
|
+
function isPathChildOf(path, prefix) {
|
|
59
|
+
if (prefix === "/") return path.startsWith("/");
|
|
60
|
+
if (path === prefix) return true;
|
|
61
|
+
return path.startsWith(prefix + "/");
|
|
290
62
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
63
|
+
var KVCacheHandler = class {
|
|
64
|
+
kv;
|
|
65
|
+
prefix;
|
|
66
|
+
ctx;
|
|
67
|
+
ttlSeconds;
|
|
68
|
+
/** Local in-memory cache for tag invalidation timestamps. Avoids redundant KV reads. */
|
|
69
|
+
_tagCache = /* @__PURE__ */ new Map();
|
|
70
|
+
/** TTL (ms) for local tag cache entries. After this, re-fetch from KV. */
|
|
71
|
+
_tagCacheTtl;
|
|
72
|
+
constructor(kvNamespace, options) {
|
|
73
|
+
this.kv = kvNamespace;
|
|
74
|
+
this.prefix = options?.appPrefix ? `${options.appPrefix}:` : "";
|
|
75
|
+
this.ctx = options?.ctx;
|
|
76
|
+
this.ttlSeconds = options?.ttlSeconds ?? 720 * 3600;
|
|
77
|
+
this._tagCacheTtl = options?.tagCacheTtlMs ?? 5e3;
|
|
78
|
+
}
|
|
79
|
+
async get(key, _ctx) {
|
|
80
|
+
const kvKey = this.prefix + ENTRY_PREFIX + key;
|
|
81
|
+
const raw = await this.kv.get(kvKey);
|
|
82
|
+
if (!raw) return null;
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = JSON.parse(raw);
|
|
86
|
+
} catch {
|
|
87
|
+
this._deleteInBackground(kvKey);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const entry = validateCacheEntry(parsed);
|
|
91
|
+
if (!entry) {
|
|
92
|
+
console.error("[vinext] Invalid cache entry shape for key:", key);
|
|
93
|
+
this._deleteInBackground(kvKey);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
let restoredValue = null;
|
|
97
|
+
if (entry.value) {
|
|
98
|
+
restoredValue = restoreArrayBuffers(entry.value);
|
|
99
|
+
if (!restoredValue) {
|
|
100
|
+
this._deleteInBackground(kvKey);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (entry.tags.length > 0) {
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
const uncachedTags = [];
|
|
107
|
+
for (const tag of entry.tags) {
|
|
108
|
+
const cached = this._tagCache.get(tag);
|
|
109
|
+
if (cached && now - cached.fetchedAt < this._tagCacheTtl) {
|
|
110
|
+
if (Number.isNaN(cached.timestamp) || cached.timestamp >= entry.lastModified) {
|
|
111
|
+
this._deleteInBackground(kvKey);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
if (cached) this._tagCache.delete(tag);
|
|
116
|
+
uncachedTags.push(tag);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (uncachedTags.length > 0) {
|
|
120
|
+
const tagResults = await Promise.all(uncachedTags.map((tag) => this.kv.get(this.prefix + TAG_PREFIX + tag)));
|
|
121
|
+
for (let i = 0; i < uncachedTags.length; i++) {
|
|
122
|
+
const tagTime = tagResults[i];
|
|
123
|
+
const tagTimestamp = tagTime ? Number(tagTime) : 0;
|
|
124
|
+
this._tagCache.set(uncachedTags[i], {
|
|
125
|
+
timestamp: tagTimestamp,
|
|
126
|
+
fetchedAt: now
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
for (const tag of uncachedTags) {
|
|
130
|
+
const cached = this._tagCache.get(tag);
|
|
131
|
+
if (cached.timestamp !== 0) {
|
|
132
|
+
if (Number.isNaN(cached.timestamp) || cached.timestamp >= entry.lastModified) {
|
|
133
|
+
this._deleteInBackground(kvKey);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (entry.revalidateAt !== null && Date.now() > entry.revalidateAt) return {
|
|
141
|
+
lastModified: entry.lastModified,
|
|
142
|
+
value: restoredValue,
|
|
143
|
+
cacheState: "stale"
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
lastModified: entry.lastModified,
|
|
147
|
+
value: restoredValue
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
set(key, data, ctx) {
|
|
151
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
152
|
+
if (data && "tags" in data && Array.isArray(data.tags)) for (const t of data.tags) {
|
|
153
|
+
const validated = validateTag(t);
|
|
154
|
+
if (validated) tagSet.add(validated);
|
|
155
|
+
}
|
|
156
|
+
if (ctx && "tags" in ctx && Array.isArray(ctx.tags)) for (const t of ctx.tags) {
|
|
157
|
+
const validated = validateTag(t);
|
|
158
|
+
if (validated) tagSet.add(validated);
|
|
159
|
+
}
|
|
160
|
+
const tags = [...tagSet];
|
|
161
|
+
let effectiveRevalidate;
|
|
162
|
+
if (ctx) {
|
|
163
|
+
const revalidate = ctx.cacheControl?.revalidate ?? ctx.revalidate;
|
|
164
|
+
if (typeof revalidate === "number") effectiveRevalidate = revalidate;
|
|
165
|
+
}
|
|
166
|
+
if (data && "revalidate" in data && typeof data.revalidate === "number") effectiveRevalidate = data.revalidate;
|
|
167
|
+
if (effectiveRevalidate === 0) return Promise.resolve();
|
|
168
|
+
const revalidateAt = typeof effectiveRevalidate === "number" && effectiveRevalidate > 0 ? Date.now() + effectiveRevalidate * 1e3 : null;
|
|
169
|
+
const entry = {
|
|
170
|
+
value: data ? serializeForJSON(data) : null,
|
|
171
|
+
tags,
|
|
172
|
+
lastModified: Date.now(),
|
|
173
|
+
revalidateAt
|
|
174
|
+
};
|
|
175
|
+
const expirationTtl = revalidateAt !== null ? this.ttlSeconds : void 0;
|
|
176
|
+
const metadata = JSON.stringify({ tags }).length <= 1024 ? { tags } : void 0;
|
|
177
|
+
return this._put(this.prefix + ENTRY_PREFIX + key, JSON.stringify(entry), {
|
|
178
|
+
expirationTtl,
|
|
179
|
+
metadata
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async revalidateTag(tags, _durations) {
|
|
183
|
+
const tagList = Array.isArray(tags) ? tags : [tags];
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
const validTags = tagList.filter((t) => validateTag(t) !== null);
|
|
186
|
+
await Promise.all(validTags.map((tag) => this.kv.put(this.prefix + TAG_PREFIX + tag, String(now), { expirationTtl: 720 * 3600 })));
|
|
187
|
+
for (const tag of validTags) this._tagCache.set(tag, {
|
|
188
|
+
timestamp: now,
|
|
189
|
+
fetchedAt: now
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Invalidate all cache entries whose path tags fall under `pathPrefix`.
|
|
194
|
+
*
|
|
195
|
+
* Uses KV list metadata to discover tags without fetching entry values —
|
|
196
|
+
* entries written by `set()` store their tags in KV metadata, so
|
|
197
|
+
* `kv.list()` returns them inline with each key. This makes prefix
|
|
198
|
+
* invalidation O(list_pages) instead of O(entries × get).
|
|
199
|
+
*
|
|
200
|
+
* Entries written before metadata was added (no metadata.tags) are
|
|
201
|
+
* gracefully skipped — they'll be picked up on next `set()` which
|
|
202
|
+
* writes metadata.
|
|
203
|
+
*
|
|
204
|
+
* When present, this method fully replaces the `revalidateTag` call
|
|
205
|
+
* path in `revalidatePath()` — implementors own all path-based tag
|
|
206
|
+
* handling.
|
|
207
|
+
*/
|
|
208
|
+
async revalidateByPathPrefix(pathPrefix) {
|
|
209
|
+
const tagsToInvalidate = /* @__PURE__ */ new Set();
|
|
210
|
+
let cursor;
|
|
211
|
+
const listPrefix = this.prefix + ENTRY_PREFIX;
|
|
212
|
+
do {
|
|
213
|
+
const page = await this.kv.list({
|
|
214
|
+
prefix: listPrefix,
|
|
215
|
+
cursor
|
|
216
|
+
});
|
|
217
|
+
for (const key of page.keys) {
|
|
218
|
+
const tags = key.metadata?.tags;
|
|
219
|
+
if (!Array.isArray(tags)) continue;
|
|
220
|
+
for (const tag of tags) {
|
|
221
|
+
if (typeof tag !== "string") continue;
|
|
222
|
+
const rawPath = tag.startsWith(PATH_TAG_PREFIX) ? tag.slice(5) : tag;
|
|
223
|
+
if (rawPath.startsWith("/") && isPathChildOf(rawPath, pathPrefix)) tagsToInvalidate.add(tag);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
cursor = page.list_complete ? void 0 : page.cursor;
|
|
227
|
+
} while (cursor);
|
|
228
|
+
if (tagsToInvalidate.size > 0) await this.revalidateTag([...tagsToInvalidate]);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Clear the in-memory tag cache for this KVCacheHandler instance.
|
|
232
|
+
*
|
|
233
|
+
* Note: KVCacheHandler instances are typically reused across multiple
|
|
234
|
+
* requests in a Cloudflare Worker. The `_tagCache` is intentionally
|
|
235
|
+
* cross-request — it reduces redundant KV reads for recently-seen tags
|
|
236
|
+
* across all requests hitting the same isolate, bounded by `tagCacheTtlMs`
|
|
237
|
+
* (default 5s). vinext does NOT call this method per request.
|
|
238
|
+
*
|
|
239
|
+
* This is an opt-in escape hatch for callers that need stricter isolation
|
|
240
|
+
* (e.g., tests, or environments with custom lifecycle management).
|
|
241
|
+
* Callers that require per-request isolation should either construct a
|
|
242
|
+
* fresh KVCacheHandler per request or invoke this method explicitly.
|
|
243
|
+
*/
|
|
244
|
+
resetRequestCache() {
|
|
245
|
+
this._tagCache.clear();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Fire a KV delete in the background.
|
|
249
|
+
* Prefers the per-request ExecutionContext from ALS (set by
|
|
250
|
+
* runWithExecutionContext in the worker entry) so that background KV
|
|
251
|
+
* operations are registered with the correct request's waitUntil().
|
|
252
|
+
* Falls back to the constructor-provided ctx for callers that set it
|
|
253
|
+
* explicitly, and to fire-and-forget when neither is available (Node.js dev).
|
|
254
|
+
*/
|
|
255
|
+
_deleteInBackground(kvKey) {
|
|
256
|
+
const promise = this.kv.delete(kvKey);
|
|
257
|
+
const ctx = getRequestExecutionContext() ?? this.ctx;
|
|
258
|
+
if (ctx) ctx.waitUntil(promise);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Execute a KV put and return the promise so callers can await completion.
|
|
262
|
+
* Also registers with ctx.waitUntil() so the Workers runtime keeps the
|
|
263
|
+
* isolate alive even if the caller does not await the returned promise.
|
|
264
|
+
*/
|
|
265
|
+
_put(kvKey, value, options) {
|
|
266
|
+
const promise = this.kv.put(kvKey, value, options);
|
|
267
|
+
const ctx = getRequestExecutionContext() ?? this.ctx;
|
|
268
|
+
if (ctx) ctx.waitUntil(promise);
|
|
269
|
+
return promise;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const VALID_KINDS = new Set([
|
|
273
|
+
"FETCH",
|
|
274
|
+
"APP_PAGE",
|
|
275
|
+
"PAGES",
|
|
276
|
+
"APP_ROUTE",
|
|
277
|
+
"REDIRECT",
|
|
278
|
+
"IMAGE"
|
|
279
|
+
]);
|
|
295
280
|
/**
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
281
|
+
* Validate that a parsed JSON value has the expected KVCacheEntry shape.
|
|
282
|
+
* Returns the validated entry or null if the shape is invalid.
|
|
283
|
+
*/
|
|
299
284
|
function validateCacheEntry(raw) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (obj.value !== null) {
|
|
312
|
-
if (!obj.value || typeof obj.value !== "object")
|
|
313
|
-
return null;
|
|
314
|
-
const value = obj.value;
|
|
315
|
-
if (typeof value.kind !== "string" || !VALID_KINDS.has(value.kind))
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
return raw;
|
|
285
|
+
if (!raw || typeof raw !== "object") return null;
|
|
286
|
+
const obj = raw;
|
|
287
|
+
if (typeof obj.lastModified !== "number") return null;
|
|
288
|
+
if (!Array.isArray(obj.tags)) return null;
|
|
289
|
+
if (obj.revalidateAt !== null && typeof obj.revalidateAt !== "number") return null;
|
|
290
|
+
if (obj.value !== null) {
|
|
291
|
+
if (!obj.value || typeof obj.value !== "object") return null;
|
|
292
|
+
const value = obj.value;
|
|
293
|
+
if (typeof value.kind !== "string" || !VALID_KINDS.has(value.kind)) return null;
|
|
294
|
+
}
|
|
295
|
+
return raw;
|
|
319
296
|
}
|
|
320
|
-
// ---------------------------------------------------------------------------
|
|
321
|
-
// ArrayBuffer serialization helpers
|
|
322
|
-
// ---------------------------------------------------------------------------
|
|
323
297
|
/**
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
298
|
+
* Deep-clone a cache value, converting ArrayBuffer fields to base64 strings
|
|
299
|
+
* so the entire structure can be JSON.stringify'd for KV storage.
|
|
300
|
+
*/
|
|
327
301
|
function serializeForJSON(value) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
...value,
|
|
343
|
-
buffer: arrayBufferToBase64(value.buffer),
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
return value;
|
|
302
|
+
if (value.kind === "APP_PAGE") return {
|
|
303
|
+
...value,
|
|
304
|
+
rscData: value.rscData ? arrayBufferToBase64(value.rscData) : void 0
|
|
305
|
+
};
|
|
306
|
+
if (value.kind === "APP_ROUTE") return {
|
|
307
|
+
...value,
|
|
308
|
+
body: arrayBufferToBase64(value.body)
|
|
309
|
+
};
|
|
310
|
+
if (value.kind === "IMAGE") return {
|
|
311
|
+
...value,
|
|
312
|
+
buffer: arrayBufferToBase64(value.buffer)
|
|
313
|
+
};
|
|
314
|
+
return value;
|
|
347
315
|
}
|
|
348
316
|
/**
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
317
|
+
* Restore base64 strings back to ArrayBuffers after JSON.parse.
|
|
318
|
+
* Returns the restored `IncrementalCacheValue`, or `null` if any base64
|
|
319
|
+
* decode fails (corrupted entry).
|
|
320
|
+
*/
|
|
353
321
|
function restoreArrayBuffers(value) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
322
|
+
if (value.kind === "APP_PAGE") {
|
|
323
|
+
if (typeof value.rscData === "string") {
|
|
324
|
+
const decoded = safeBase64ToArrayBuffer(value.rscData);
|
|
325
|
+
if (!decoded) return null;
|
|
326
|
+
return {
|
|
327
|
+
...value,
|
|
328
|
+
rscData: decoded
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return value;
|
|
332
|
+
}
|
|
333
|
+
if (value.kind === "APP_ROUTE") {
|
|
334
|
+
if (typeof value.body === "string") {
|
|
335
|
+
const decoded = safeBase64ToArrayBuffer(value.body);
|
|
336
|
+
if (!decoded) return null;
|
|
337
|
+
return {
|
|
338
|
+
...value,
|
|
339
|
+
body: decoded
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return value;
|
|
343
|
+
}
|
|
344
|
+
if (value.kind === "IMAGE") {
|
|
345
|
+
if (typeof value.buffer === "string") {
|
|
346
|
+
const decoded = safeBase64ToArrayBuffer(value.buffer);
|
|
347
|
+
if (!decoded) return null;
|
|
348
|
+
return {
|
|
349
|
+
...value,
|
|
350
|
+
buffer: decoded
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
return value;
|
|
382
356
|
}
|
|
383
357
|
function arrayBufferToBase64(buffer) {
|
|
384
|
-
|
|
358
|
+
return Buffer.from(buffer).toString("base64");
|
|
385
359
|
}
|
|
386
360
|
/**
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
361
|
+
* Decode a base64 string to an ArrayBuffer.
|
|
362
|
+
* Validates the input against the base64 alphabet before decoding,
|
|
363
|
+
* since Buffer.from(str, "base64") silently ignores invalid characters.
|
|
364
|
+
*/
|
|
391
365
|
function base64ToArrayBuffer(base64) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const buf = Buffer.from(base64, "base64");
|
|
396
|
-
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
366
|
+
if (!BASE64_RE.test(base64) || base64.length % 4 !== 0) throw new Error("Invalid base64 string");
|
|
367
|
+
const buf = Buffer.from(base64, "base64");
|
|
368
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
397
369
|
}
|
|
398
370
|
/**
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
371
|
+
* Safely decode base64 to ArrayBuffer. Returns null on invalid input
|
|
372
|
+
* instead of throwing.
|
|
373
|
+
*/
|
|
402
374
|
function safeBase64ToArrayBuffer(base64) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
375
|
+
try {
|
|
376
|
+
return base64ToArrayBuffer(base64);
|
|
377
|
+
} catch {
|
|
378
|
+
console.error("[vinext] Invalid base64 in cache entry");
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
410
381
|
}
|
|
382
|
+
//#endregion
|
|
383
|
+
export { KVCacheHandler };
|
|
384
|
+
|
|
411
385
|
//# sourceMappingURL=kv-cache-handler.js.map
|