@nifrajs/web 3.3.0 → 4.0.0
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/dist/backend-only.d.ts +15 -0
- package/dist/backend-only.d.ts.map +1 -0
- package/dist/backend-only.js +16 -0
- package/dist/backend-only.js.map +1 -0
- package/dist/boundary.d.ts +5 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +11 -2
- package/dist/boundary.js.map +1 -1
- package/dist/build-plan.d.ts +35 -6
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +89 -35
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +18 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +97 -17
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +72 -23
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +411 -98
- package/dist/build.js.map +1 -1
- package/dist/bun-dev-entry.d.ts +1 -1
- package/dist/bun-dev-entry.js +1 -1
- package/dist/cdn.d.ts +74 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +132 -0
- package/dist/cdn.js.map +1 -0
- package/dist/client.d.ts +21 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +226 -15
- package/dist/client.js.map +1 -1
- package/dist/conformance.d.ts +4 -3
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +12 -3
- package/dist/conformance.js.map +1 -1
- package/dist/csp.d.ts +93 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +122 -0
- package/dist/csp.js.map +1 -0
- package/dist/css-contract.d.ts +21 -0
- package/dist/css-contract.d.ts.map +1 -0
- package/dist/css-contract.js +28 -0
- package/dist/css-contract.js.map +1 -0
- package/dist/dev-client.d.ts +76 -0
- package/dist/dev-client.d.ts.map +1 -0
- package/dist/dev-client.js +280 -0
- package/dist/dev-client.js.map +1 -0
- package/dist/dev-diagnostics.d.ts +26 -2
- package/dist/dev-diagnostics.d.ts.map +1 -1
- package/dist/dev-diagnostics.js +38 -7
- package/dist/dev-diagnostics.js.map +1 -1
- package/dist/dev-error.d.ts +4 -13
- package/dist/dev-error.d.ts.map +1 -1
- package/dist/dev-error.js +46 -4
- package/dist/dev-error.js.map +1 -1
- package/dist/dev-feed.d.ts +298 -0
- package/dist/dev-feed.d.ts.map +1 -0
- package/dist/dev-feed.js +797 -0
- package/dist/dev-feed.js.map +1 -0
- package/dist/dev-indicator.d.ts +38 -0
- package/dist/dev-indicator.d.ts.map +1 -0
- package/dist/dev-indicator.js +93 -0
- package/dist/dev-indicator.js.map +1 -0
- package/dist/dev-session.d.ts +77 -0
- package/dist/dev-session.d.ts.map +1 -0
- package/dist/dev-session.js +580 -0
- package/dist/dev-session.js.map +1 -0
- package/dist/dev-sourcemap.d.ts +27 -0
- package/dist/dev-sourcemap.d.ts.map +1 -0
- package/dist/dev-sourcemap.js +235 -0
- package/dist/dev-sourcemap.js.map +1 -0
- package/dist/dev-ssr-graph.d.ts.map +1 -1
- package/dist/dev-ssr-graph.js +13 -5
- package/dist/dev-ssr-graph.js.map +1 -1
- package/dist/dev.d.ts +15 -2
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +69 -44
- package/dist/dev.js.map +1 -1
- package/dist/diagnostic-catalog.d.ts +35 -0
- package/dist/diagnostic-catalog.d.ts.map +1 -0
- package/dist/diagnostic-catalog.js +203 -0
- package/dist/diagnostic-catalog.js.map +1 -0
- package/dist/diagnostic-prompt.d.ts +49 -0
- package/dist/diagnostic-prompt.d.ts.map +1 -0
- package/dist/diagnostic-prompt.js +303 -0
- package/dist/diagnostic-prompt.js.map +1 -0
- package/dist/diagnostic.d.ts +17 -23
- package/dist/diagnostic.d.ts.map +1 -1
- package/dist/diagnostic.js +31 -39
- package/dist/diagnostic.js.map +1 -1
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +4 -1
- package/dist/draft.js.map +1 -1
- package/dist/fn.d.ts +5 -0
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +13 -3
- package/dist/fn.js.map +1 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +4 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +15 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/cdn/cloudflare.d.ts +48 -0
- package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
- package/dist/internal/cdn/cloudflare.js +94 -0
- package/dist/internal/cdn/cloudflare.js.map +1 -0
- package/dist/internal/cdn/core.d.ts +91 -0
- package/dist/internal/cdn/core.d.ts.map +1 -0
- package/dist/internal/cdn/core.js +220 -0
- package/dist/internal/cdn/core.js.map +1 -0
- package/dist/internal/cdn/fastly.d.ts +17 -0
- package/dist/internal/cdn/fastly.d.ts.map +1 -0
- package/dist/internal/cdn/fastly.js +43 -0
- package/dist/internal/cdn/fastly.js.map +1 -0
- package/dist/internal/cdn/vercel.d.ts +31 -0
- package/dist/internal/cdn/vercel.d.ts.map +1 -0
- package/dist/internal/cdn/vercel.js +56 -0
- package/dist/internal/cdn/vercel.js.map +1 -0
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/codegen.d.ts +3 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +151 -30
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/dev-csp.d.ts +24 -0
- package/dist/internal/dev-csp.d.ts.map +1 -0
- package/dist/internal/dev-csp.js +160 -0
- package/dist/internal/dev-csp.js.map +1 -0
- package/dist/internal/dev-reserved.d.ts +25 -0
- package/dist/internal/dev-reserved.d.ts.map +1 -0
- package/dist/internal/dev-reserved.js +39 -0
- package/dist/internal/dev-reserved.js.map +1 -0
- package/dist/internal/html-spans.d.ts +10 -0
- package/dist/internal/html-spans.d.ts.map +1 -0
- package/dist/internal/html-spans.js +46 -0
- package/dist/internal/html-spans.js.map +1 -0
- package/dist/internal/loading-runtime.d.ts +44 -0
- package/dist/internal/loading-runtime.d.ts.map +1 -0
- package/dist/internal/loading-runtime.js +206 -0
- package/dist/internal/loading-runtime.js.map +1 -0
- package/dist/internal/matches-runtime.d.ts +8 -0
- package/dist/internal/matches-runtime.d.ts.map +1 -0
- package/dist/internal/matches-runtime.js +61 -0
- package/dist/internal/matches-runtime.js.map +1 -0
- package/dist/internal/mount-shadow.d.ts +36 -0
- package/dist/internal/mount-shadow.d.ts.map +1 -0
- package/dist/internal/mount-shadow.js +65 -0
- package/dist/internal/mount-shadow.js.map +1 -0
- package/dist/internal/node-builtins.d.ts +3 -0
- package/dist/internal/node-builtins.d.ts.map +1 -0
- package/dist/internal/node-builtins.js +79 -0
- package/dist/internal/node-builtins.js.map +1 -0
- package/dist/internal/output-guard.d.ts +65 -0
- package/dist/internal/output-guard.d.ts.map +1 -0
- package/dist/internal/output-guard.js +630 -0
- package/dist/internal/output-guard.js.map +1 -0
- package/dist/internal/page-execution.d.ts +69 -0
- package/dist/internal/page-execution.d.ts.map +1 -0
- package/dist/internal/page-execution.js +1021 -0
- package/dist/internal/page-execution.js.map +1 -0
- package/dist/internal/parity.d.ts +21 -0
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +104 -22
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +12 -0
- package/dist/internal/private-env.d.ts.map +1 -0
- package/dist/internal/private-env.js +335 -0
- package/dist/internal/private-env.js.map +1 -0
- package/dist/internal/public-url.d.ts +3 -0
- package/dist/internal/public-url.d.ts.map +1 -0
- package/dist/internal/public-url.js +18 -0
- package/dist/internal/public-url.js.map +1 -0
- package/dist/internal/render-document.d.ts +37 -21
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +104 -65
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/request-url.d.ts +3 -0
- package/dist/internal/request-url.d.ts.map +1 -0
- package/dist/internal/request-url.js +14 -0
- package/dist/internal/request-url.js.map +1 -0
- package/dist/internal/response-controls.d.ts +75 -0
- package/dist/internal/response-controls.d.ts.map +1 -0
- package/dist/internal/response-controls.js +256 -0
- package/dist/internal/response-controls.js.map +1 -0
- package/dist/internal/runtime-contract.d.ts +11 -16
- package/dist/internal/runtime-contract.d.ts.map +1 -1
- package/dist/internal/runtime-contract.js.map +1 -1
- package/dist/internal/secret-scan.d.ts +104 -0
- package/dist/internal/secret-scan.d.ts.map +1 -0
- package/dist/internal/secret-scan.js +485 -0
- package/dist/internal/secret-scan.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +6 -29
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +6 -66
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/source-map.d.ts +47 -0
- package/dist/internal/source-map.d.ts.map +1 -0
- package/dist/internal/source-map.js +225 -0
- package/dist/internal/source-map.js.map +1 -0
- package/dist/internal/target-compat.d.ts +27 -0
- package/dist/internal/target-compat.d.ts.map +1 -0
- package/dist/internal/target-compat.js +77 -0
- package/dist/internal/target-compat.js.map +1 -0
- package/dist/internal/timing-safe-equal.d.ts +6 -0
- package/dist/internal/timing-safe-equal.d.ts.map +1 -1
- package/dist/internal/timing-safe-equal.js +10 -0
- package/dist/internal/timing-safe-equal.js.map +1 -1
- package/dist/internal/vitals-runtime.d.ts +18 -0
- package/dist/internal/vitals-runtime.d.ts.map +1 -0
- package/dist/internal/vitals-runtime.js +38 -0
- package/dist/internal/vitals-runtime.js.map +1 -0
- package/dist/internal/web-app.d.ts +56 -8
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +215 -772
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/zone-graph.d.ts +89 -0
- package/dist/internal/zone-graph.d.ts.map +1 -0
- package/dist/internal/zone-graph.js +492 -0
- package/dist/internal/zone-graph.js.map +1 -0
- package/dist/isr.d.ts +83 -6
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +301 -39
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +3 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +37 -9
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +221 -14
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +386 -9
- package/dist/manifest.js.map +1 -1
- package/dist/module-graph.d.ts +26 -5
- package/dist/module-graph.d.ts.map +1 -1
- package/dist/module-graph.js +17 -7
- package/dist/module-graph.js.map +1 -1
- package/dist/navigation.d.ts +17 -3
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +11 -5
- package/dist/navigation.js.map +1 -1
- package/dist/nonce.d.ts +47 -0
- package/dist/nonce.d.ts.map +1 -0
- package/dist/nonce.js +63 -0
- package/dist/nonce.js.map +1 -0
- package/dist/plugins/css-modules.d.ts.map +1 -1
- package/dist/plugins/css-modules.js +4 -3
- package/dist/plugins/css-modules.js.map +1 -1
- package/dist/plugins/kit.d.ts +9 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +18 -0
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/svg.d.ts +22 -3
- package/dist/plugins/svg.d.ts.map +1 -1
- package/dist/plugins/svg.js +112 -1
- package/dist/plugins/svg.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +107 -3
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +378 -17
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts +61 -0
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
- package/dist/plugins/vite-zone-guard.js +208 -0
- package/dist/plugins/vite-zone-guard.js.map +1 -0
- package/dist/plugins/zone-guard.d.ts +27 -0
- package/dist/plugins/zone-guard.d.ts.map +1 -0
- package/dist/plugins/zone-guard.js +72 -0
- package/dist/plugins/zone-guard.js.map +1 -0
- package/dist/prerender.d.ts +10 -0
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +35 -3
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +9 -0
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +131 -48
- package/dist/public-dir.js.map +1 -1
- package/dist/pwa-manifest.d.ts +68 -0
- package/dist/pwa-manifest.d.ts.map +1 -0
- package/dist/pwa-manifest.js +178 -0
- package/dist/pwa-manifest.js.map +1 -0
- package/dist/query.js +4 -4
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +42 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +6 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-manifest.d.ts +3 -0
- package/dist/route-manifest.d.ts.map +1 -1
- package/dist/route-manifest.js +26 -3
- package/dist/route-manifest.js.map +1 -1
- package/dist/route-types.d.ts +24 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +195 -0
- package/dist/route-types.js.map +1 -0
- package/dist/router.d.ts +28 -6
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +129 -40
- package/dist/router.js.map +1 -1
- package/dist/trusted-html.d.ts +34 -0
- package/dist/trusted-html.d.ts.map +1 -0
- package/dist/trusted-html.js +35 -0
- package/dist/trusted-html.js.map +1 -0
- package/dist/vitals.d.ts +33 -0
- package/dist/vitals.d.ts.map +1 -0
- package/dist/vitals.js +16 -0
- package/dist/vitals.js.map +1 -0
- package/dist/vite.d.ts +16 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +187 -36
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +70 -0
- package/dist/zones.d.ts.map +1 -0
- package/dist/zones.js +365 -0
- package/dist/zones.js.map +1 -0
- package/package.json +67 -14
- package/src/backend-only.ts +16 -0
- package/src/boundary.ts +18 -5
- package/src/build-plan.ts +113 -36
- package/src/build-vite.ts +124 -15
- package/src/build.ts +535 -102
- package/src/bun-dev-entry.ts +1 -1
- package/src/cdn.ts +216 -0
- package/src/client.ts +251 -18
- package/src/conformance.ts +16 -3
- package/src/csp.ts +194 -0
- package/src/css-contract.ts +44 -0
- package/src/dev-client.ts +349 -0
- package/src/dev-diagnostics.ts +60 -7
- package/src/dev-error.ts +49 -5
- package/src/dev-feed.ts +1140 -0
- package/src/dev-indicator.ts +113 -0
- package/src/dev-session.ts +729 -0
- package/src/dev-sourcemap.ts +281 -0
- package/src/dev-ssr-graph.ts +16 -5
- package/src/dev.ts +86 -43
- package/src/diagnostic-catalog.ts +243 -0
- package/src/diagnostic-prompt.ts +355 -0
- package/src/diagnostic.ts +63 -66
- package/src/draft.ts +4 -1
- package/src/fn.ts +19 -3
- package/src/fs.ts +13 -2
- package/src/index.ts +57 -10
- package/src/internal/cdn/cloudflare.ts +146 -0
- package/src/internal/cdn/core.ts +316 -0
- package/src/internal/cdn/fastly.ts +62 -0
- package/src/internal/cdn/vercel.ts +95 -0
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/codegen.ts +180 -29
- package/src/internal/dev-csp.ts +176 -0
- package/src/internal/dev-reserved.ts +52 -0
- package/src/internal/html-spans.ts +50 -0
- package/src/internal/loading-runtime.ts +244 -0
- package/src/internal/matches-runtime.ts +62 -0
- package/src/internal/mount-shadow.ts +88 -0
- package/src/internal/node-builtins.ts +79 -0
- package/src/internal/output-guard.ts +701 -0
- package/src/internal/page-execution.ts +1371 -0
- package/src/internal/parity.ts +150 -36
- package/src/internal/private-env.ts +334 -0
- package/src/internal/public-url.ts +22 -0
- package/src/internal/render-document.ts +153 -73
- package/src/internal/request-url.ts +14 -0
- package/src/internal/response-controls.ts +284 -0
- package/src/internal/runtime-contract.ts +10 -15
- package/src/internal/secret-scan.ts +623 -0
- package/src/internal/server-boundary.ts +6 -69
- package/src/internal/source-map.ts +256 -0
- package/src/internal/target-compat.ts +92 -0
- package/src/internal/timing-safe-equal.ts +13 -0
- package/src/internal/vitals-runtime.ts +53 -0
- package/src/internal/web-app.ts +290 -879
- package/src/internal/zone-graph.ts +593 -0
- package/src/isr.ts +366 -39
- package/src/llms-txt.ts +44 -8
- package/src/manifest.ts +671 -17
- package/src/module-graph.ts +53 -9
- package/src/navigation.ts +29 -6
- package/src/nonce.ts +104 -0
- package/src/plugins/css-modules.ts +5 -3
- package/src/plugins/kit.ts +24 -0
- package/src/plugins/svg.ts +135 -4
- package/src/plugins/vite-leak-guard.ts +529 -16
- package/src/plugins/vite-zone-guard.ts +263 -0
- package/src/plugins/zone-guard.ts +110 -0
- package/src/prerender.ts +49 -3
- package/src/public-dir.ts +135 -49
- package/src/pwa-manifest.ts +226 -0
- package/src/query.ts +4 -4
- package/src/render-seam.ts +46 -1
- package/src/route-manifest.ts +15 -3
- package/src/route-types.ts +264 -0
- package/src/router.ts +163 -63
- package/src/trusted-html.ts +44 -0
- package/src/vitals.ts +44 -0
- package/src/vite.ts +207 -36
- package/src/zones.ts +430 -0
- package/dist/plugins/vite-server-only.d.ts +0 -49
- package/dist/plugins/vite-server-only.d.ts.map +0 -1
- package/dist/plugins/vite-server-only.js +0 -57
- package/dist/plugins/vite-server-only.js.map +0 -1
- package/dist/server-only.d.ts +0 -21
- package/dist/server-only.d.ts.map +0 -1
- package/dist/server-only.js +0 -25
- package/dist/server-only.js.map +0 -1
- package/src/plugins/vite-server-only.ts +0 -75
- package/src/server-only.ts +0 -25
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider-neutral half of `@nifrajs/web/cdn`: the path tag every cached page carries, and the
|
|
3
|
+
* purge queue each provider runs its API calls through (coalescing, chunking, `Retry-After`).
|
|
4
|
+
*/
|
|
5
|
+
import type { CdnPurgeOutcome, CdnPurgeTarget, ISRPlatform } from "../../isr.ts"
|
|
6
|
+
|
|
7
|
+
/** What a cacheable page tells the CDN: its tags and how long the CDN may serve it. */
|
|
8
|
+
export interface CdnCacheInput {
|
|
9
|
+
readonly tags: readonly string[]
|
|
10
|
+
/** Seconds the CDN may serve the page as fresh. */
|
|
11
|
+
readonly maxAge: number
|
|
12
|
+
/** Seconds past `maxAge` the CDN may serve it while it refetches. */
|
|
13
|
+
readonly staleWhileRevalidate: number
|
|
14
|
+
/** Seconds past `maxAge` the CDN may serve it while the origin errors. */
|
|
15
|
+
readonly staleIfError?: number | undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A CDN nifra can tag pages for and purge. Build one with {@link defineCdnProvider}. */
|
|
19
|
+
export interface CdnProvider extends CdnPurgeTarget {
|
|
20
|
+
readonly name: string
|
|
21
|
+
/** Most tags one response may carry on this CDN. */
|
|
22
|
+
readonly maxResponseTags: number
|
|
23
|
+
/** Headers that let the CDN store a page under `input.tags` (consumed by the CDN, not the browser). */
|
|
24
|
+
cacheHeaders(input: CdnCacheInput): Readonly<Record<string, string>>
|
|
25
|
+
/** Headers that keep the CDN from storing a page, whatever its rules say. */
|
|
26
|
+
noStoreHeaders(): Readonly<Record<string, string>>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** One purge API call's result. A refusal is `retryable` when sending it again may succeed. */
|
|
30
|
+
export type PurgeAttempt =
|
|
31
|
+
| { readonly ok: true }
|
|
32
|
+
| {
|
|
33
|
+
readonly ok: false
|
|
34
|
+
readonly retryable: boolean
|
|
35
|
+
readonly reason: string
|
|
36
|
+
readonly retryAfterMs?: number | undefined
|
|
37
|
+
readonly rateLimited?: boolean | undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** What a provider file supplies; {@link defineCdnProvider} adds the queue. */
|
|
41
|
+
export interface CdnProviderDefinition {
|
|
42
|
+
readonly name: string
|
|
43
|
+
/** Most tags one purge API call may name. */
|
|
44
|
+
readonly tagsPerCall: number
|
|
45
|
+
readonly maxResponseTags: number
|
|
46
|
+
cacheHeaders(input: CdnCacheInput): Readonly<Record<string, string>>
|
|
47
|
+
noStoreHeaders(): Readonly<Record<string, string>>
|
|
48
|
+
purgeTags(tags: readonly string[]): Promise<PurgeAttempt>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A purge that did not go through, as reported to {@link PurgeQueueOptions.onError}. */
|
|
52
|
+
export interface CdnPurgeError {
|
|
53
|
+
readonly code: "NIFRA_CDN_PURGE_FAILED" | "NIFRA_CDN_RATE_LIMITED"
|
|
54
|
+
readonly provider: string
|
|
55
|
+
readonly reason: string
|
|
56
|
+
/** How many tags the refused call named (never the tags or the credentials). */
|
|
57
|
+
readonly tags: number
|
|
58
|
+
/** Whether the queue gave up on them. */
|
|
59
|
+
readonly final: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PurgeQueueOptions {
|
|
63
|
+
/** Tags arriving within this window go out in one call (ms). Default 250. */
|
|
64
|
+
readonly debounceMs?: number
|
|
65
|
+
/** Attempts per call before a retryable refusal is given up on. Default 5. */
|
|
66
|
+
readonly maxAttempts?: number
|
|
67
|
+
/** Longest wait between attempts, `Retry-After` included (ms). Default 60000. */
|
|
68
|
+
readonly maxBackoffMs?: number
|
|
69
|
+
/** Most tags waiting or retrying at once; a purge past it is refused as retryable. Default 5000. */
|
|
70
|
+
readonly maxQueuedTags?: number
|
|
71
|
+
/** Where a refused purge is reported. Default: one `console.error` line per refusal. */
|
|
72
|
+
readonly onError?: (error: CdnPurgeError) => void
|
|
73
|
+
/** Timer for the debounce and the backoff; tests pass their own. */
|
|
74
|
+
readonly sleep?: (ms: number) => Promise<void>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const BASE32 = "abcdefghijklmnopqrstuvwxyz234567"
|
|
78
|
+
|
|
79
|
+
function base32(bytes: Uint8Array): string {
|
|
80
|
+
let out = ""
|
|
81
|
+
let bits = 0
|
|
82
|
+
let value = 0
|
|
83
|
+
for (const byte of bytes) {
|
|
84
|
+
value = (value << 8) | byte
|
|
85
|
+
bits += 8
|
|
86
|
+
while (bits >= 5) {
|
|
87
|
+
out += BASE32[(value >>> (bits - 5)) & 31]
|
|
88
|
+
bits -= 5
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (bits > 0) out += BASE32[(value << (5 - bits)) & 31]
|
|
92
|
+
return out
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The tag every page a CDN stores carries for its own path, so a path purge works on CDNs that only
|
|
97
|
+
* purge by tag. It hashes the normalized pathname alone: the query and the host stay out, so one purge
|
|
98
|
+
* reaches every query variant and needs no knowledge of the public origin. Two hosts serving the same
|
|
99
|
+
* path share the tag, which can only make a purge reach more than asked, never serve a wrong page.
|
|
100
|
+
*/
|
|
101
|
+
export async function pathTag(path: string): Promise<string> {
|
|
102
|
+
const cut = path.search(/[?#]/)
|
|
103
|
+
// Joined as text, never resolved: `//host/x` stays a path, as the revalidation endpoint keys it.
|
|
104
|
+
const pathname = new URL(`http://n${cut === -1 ? path : path.slice(0, cut)}`).pathname
|
|
105
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(pathname))
|
|
106
|
+
return `nifra.p:${base32(new Uint8Array(digest)).slice(0, 26)}`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** `Retry-After` as milliseconds: delta seconds or an HTTP date; undefined when absent or unreadable. */
|
|
110
|
+
export function retryAfterMs(value: string | null, now = Date.now()): number | undefined {
|
|
111
|
+
if (value === null) return undefined
|
|
112
|
+
const trimmed = value.trim()
|
|
113
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1000
|
|
114
|
+
const at = Date.parse(trimmed)
|
|
115
|
+
return Number.isNaN(at) ? undefined : Math.max(0, at - now)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A purge API reply read the same way for every HTTP provider. */
|
|
119
|
+
export function attemptFromStatus(res: Response, reason: string): PurgeAttempt {
|
|
120
|
+
if (res.ok) return { ok: true }
|
|
121
|
+
const retryAfter = retryAfterMs(res.headers.get("retry-after"))
|
|
122
|
+
if (res.status === 429) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
retryable: true,
|
|
126
|
+
rateLimited: true,
|
|
127
|
+
reason: "rate_limited",
|
|
128
|
+
retryAfterMs: retryAfter,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (res.status >= 500) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
retryable: true,
|
|
135
|
+
reason: `${reason}_${res.status}`,
|
|
136
|
+
retryAfterMs: retryAfter,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { ok: false, retryable: false, reason: `${reason}_${res.status}` }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** `max-age=..., stale-while-revalidate=...[, stale-if-error=...]` for a CDN TTL header. */
|
|
143
|
+
export function cdnDirectives(input: CdnCacheInput): string {
|
|
144
|
+
const parts = [`max-age=${input.maxAge}`, `stale-while-revalidate=${input.staleWhileRevalidate}`]
|
|
145
|
+
if (input.staleIfError !== undefined) parts.push(`stale-if-error=${input.staleIfError}`)
|
|
146
|
+
return parts.join(", ")
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function positive(value: number | undefined, fallback: number, name: string): number {
|
|
150
|
+
if (value === undefined) return fallback
|
|
151
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
152
|
+
throw new RangeError(`[nifra/web/cdn] ${name} must be a finite non-negative number`)
|
|
153
|
+
}
|
|
154
|
+
return value
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface Waiter {
|
|
158
|
+
readonly tags: ReadonlySet<string>
|
|
159
|
+
readonly platform: ISRPlatform | undefined
|
|
160
|
+
readonly resolve: (outcome: CdnPurgeOutcome) => void
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type TagState =
|
|
164
|
+
| { state: "accepted" }
|
|
165
|
+
| { state: "queued" }
|
|
166
|
+
| { state: "failed"; retryable: boolean; reason: string }
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A {@link CdnProvider} from a provider's API calls, with the purge queue in front: tags arriving
|
|
170
|
+
* within `debounceMs` go out together, chunked to the provider's per-call limit and sent one call at
|
|
171
|
+
* a time; a 429 or 5xx is retried with capped exponential backoff that honors `Retry-After`. A
|
|
172
|
+
* `purge` resolves once its tags' first attempt is answered: `accepted`, `queued` (a retry is
|
|
173
|
+
* pending, kept alive with `platform.waitUntil` on edge runtimes), or `failed`.
|
|
174
|
+
*/
|
|
175
|
+
export function defineCdnProvider(
|
|
176
|
+
definition: CdnProviderDefinition,
|
|
177
|
+
options: PurgeQueueOptions = {},
|
|
178
|
+
): CdnProvider {
|
|
179
|
+
const debounceMs = positive(options.debounceMs, 250, "debounceMs")
|
|
180
|
+
const maxAttempts = Math.max(1, Math.floor(positive(options.maxAttempts, 5, "maxAttempts")))
|
|
181
|
+
const maxBackoffMs = positive(options.maxBackoffMs, 60_000, "maxBackoffMs")
|
|
182
|
+
const maxQueuedTags = positive(options.maxQueuedTags, 5000, "maxQueuedTags")
|
|
183
|
+
const sleep = options.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)))
|
|
184
|
+
const report =
|
|
185
|
+
options.onError ??
|
|
186
|
+
((error: CdnPurgeError) =>
|
|
187
|
+
console.error(
|
|
188
|
+
`[nifra/web/cdn] ${error.code}: ${error.provider} refused a purge of ${error.tags} tag(s) (${error.reason})${error.final ? "" : "; retrying"}`,
|
|
189
|
+
))
|
|
190
|
+
|
|
191
|
+
let pending = new Set<string>()
|
|
192
|
+
let waiters: Waiter[] = []
|
|
193
|
+
let scheduled = false
|
|
194
|
+
let retrying = 0
|
|
195
|
+
// Flushes run one after another, so the provider sees one purge call at a time from this queue.
|
|
196
|
+
let tail: Promise<void> = Promise.resolve()
|
|
197
|
+
|
|
198
|
+
const attempt = async (chunk: readonly string[]): Promise<PurgeAttempt> => {
|
|
199
|
+
try {
|
|
200
|
+
return await definition.purgeTags(chunk)
|
|
201
|
+
} catch {
|
|
202
|
+
// The provider's own error text can quote the request; only the kind leaves this function.
|
|
203
|
+
return { ok: false, retryable: true, reason: "network_error" }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const refusal = (
|
|
208
|
+
chunk: readonly string[],
|
|
209
|
+
result: PurgeAttempt & { ok: false },
|
|
210
|
+
final: boolean,
|
|
211
|
+
) => {
|
|
212
|
+
try {
|
|
213
|
+
report({
|
|
214
|
+
code: result.rateLimited === true ? "NIFRA_CDN_RATE_LIMITED" : "NIFRA_CDN_PURGE_FAILED",
|
|
215
|
+
provider: definition.name,
|
|
216
|
+
reason: result.reason,
|
|
217
|
+
tags: chunk.length,
|
|
218
|
+
final,
|
|
219
|
+
})
|
|
220
|
+
} catch {
|
|
221
|
+
// A reporter that throws must not strand the purge or the callers waiting on it.
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const retry = async (
|
|
226
|
+
chunk: readonly string[],
|
|
227
|
+
first: PurgeAttempt & { ok: false },
|
|
228
|
+
): Promise<void> => {
|
|
229
|
+
retrying += chunk.length
|
|
230
|
+
try {
|
|
231
|
+
let last = first
|
|
232
|
+
for (let n = 2; n <= maxAttempts; n++) {
|
|
233
|
+
const backoff = Math.min(maxBackoffMs, 1000 * 2 ** (n - 2))
|
|
234
|
+
await sleep(Math.min(maxBackoffMs, Math.max(backoff, last.retryAfterMs ?? 0)))
|
|
235
|
+
const result = await attempt(chunk)
|
|
236
|
+
if (result.ok) return
|
|
237
|
+
last = result
|
|
238
|
+
if (!result.retryable || n === maxAttempts) {
|
|
239
|
+
refusal(chunk, result, true)
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
refusal(chunk, result, false)
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
retrying -= chunk.length
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const flush = async (): Promise<void> => {
|
|
250
|
+
const batch = [...pending]
|
|
251
|
+
const settled = waiters
|
|
252
|
+
pending = new Set()
|
|
253
|
+
waiters = []
|
|
254
|
+
scheduled = false
|
|
255
|
+
const states = new Map<string, TagState>()
|
|
256
|
+
const background: Promise<void>[] = []
|
|
257
|
+
for (let i = 0; i < batch.length; i += definition.tagsPerCall) {
|
|
258
|
+
const chunk = batch.slice(i, i + definition.tagsPerCall)
|
|
259
|
+
const result = await attempt(chunk)
|
|
260
|
+
let state: TagState
|
|
261
|
+
if (result.ok) state = { state: "accepted" }
|
|
262
|
+
else if (result.retryable && maxAttempts > 1) {
|
|
263
|
+
refusal(chunk, result, false)
|
|
264
|
+
background.push(retry(chunk, result))
|
|
265
|
+
state = { state: "queued" }
|
|
266
|
+
} else {
|
|
267
|
+
refusal(chunk, result, true)
|
|
268
|
+
state = { state: "failed", retryable: result.retryable, reason: result.reason }
|
|
269
|
+
}
|
|
270
|
+
for (const tag of chunk) states.set(tag, state)
|
|
271
|
+
}
|
|
272
|
+
const retries = background.length === 0 ? undefined : Promise.all(background)
|
|
273
|
+
for (const waiter of settled) {
|
|
274
|
+
if (retries !== undefined) waiter.platform?.waitUntil?.(retries)
|
|
275
|
+
let failed: (TagState & { state: "failed" }) | undefined
|
|
276
|
+
let queued = false
|
|
277
|
+
for (const tag of waiter.tags) {
|
|
278
|
+
const state = states.get(tag)
|
|
279
|
+
if (state?.state === "failed") failed = failed?.retryable === false ? failed : state
|
|
280
|
+
else if (state?.state === "queued") queued = true
|
|
281
|
+
}
|
|
282
|
+
waiter.resolve(
|
|
283
|
+
failed !== undefined
|
|
284
|
+
? { cdn: "failed", retryable: failed.retryable, error: failed.reason }
|
|
285
|
+
: queued
|
|
286
|
+
? { cdn: "queued", retryable: true }
|
|
287
|
+
: { cdn: "accepted", retryable: false },
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
name: definition.name,
|
|
294
|
+
maxResponseTags: definition.maxResponseTags,
|
|
295
|
+
cacheHeaders: (input) => definition.cacheHeaders(input),
|
|
296
|
+
noStoreHeaders: () => definition.noStoreHeaders(),
|
|
297
|
+
async purge(target, platform) {
|
|
298
|
+
const tags = new Set(target.tags ?? [])
|
|
299
|
+
for (const path of target.paths ?? []) tags.add(await pathTag(path))
|
|
300
|
+
if (tags.size === 0) return { cdn: "accepted", retryable: false }
|
|
301
|
+
if (pending.size + retrying + tags.size > maxQueuedTags) {
|
|
302
|
+
return { cdn: "failed", retryable: true, error: "queue_full" }
|
|
303
|
+
}
|
|
304
|
+
const outcome = new Promise<CdnPurgeOutcome>((resolve) => {
|
|
305
|
+
for (const tag of tags) pending.add(tag)
|
|
306
|
+
waiters.push({ tags, platform, resolve })
|
|
307
|
+
})
|
|
308
|
+
if (!scheduled) {
|
|
309
|
+
scheduled = true
|
|
310
|
+
tail = tail.then(() => sleep(debounceMs)).then(flush)
|
|
311
|
+
platform?.waitUntil?.(tail)
|
|
312
|
+
}
|
|
313
|
+
return outcome
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fastly: `Surrogate-Key` (space-separated) and `Surrogate-Control`, purged in batches of up to 256
|
|
3
|
+
* keys. Soft purge by default, so a purged page is served stale while Fastly refetches it.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
attemptFromStatus,
|
|
7
|
+
type CdnProvider,
|
|
8
|
+
cdnDirectives,
|
|
9
|
+
defineCdnProvider,
|
|
10
|
+
type PurgeQueueOptions,
|
|
11
|
+
} from "./core.ts"
|
|
12
|
+
|
|
13
|
+
export interface FastlyOptions {
|
|
14
|
+
readonly serviceId: string
|
|
15
|
+
/** A Fastly API token with the `purge_select` scope. Keep it in an env var. */
|
|
16
|
+
readonly apiToken: string
|
|
17
|
+
/** Mark purged pages stale instead of dropping them. Default `true`. */
|
|
18
|
+
readonly soft?: boolean
|
|
19
|
+
readonly queue?: PurgeQueueOptions
|
|
20
|
+
/** The fetch purge calls go through; tests pass their own. */
|
|
21
|
+
readonly fetch?: typeof fetch
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function fastly(options: FastlyOptions): CdnProvider {
|
|
25
|
+
if (!/^[A-Za-z0-9]+$/.test(options.serviceId)) {
|
|
26
|
+
throw new TypeError("[nifra/web/cdn] fastly: serviceId must be the service's alphanumeric id")
|
|
27
|
+
}
|
|
28
|
+
if (typeof options.apiToken !== "string" || options.apiToken === "") {
|
|
29
|
+
throw new TypeError("[nifra/web/cdn] fastly: apiToken is empty (is the env var set?)")
|
|
30
|
+
}
|
|
31
|
+
const send = options.fetch ?? fetch
|
|
32
|
+
const endpoint = `https://api.fastly.com/service/${options.serviceId}/purge`
|
|
33
|
+
const soft = options.soft !== false
|
|
34
|
+
return defineCdnProvider(
|
|
35
|
+
{
|
|
36
|
+
name: "fastly",
|
|
37
|
+
tagsPerCall: 256,
|
|
38
|
+
maxResponseTags: 1000,
|
|
39
|
+
cacheHeaders: (input) => ({
|
|
40
|
+
"surrogate-key": input.tags.join(" "),
|
|
41
|
+
"surrogate-control": cdnDirectives(input),
|
|
42
|
+
}),
|
|
43
|
+
noStoreHeaders: () => ({ "surrogate-control": "no-store" }),
|
|
44
|
+
async purgeTags(tags) {
|
|
45
|
+
const headers: Record<string, string> = {
|
|
46
|
+
"fastly-key": options.apiToken,
|
|
47
|
+
"content-type": "application/json",
|
|
48
|
+
accept: "application/json",
|
|
49
|
+
}
|
|
50
|
+
if (soft) headers["fastly-soft-purge"] = "1"
|
|
51
|
+
const res = await send(endpoint, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers,
|
|
54
|
+
body: JSON.stringify({ surrogate_keys: tags }),
|
|
55
|
+
})
|
|
56
|
+
await res.body?.cancel()
|
|
57
|
+
return attemptFromStatus(res, "fastly")
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
options.queue,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel's CDN: `Vercel-Cache-Tag` and `Vercel-CDN-Cache-Control`, both consumed by Vercel and kept
|
|
3
|
+
* from the browser. Tags are case-sensitive; at most 16 a purge call and 128 a response.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
attemptFromStatus,
|
|
7
|
+
type CdnProvider,
|
|
8
|
+
cdnDirectives,
|
|
9
|
+
defineCdnProvider,
|
|
10
|
+
type PurgeQueueOptions,
|
|
11
|
+
} from "./core.ts"
|
|
12
|
+
|
|
13
|
+
type TagPurge = (tags: string | string[]) => Promise<unknown>
|
|
14
|
+
|
|
15
|
+
export type VercelOptions = (
|
|
16
|
+
| {
|
|
17
|
+
/** `invalidateByTag` from `@vercel/functions`: inside a Vercel Function, no token needed. */
|
|
18
|
+
readonly invalidateByTag: TagPurge
|
|
19
|
+
/** `dangerouslyDeleteByTag` from `@vercel/functions`, for `mode: "delete"`. */
|
|
20
|
+
readonly dangerouslyDeleteByTag?: TagPurge
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
/** A Vercel access token for the REST API. Keep it in an env var. */
|
|
24
|
+
readonly token: string
|
|
25
|
+
readonly projectId: string
|
|
26
|
+
readonly teamId?: string
|
|
27
|
+
/** Purge one environment only; default both. */
|
|
28
|
+
readonly target?: "production" | "preview"
|
|
29
|
+
/** The fetch purge calls go through; tests pass their own. */
|
|
30
|
+
readonly fetch?: typeof fetch
|
|
31
|
+
}
|
|
32
|
+
) & {
|
|
33
|
+
/**
|
|
34
|
+
* `invalidate` (default) marks tagged pages stale: the next visitor gets the old page while Vercel
|
|
35
|
+
* refetches. `delete` drops them, so the next visitors wait on the origin together.
|
|
36
|
+
*/
|
|
37
|
+
readonly mode?: "invalidate" | "delete"
|
|
38
|
+
readonly queue?: PurgeQueueOptions
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function vercel(options: VercelOptions): CdnProvider {
|
|
42
|
+
const mode = options.mode ?? "invalidate"
|
|
43
|
+
let purgeTags: (
|
|
44
|
+
tags: readonly string[],
|
|
45
|
+
) => ReturnType<Parameters<typeof defineCdnProvider>[0]["purgeTags"]>
|
|
46
|
+
if ("invalidateByTag" in options) {
|
|
47
|
+
const fn = mode === "delete" ? options.dangerouslyDeleteByTag : options.invalidateByTag
|
|
48
|
+
if (typeof fn !== "function") {
|
|
49
|
+
throw new TypeError(
|
|
50
|
+
`[nifra/web/cdn] vercel: pass ${mode === "delete" ? "dangerouslyDeleteByTag" : "invalidateByTag"} from @vercel/functions`,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
purgeTags = async (tags) => {
|
|
54
|
+
await fn([...tags])
|
|
55
|
+
return { ok: true }
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
if (typeof options.token !== "string" || options.token === "") {
|
|
59
|
+
throw new TypeError("[nifra/web/cdn] vercel: token is empty (is the env var set?)")
|
|
60
|
+
}
|
|
61
|
+
if (typeof options.projectId !== "string" || options.projectId === "") {
|
|
62
|
+
throw new TypeError("[nifra/web/cdn] vercel: projectId is required")
|
|
63
|
+
}
|
|
64
|
+
const send = options.fetch ?? fetch
|
|
65
|
+
const query = new URLSearchParams({ projectIdOrName: options.projectId })
|
|
66
|
+
if (options.teamId !== undefined) query.set("teamId", options.teamId)
|
|
67
|
+
const path = mode === "delete" ? "dangerously-delete-by-tags" : "invalidate-by-tags"
|
|
68
|
+
const endpoint = `https://api.vercel.com/v1/edge-cache/${path}?${query}`
|
|
69
|
+
const token = options.token
|
|
70
|
+
const target = options.target
|
|
71
|
+
purgeTags = async (tags) => {
|
|
72
|
+
const res = await send(endpoint, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
75
|
+
body: JSON.stringify(target === undefined ? { tags } : { tags, target }),
|
|
76
|
+
})
|
|
77
|
+
await res.body?.cancel()
|
|
78
|
+
return attemptFromStatus(res, "vercel")
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return defineCdnProvider(
|
|
82
|
+
{
|
|
83
|
+
name: "vercel",
|
|
84
|
+
tagsPerCall: 16,
|
|
85
|
+
maxResponseTags: 128,
|
|
86
|
+
cacheHeaders: (input) => ({
|
|
87
|
+
"vercel-cache-tag": input.tags.join(","),
|
|
88
|
+
"vercel-cdn-cache-control": cdnDirectives(input),
|
|
89
|
+
}),
|
|
90
|
+
noStoreHeaders: () => ({ "vercel-cdn-cache-control": "no-store" }),
|
|
91
|
+
purgeTags,
|
|
92
|
+
},
|
|
93
|
+
options.queue,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Code-unit order, the same on every runtime and in every locale. A build artifact or a report sorts
|
|
2
|
+
* with this, so its bytes and its order do not depend on the machine that produced it. */
|
|
3
|
+
export function codeUnitOrder(a: string, b: string): number {
|
|
4
|
+
return a < b ? -1 : a > b ? 1 : 0
|
|
5
|
+
}
|