@wular/pnext 0.0.4 → 0.0.6
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 +11 -11
- package/bin/pnext +1 -1
- package/config/lint/base.js +7 -7
- package/config/ts/base.json +2 -4
- package/config/ts/react.json +2 -6
- package/package.json +23 -2
- package/reference/compat.md +1 -1
- package/reference/config.md +2 -2
- package/reference/css.md +13 -9
- package/reference/dev.md +1 -1
- package/reference/metadata.md +7 -7
- package/reference/navigation.md +35 -28
- package/reference/performance.md +70 -70
- package/reference/rendering.md +17 -17
- package/reference/routing.md +17 -17
- package/reference/typegen.md +12 -8
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +113 -109
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +49 -52
- package/src/api/navigation.ts +59 -58
- package/src/api/server.ts +152 -144
- package/src/api/suspense.ts +4 -4
- package/src/cli/adapters/vercel-warm.ts +126 -121
- package/src/cli/adapters/vercel.ts +437 -443
- package/src/cli/analyze.ts +333 -144
- package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
- package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
- package/src/cli/build.ts +976 -978
- package/src/cli/create.ts +71 -63
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +88 -85
- package/src/cli/migrate/package-json.ts +73 -71
- package/src/cli/migrate/report.ts +32 -33
- package/src/cli/migrate/{index.ts → run.ts} +48 -49
- package/src/cli/migrate/scan.ts +46 -46
- package/src/cli/migrate/spinner.ts +9 -9
- package/src/cli/migrate/tsconfig.ts +35 -35
- package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
- package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
- package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
- package/src/cli/start.ts +65 -66
- package/src/{typegen.ts → cli/typegen.ts} +59 -59
- package/src/client/build.ts +794 -768
- package/src/client/chunk-fold.ts +245 -240
- package/src/client/{paths.ts → chunk-name.ts} +6 -6
- package/src/client/entry.ts +162 -147
- package/src/client/prebuilt.ts +231 -223
- package/src/client/profile.ts +29 -29
- package/src/client/react-compiler.ts +20 -15
- package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
- package/src/client/reference-stub.ts +51 -51
- package/src/client/reference.ts +19 -19
- package/src/{api → client}/router/events.ts +17 -17
- package/src/{api → client}/router/history.ts +16 -16
- package/src/{api → client}/router/hub.ts +52 -53
- package/src/{api/router.ts → client/router/index.ts} +61 -60
- package/src/{api → client}/router/policies.ts +24 -24
- package/src/{api → client}/router/runtime.ts +1988 -1992
- package/src/{api → client}/router/types.ts +93 -98
- package/src/compat/actions/client-plugin.ts +42 -43
- package/src/compat/actions/client-stub.ts +14 -14
- package/src/compat/actions/{action-client.ts → client.ts} +194 -193
- package/src/compat/actions/config.ts +63 -64
- package/src/compat/actions/detect.ts +68 -68
- package/src/compat/actions/discovery.ts +105 -105
- package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
- package/src/compat/actions/early-submit.ts +1 -1
- package/src/compat/actions/endpoint.ts +198 -198
- package/src/compat/actions/extensions.ts +811 -0
- package/src/compat/actions/flight.ts +23 -23
- package/src/compat/actions/form-state.ts +34 -34
- package/src/compat/actions/hoist.ts +382 -236
- package/src/compat/actions/ids.ts +7 -12
- package/src/compat/actions/index.ts +8 -8
- package/src/compat/actions/instances.ts +46 -46
- package/src/compat/actions/origin.ts +47 -48
- package/src/compat/actions/protocol.ts +22 -22
- package/src/compat/actions/registry.ts +19 -19
- package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
- package/src/compat/actions/rewrite.ts +125 -125
- package/src/compat/actions/{action-router.ts → router.ts} +10 -10
- package/src/compat/actions/serve.ts +132 -136
- package/src/compat/actions/server-tag.ts +4 -4
- package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
- package/src/compat/actions/unrecognized-error.ts +4 -4
- package/src/compat/{index.ts → aliases.ts} +112 -109
- package/src/compat/bundler/bun-externals.ts +18 -18
- package/src/compat/bundler/cjs-exports.ts +271 -223
- package/src/compat/bundler/config.ts +116 -111
- package/src/compat/bundler/externals.ts +12 -12
- package/src/compat/bundler/import-meta-url.ts +19 -19
- package/src/compat/bundler/modularize-imports.ts +36 -33
- package/src/compat/bundler/new-url-asset.ts +22 -24
- package/src/compat/bundler/optimize-package-imports.ts +111 -107
- package/src/compat/bundler/polyfill.ts +28 -28
- package/src/compat/bundler/react-compiler.ts +35 -29
- package/src/compat/bundler/react-profiler.tsx +11 -11
- package/src/compat/bundler/relay-transform.ts +48 -47
- package/src/compat/bundler/require-context.ts +119 -113
- package/src/compat/bundler/resolve-extensions.ts +19 -19
- package/src/compat/bundler/source-cache.ts +25 -25
- package/src/compat/bundler/static-imports.ts +7 -7
- package/src/compat/bundler/symlink-imports.ts +46 -46
- package/src/compat/bundler/tsconfig-paths.ts +13 -15
- package/src/compat/bundler/wasm.ts +58 -60
- package/src/compat/bundler/webpack-loaders.ts +254 -241
- package/src/compat/bundler/worker.ts +101 -104
- package/src/compat/cache/build-flags.ts +29 -29
- package/src/compat/cache/build-prerender-errors.ts +40 -44
- package/src/compat/cache/custom-handler.ts +60 -53
- package/src/compat/cache/fetch-patch.ts +240 -240
- package/src/compat/cache/handler.ts +27 -31
- package/src/compat/cache/modern-handler.ts +148 -126
- package/src/compat/cache/resume-data-cache.ts +47 -45
- package/src/compat/cache/revalidate.ts +233 -232
- package/src/compat/cache/runtime-error.ts +35 -35
- package/src/compat/cache/use-cache-transform.ts +442 -417
- package/src/compat/cache/use-cache.ts +650 -614
- package/src/compat/cache-control.ts +140 -142
- package/src/compat/client/base-path.ts +21 -20
- package/src/compat/client/css-order.ts +18 -18
- package/src/compat/client/errors/bare-boundary.ts +11 -11
- package/src/compat/client/errors/control-flow.ts +23 -23
- package/src/compat/client/errors/error-boundary.ts +61 -61
- package/src/compat/client/errors/global-error.ts +101 -91
- package/src/compat/client/errors/install.ts +71 -72
- package/src/compat/client/errors/lazy.ts +23 -23
- package/src/compat/client/errors/primitive-throw.ts +47 -45
- package/src/compat/client/errors/soft-refresh.ts +4 -4
- package/src/compat/client/link-status.ts +33 -33
- package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
- package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
- package/src/compat/client/navigation-scroll.ts +63 -63
- package/src/compat/client/optimistic-routing.ts +93 -88
- package/src/compat/client/prefetch-cache.ts +23 -24
- package/src/compat/client/route-announcer.ts +35 -35
- package/src/compat/client/segment-cache-policy.ts +20 -20
- package/src/compat/client/segment-cache.ts +309 -315
- package/src/compat/client/segment-prefetch.ts +127 -132
- package/src/compat/client/trailing-slash.ts +5 -4
- package/src/compat/css/chunking.ts +113 -116
- package/src/compat/css/inline-css.ts +21 -21
- package/src/compat/css/lightningcss.ts +37 -38
- package/src/compat/css/modules.ts +161 -175
- package/src/compat/css/nonce.ts +7 -7
- package/src/compat/css/sass-plugin.ts +18 -21
- package/src/compat/css/sass.ts +150 -152
- package/src/compat/css/styled-jsx-runtime.ts +27 -27
- package/src/compat/css/styled-jsx.ts +21 -21
- package/src/compat/edge-runtime.ts +27 -27
- package/src/compat/{adapter → export}/build-complete.ts +72 -75
- package/src/compat/export/client.ts +29 -31
- package/src/compat/export/{index.ts → emit.ts} +111 -110
- package/src/compat/export/standalone.ts +62 -54
- package/src/compat/image-optimizer/cache.ts +51 -49
- package/src/compat/image-optimizer/detect.ts +52 -52
- package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
- package/src/compat/image-optimizer/source.ts +77 -80
- package/src/compat/lifecycle/after-scope.ts +26 -26
- package/src/compat/lifecycle/after.ts +48 -45
- package/src/compat/lifecycle/error-funnel.ts +98 -102
- package/src/compat/lifecycle/error-serialize.ts +30 -26
- package/src/compat/lifecycle/error-ui.ts +67 -32
- package/src/compat/lifecycle/instrumentation-client.ts +36 -38
- package/src/compat/lifecycle/instrumentation.ts +85 -85
- package/src/compat/lifecycle/node-console.ts +11 -11
- package/src/compat/lifecycle/testmode.ts +160 -132
- package/src/compat/mdx/compile.ts +60 -57
- package/src/compat/mdx/plugin.ts +11 -11
- package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
- package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
- package/src/compat/metadata.ts +75 -81
- package/src/compat/next/cache.ts +68 -69
- package/src/compat/next/canonical-url.ts +9 -9
- package/src/compat/next/client-cache.ts +19 -19
- package/src/compat/next/client-navigation.ts +112 -116
- package/src/compat/next/client-only.ts +1 -1
- package/src/compat/next/client-script.tsx +99 -93
- package/src/compat/next/client-server.ts +10 -10
- package/src/compat/next/config-loader.ts +176 -173
- package/src/compat/next/config.ts +7 -7
- package/src/compat/next/constants.cjs +6 -6
- package/src/compat/next/constants.ts +6 -6
- package/src/compat/next/custom-server.ts +26 -24
- package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
- package/src/compat/next/dynamic.tsx +21 -18
- package/src/compat/next/error.tsx +52 -52
- package/src/compat/next/font/cache.ts +74 -74
- package/src/compat/next/font/google.ts +2 -2
- package/src/compat/next/font/index.ts +1 -1
- package/src/compat/next/font/local.ts +3 -3
- package/src/compat/next/font/runtime-client.ts +17 -15
- package/src/compat/next/font/runtime.ts +443 -408
- package/src/compat/next/font/shared.ts +120 -108
- package/src/compat/next/form.tsx +63 -63
- package/src/compat/next/head.tsx +2 -2
- package/src/compat/next/headers.ts +103 -95
- package/src/compat/next/image/client.tsx +220 -0
- package/src/compat/next/image/config.ts +56 -58
- package/src/compat/next/image/optimizer.ts +40 -36
- package/src/compat/next/image/patterns.ts +37 -40
- package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
- package/src/compat/next/image/shared.ts +65 -57
- package/src/compat/next/image/static-metadata.ts +98 -107
- package/src/compat/next/image/validate.ts +79 -88
- package/src/compat/next/image.tsx +19 -23
- package/src/compat/next/index.ts +1 -1
- package/src/compat/next/legacy-image.tsx +59 -60
- package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
- package/src/compat/next/link.tsx +158 -158
- package/src/compat/next/navigation.cjs +12 -3
- package/src/compat/next/navigation.ts +48 -50
- package/src/compat/next/offline.ts +27 -27
- package/src/compat/next/og.ts +121 -124
- package/src/compat/next/preferred-region.ts +13 -14
- package/src/compat/next/redirects.ts +58 -56
- package/src/compat/next/resource-hints.ts +73 -76
- package/src/compat/next/rewrites.ts +130 -133
- package/src/compat/next/root-params.ts +45 -45
- package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
- package/src/compat/next/router.cjs +4 -2
- package/src/compat/next/router.ts +58 -61
- package/src/compat/next/script.tsx +108 -108
- package/src/compat/next/server-only.ts +1 -1
- package/src/compat/next/server.ts +19 -19
- package/src/compat/next/svgr.ts +18 -17
- package/src/compat/next/telemetry.ts +24 -24
- package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
- package/src/compat/next/user-agent.ts +53 -49
- package/src/compat/next/web-vitals.ts +22 -24
- package/src/compat/otel/api.ts +41 -41
- package/src/compat/otel/client-trace-metadata.ts +25 -27
- package/src/compat/otel/fetch-span.ts +29 -29
- package/src/compat/otel/tracer.ts +331 -331
- package/src/compat/pages/api.ts +456 -0
- package/src/compat/pages/client-plugin.ts +36 -36
- package/src/compat/pages/router-state.ts +34 -34
- package/src/compat/pages/{index.ts → router.ts} +130 -135
- package/src/compat/ppr/io.ts +12 -12
- package/src/compat/ppr/missing-root-params.ts +34 -38
- package/src/compat/ppr/root-params-scan.ts +66 -66
- package/src/compat/ppr/root-params-transform.ts +24 -26
- package/src/compat/ppr/root-params.ts +30 -30
- package/src/compat/ppr/segment-config-incompat.ts +6 -7
- package/src/compat/protocol.ts +71 -70
- package/src/compat/react/action-state.ts +47 -48
- package/src/compat/react/client-lite.ts +15 -15
- package/src/compat/react/client.ts +4 -4
- package/src/compat/react/compiler-runtime.ts +11 -11
- package/src/compat/react/dom-client.ts +44 -44
- package/src/compat/react/dom-react-server.ts +10 -16
- package/src/compat/react/dom-server.ts +10 -10
- package/src/compat/react/dom.ts +52 -52
- package/src/compat/react/hooks-extra.ts +34 -35
- package/src/compat/react/parity.ts +64 -61
- package/src/compat/react/preact.ts +81 -82
- package/src/compat/react/react-server.ts +28 -28
- package/src/compat/react/router-shim.ts +1 -1
- package/src/compat/react/server-component-use.ts +8 -8
- package/src/compat/react/server-inserted-html.ts +30 -31
- package/src/compat/react/server.ts +53 -55
- package/src/compat/react/use.ts +32 -32
- package/src/compat/react/view-transition.ts +20 -20
- package/src/compat/register/actions.ts +35 -824
- package/src/compat/register/boot.ts +41 -41
- package/src/compat/register/build-tier.ts +5 -5
- package/src/compat/register/build.ts +74 -70
- package/src/compat/register/bundler.ts +161 -156
- package/src/compat/register/cache.ts +20 -20
- package/src/compat/register/client-errors.ts +3 -3
- package/src/compat/register/config.ts +6 -6
- package/src/compat/register/css-extras.ts +30 -34
- package/src/compat/register/edge-runtime.ts +3 -3
- package/src/compat/register/errors.ts +10 -12
- package/src/compat/register/export.ts +16 -16
- package/src/compat/register/font.ts +11 -11
- package/src/compat/register/hooks.ts +2 -2
- package/src/compat/register/image.ts +40 -40
- package/src/compat/register/index.ts +59 -62
- package/src/compat/register/instrumentation-client.ts +10 -10
- package/src/compat/register/lifecycle.ts +25 -28
- package/src/compat/register/mdx.ts +10 -10
- package/src/compat/register/middleware.ts +226 -16
- package/src/compat/register/otel.ts +80 -88
- package/src/compat/register/pages-api.ts +10 -463
- package/src/compat/register/ppr.ts +16 -16
- package/src/compat/register/protocol.ts +17 -18
- package/src/compat/register/proxy.ts +49 -51
- package/src/compat/register/render.ts +79 -76
- package/src/compat/register/routing.ts +162 -159
- package/src/compat/register/segment.ts +24 -1897
- package/src/compat/register/static-image.ts +3 -3
- package/src/compat/register/{misc.ts → taint.ts} +9 -9
- package/src/compat/register/typed-routes.ts +14 -14
- package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
- package/src/compat/register/validation.ts +18 -18
- package/src/compat/segment/loading-boundary.ts +43 -45
- package/src/compat/segment/page-slot.ts +69 -69
- package/src/compat/segment/serve.ts +1884 -0
- package/src/compat/segment/tree.ts +113 -112
- package/src/compat/segment/vary-key.ts +38 -38
- package/src/compat/segment/vary-params.ts +138 -142
- package/src/compat/static-params.ts +14 -12
- package/src/compat/tsconfig-defaults.ts +87 -91
- package/src/compat/typecheck/{index.ts → check.ts} +234 -212
- package/src/compat/typecheck/worker.ts +15 -12
- package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
- package/src/compat/typed-routes/manifest.ts +174 -170
- package/src/compat/typed-routes/typegen.ts +127 -110
- package/src/compat/validation/errors.ts +16 -19
- package/src/compat/validation/prerender-diagnostics.ts +521 -504
- package/src/compat/validation/{index.ts → validate.ts} +647 -648
- package/src/compat-bootstrap.ts +16 -16
- package/src/config.ts +69 -69
- package/src/css/build.ts +230 -227
- package/src/css/postcss.ts +79 -80
- package/src/css/worker.ts +14 -15
- package/src/dev/client-actions.ts +10 -10
- package/src/dev/client-chunk-store.ts +27 -27
- package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
- package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
- package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
- package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
- package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
- package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
- package/src/dev/server.ts +804 -820
- package/src/env.ts +46 -43
- package/src/extensions.ts +491 -478
- package/src/index.ts +8 -8
- package/src/internal.ts +20 -23
- package/src/{islands → render}/boundary-error.ts +3 -3
- package/src/render/hooks.ts +71 -71
- package/src/render/island-context.ts +14 -14
- package/src/render/metadata.ts +310 -310
- package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
- package/src/{ppr.ts → render/ppr.ts} +244 -245
- package/src/render/renderer.ts +2076 -2082
- package/src/render/resource-hints.ts +16 -17
- package/src/render/slots.tsx +224 -235
- package/src/{islands → render}/static-children.ts +9 -12
- package/src/{islands → render}/static-slots.ts +37 -37
- package/src/{cache/context.ts → request/cache.ts} +20 -20
- package/src/request/context.ts +107 -107
- package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +318 -296
- package/src/resolve/source-text.ts +37 -37
- package/src/{dynamic → resolve}/tree-shake.ts +132 -128
- package/src/routing/forwarded.ts +19 -19
- package/src/routing/handler.ts +84 -91
- package/src/routing/href.ts +69 -70
- package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
- package/src/{proxy.ts → routing/proxy.ts} +306 -312
- package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
- package/src/routing/routes.ts +827 -815
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +952 -0
- package/src/{dev → runtime}/module-cache.ts +309 -287
- package/src/{dev → runtime}/module-generations.ts +9 -9
- package/src/{dev → runtime}/module-transform.ts +81 -72
- package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
- package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
- package/src/runtime/vendor.ts +425 -404
- package/src/styles.d.ts +9 -0
- package/src/types.ts +320 -335
- package/src/utils/ansi.ts +5 -5
- package/src/utils/{source.ts → code.ts} +15 -12
- package/src/utils/content-type.ts +3 -3
- package/src/utils/decode.ts +2 -2
- package/src/utils/dev-profile.ts +13 -13
- package/src/utils/error-log.ts +6 -6
- package/src/utils/esbuild.ts +18 -18
- package/src/utils/fs-cache.ts +13 -13
- package/src/utils/fs.ts +57 -49
- package/src/utils/html.ts +20 -24
- package/src/utils/native-require.ts +8 -8
- package/src/utils/serialize.ts +139 -146
- package/src/utils/verbose.ts +18 -18
- package/src/cli/analyze-print.ts +0 -181
- package/src/compat/middleware/manifest.ts +0 -210
- package/src/compat/next/image-client.tsx +0 -215
- package/src/compat/next/link-usage.ts +0 -29
- package/src/css/index.ts +0 -2
- package/src/render/index.ts +0 -1
- package/src/style-modules.d.ts +0 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { runAction, type RunActionOptions } from './endpoint'
|
|
2
|
-
import { ACTION_FLIGHT_CONTENT_TYPE, createActionFlightResponse } from './flight'
|
|
3
|
-
import { INVALID_ORIGIN_MESSAGE, isActionOriginAllowed } from './origin'
|
|
1
|
+
import { runAction, type RunActionOptions } from './endpoint'
|
|
2
|
+
import { ACTION_FLIGHT_CONTENT_TYPE, createActionFlightResponse } from './flight'
|
|
3
|
+
import { INVALID_ORIGIN_MESSAGE, isActionOriginAllowed } from './origin'
|
|
4
4
|
import {
|
|
5
5
|
ACTION_ID_FIELD,
|
|
6
6
|
SUBMIT_ACTION_ID_FIELD,
|
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
MASKED_ACTION_ERROR,
|
|
15
15
|
REDIRECT_TYPE_HEADER,
|
|
16
16
|
type ActionSuccessBody,
|
|
17
|
-
} from './protocol'
|
|
17
|
+
} from './protocol'
|
|
18
18
|
|
|
19
|
-
export { ACTION_REFRESH_HEADER, ACTION_ID_FIELD }
|
|
19
|
+
export { ACTION_REFRESH_HEADER, ACTION_ID_FIELD }
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Server-action dispatch for the pnext servers. Actions POST to the page's own
|
|
@@ -34,7 +34,7 @@ export { ACTION_REFRESH_HEADER, ACTION_ID_FIELD };
|
|
|
34
34
|
|
|
35
35
|
/** True when a request is a JS-client action RPC (id in header). */
|
|
36
36
|
export function isActionRequest(request: Request): boolean {
|
|
37
|
-
return request.method.toUpperCase() === 'POST' && request.headers.has(ACTION_ID_HEADER)
|
|
37
|
+
return request.method.toUpperCase() === 'POST' && request.headers.has(ACTION_ID_HEADER)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -43,28 +43,27 @@ export function isActionRequest(request: Request): boolean {
|
|
|
43
43
|
* stays consumable. Non-form POSTs and forms without the field return false.
|
|
44
44
|
*/
|
|
45
45
|
export async function isProgressiveActionRequest(request: Request): Promise<boolean> {
|
|
46
|
-
if (request.method.toUpperCase() !== 'POST') return false
|
|
47
|
-
if (request.headers.has(ACTION_ID_HEADER)) return false
|
|
48
|
-
const contentType = request.headers.get('content-type') ?? ''
|
|
46
|
+
if (request.method.toUpperCase() !== 'POST') return false
|
|
47
|
+
if (request.headers.has(ACTION_ID_HEADER)) return false
|
|
48
|
+
const contentType = request.headers.get('content-type') ?? ''
|
|
49
49
|
const isForm =
|
|
50
|
-
contentType.includes('multipart/form-data') || contentType.includes('form-urlencoded')
|
|
51
|
-
if (!isForm) return false
|
|
50
|
+
contentType.includes('multipart/form-data') || contentType.includes('form-urlencoded')
|
|
51
|
+
if (!isForm) return false
|
|
52
52
|
try {
|
|
53
|
-
const form = await request.clone().formData()
|
|
53
|
+
const form = await request.clone().formData()
|
|
54
54
|
// A form-level action (ACTION_ID_FIELD) OR a bare submitter override
|
|
55
55
|
// (SUBMIT_ACTION_ID_FIELD, e.g. <form><button formAction={fn}> with no form
|
|
56
56
|
// action) both count as a progressive action submission.
|
|
57
|
-
const id =
|
|
58
|
-
|
|
59
|
-
return typeof id === 'string' && id !== '';
|
|
57
|
+
const id = lastFieldValue(form, SUBMIT_ACTION_ID_FIELD) ?? lastFieldValue(form, ACTION_ID_FIELD)
|
|
58
|
+
return typeof id === 'string' && id !== ''
|
|
60
59
|
} catch {
|
|
61
|
-
return false
|
|
60
|
+
return false
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
export interface ServeActionOptions extends RunActionOptions {
|
|
66
65
|
/** Render the app's 404 page for a notFound() result (progressive + RPC). */
|
|
67
|
-
renderNotFound?: (request: Request) => Promise<Response
|
|
66
|
+
renderNotFound?: (request: Request) => Promise<Response>
|
|
68
67
|
/**
|
|
69
68
|
* Progressive useActionState submissions: re-render the posted page with the
|
|
70
69
|
* action's result as the form's state (published via the consumed-once
|
|
@@ -74,22 +73,22 @@ export interface ServeActionOptions extends RunActionOptions {
|
|
|
74
73
|
request: Request,
|
|
75
74
|
state: unknown,
|
|
76
75
|
actionId: string,
|
|
77
|
-
) => Promise<Response | null
|
|
76
|
+
) => Promise<Response | null>
|
|
78
77
|
/**
|
|
79
78
|
* Single-pass redirects: render the redirect target's page for an RPC redirect so the 303 response
|
|
80
79
|
* carries the destination HTML and the client swaps it in without a follow-up GET. The request passed
|
|
81
80
|
* already carries the action's cookie mutations. Null falls back to the JSON redirect envelope.
|
|
82
81
|
*/
|
|
83
|
-
renderRedirectTarget?: (request: Request, target: URL) => Promise<Response | null
|
|
82
|
+
renderRedirectTarget?: (request: Request, target: URL) => Promise<Response | null>
|
|
84
83
|
/** Render a page payload for a JS action Flight response. */
|
|
85
|
-
renderActionFlight?: (request: Request, target: URL) => Promise<Response | null
|
|
84
|
+
renderActionFlight?: (request: Request, target: URL) => Promise<Response | null>
|
|
86
85
|
/**
|
|
87
86
|
* Next "forwarded action" discrimination: true when the POSTed route's module graph does NOT contain
|
|
88
87
|
* the action (the client dispatched it after navigating away from the page that owns it). Next
|
|
89
88
|
* forwards such requests to the owning worker and relays the inner response's HEADERS but not its
|
|
90
89
|
* status, so the browser observes 200 + `x-action-redirect` instead of the direct-dispatch 303.
|
|
91
90
|
*/
|
|
92
|
-
isForwardedAction?: (id: string) => boolean
|
|
91
|
+
isForwardedAction?: (id: string) => boolean
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
/** Serve a server-action request (either kind). */
|
|
@@ -97,7 +96,7 @@ export async function serveAction(
|
|
|
97
96
|
request: Request,
|
|
98
97
|
options: ServeActionOptions,
|
|
99
98
|
): Promise<Response> {
|
|
100
|
-
const progressive = !request.headers.has(ACTION_ID_HEADER)
|
|
99
|
+
const progressive = !request.headers.has(ACTION_ID_HEADER)
|
|
101
100
|
|
|
102
101
|
// CSRF origin check runs before the action executes: a mismatched origin must
|
|
103
102
|
// never run the action. JS clients get the error as a text/plain body (thrown
|
|
@@ -107,16 +106,16 @@ export async function serveAction(
|
|
|
107
106
|
return new Response('Internal Server Error', {
|
|
108
107
|
status: 500,
|
|
109
108
|
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
110
|
-
})
|
|
109
|
+
})
|
|
111
110
|
}
|
|
112
111
|
return new Response(options.dev ? INVALID_ORIGIN_MESSAGE : MASKED_ACTION_ERROR, {
|
|
113
112
|
status: 500,
|
|
114
113
|
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
115
|
-
})
|
|
114
|
+
})
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
const promoted = progressive ? await promoteFormActionId(request) : undefined
|
|
119
|
-
const prepared = promoted?.request ?? request
|
|
117
|
+
const promoted = progressive ? await promoteFormActionId(request) : undefined
|
|
118
|
+
const prepared = promoted?.request ?? request
|
|
120
119
|
|
|
121
120
|
// A form POST to a page carrying no recognizable action id is an
|
|
122
121
|
// unrecognized MPA action: 404 + Next's marker header and log, rather than a
|
|
@@ -125,64 +124,64 @@ export async function serveAction(
|
|
|
125
124
|
console.error(
|
|
126
125
|
'Failed to find Server Action. This request might be from an older or newer deployment.\n' +
|
|
127
126
|
'Read more: https://nextjs.org/docs/messages/failed-to-find-server-action',
|
|
128
|
-
)
|
|
127
|
+
)
|
|
129
128
|
return new Response('Server action not found.', {
|
|
130
129
|
status: 404,
|
|
131
130
|
headers: {
|
|
132
131
|
'content-type': 'text/plain; charset=utf-8',
|
|
133
132
|
[NEXT_ACTION_NOT_FOUND_HEADER]: '1',
|
|
134
133
|
},
|
|
135
|
-
})
|
|
134
|
+
})
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
const response = await runAction(prepared, options)
|
|
137
|
+
const response = await runAction(prepared, options)
|
|
139
138
|
|
|
140
139
|
if (response.headers.has(ACTION_NOT_FOUND_HEADER) && options.renderNotFound) {
|
|
141
140
|
// Serve the real 404 page HTML with a 404 status and the marker header. A
|
|
142
141
|
// progressive submit shows it directly; the JS client runtime detects the
|
|
143
|
-
// 404 + marker and swaps the document in place (
|
|
142
|
+
// 404 + marker and swaps the document in place (client.ts). Returning
|
|
144
143
|
// an empty 200 JSON here would leave the JS client with nothing to render.
|
|
145
|
-
const notFound = await options.renderNotFound(request)
|
|
146
|
-
const headers = new Headers(notFound.headers)
|
|
147
|
-
headers.set(ACTION_NOT_FOUND_HEADER, '1')
|
|
148
|
-
copySetCookies(response.headers, headers)
|
|
149
|
-
return new Response(notFound.body, { status: 404, headers })
|
|
144
|
+
const notFound = await options.renderNotFound(request)
|
|
145
|
+
const headers = new Headers(notFound.headers)
|
|
146
|
+
headers.set(ACTION_NOT_FOUND_HEADER, '1')
|
|
147
|
+
copySetCookies(response.headers, headers)
|
|
148
|
+
return new Response(notFound.body, { status: 404, headers })
|
|
150
149
|
}
|
|
151
150
|
|
|
152
151
|
if (!progressive) {
|
|
153
|
-
const wantsFlight = acceptsActionFlight(request)
|
|
152
|
+
const wantsFlight = acceptsActionFlight(request)
|
|
154
153
|
if (response.status === 303 && response.headers.has(ACTION_REDIRECT_HEADER)) {
|
|
155
|
-
const target = response.headers.get(ACTION_REDIRECT_HEADER)
|
|
156
|
-
const headers = new Headers()
|
|
157
|
-
if (response.headers.has(ACTION_REFRESH_HEADER)) headers.set(ACTION_REFRESH_HEADER, '1')
|
|
158
|
-
const revalidated = response.headers.get(NEXT_ACTION_REVALIDATED_HEADER)
|
|
159
|
-
if (revalidated) headers.set(NEXT_ACTION_REVALIDATED_HEADER, revalidated)
|
|
160
|
-
const redirectType = response.headers.get(REDIRECT_TYPE_HEADER)
|
|
161
|
-
if (redirectType) headers.set(REDIRECT_TYPE_HEADER, redirectType)
|
|
162
|
-
copySetCookies(response.headers, headers)
|
|
154
|
+
const target = response.headers.get(ACTION_REDIRECT_HEADER)!
|
|
155
|
+
const headers = new Headers()
|
|
156
|
+
if (response.headers.has(ACTION_REFRESH_HEADER)) headers.set(ACTION_REFRESH_HEADER, '1')
|
|
157
|
+
const revalidated = response.headers.get(NEXT_ACTION_REVALIDATED_HEADER)
|
|
158
|
+
if (revalidated) headers.set(NEXT_ACTION_REVALIDATED_HEADER, revalidated)
|
|
159
|
+
const redirectType = response.headers.get(REDIRECT_TYPE_HEADER)
|
|
160
|
+
if (redirectType) headers.set(REDIRECT_TYPE_HEADER, redirectType)
|
|
161
|
+
copySetCookies(response.headers, headers)
|
|
163
162
|
|
|
164
163
|
// Forwarded action (dispatched from a page that does not own it): Next
|
|
165
164
|
// relays the owning worker's headers on a 200 — no single-pass 303.
|
|
166
165
|
// The JSON envelope makes the client soft-navigate to the target.
|
|
167
|
-
const actionId = prepared.headers.get(ACTION_ID_HEADER)
|
|
166
|
+
const actionId = prepared.headers.get(ACTION_ID_HEADER)
|
|
168
167
|
if (actionId && options.isForwardedAction?.(actionId)) {
|
|
169
|
-
headers.set(ACTION_REDIRECT_HEADER, target)
|
|
170
|
-
headers.set('content-type', 'application/json; charset=utf-8')
|
|
171
|
-
return new Response(JSON.stringify({ redirect: target }), { status: 200, headers })
|
|
168
|
+
headers.set(ACTION_REDIRECT_HEADER, target)
|
|
169
|
+
headers.set('content-type', 'application/json; charset=utf-8')
|
|
170
|
+
return new Response(JSON.stringify({ redirect: target }), { status: 200, headers })
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
// Same-origin targets: single-pass redirect. The 303 has NO location
|
|
175
174
|
// header (fetch would transparently follow one) and carries the
|
|
176
175
|
// destination page HTML; the client pushes history and swaps the
|
|
177
176
|
// document in one round trip, like Next's flight redirects.
|
|
178
|
-
const targetUrl = resolveRedirectTarget(request, target)
|
|
177
|
+
const targetUrl = resolveRedirectTarget(request, target)
|
|
179
178
|
if (targetUrl && (options.renderActionFlight || options.renderRedirectTarget)) {
|
|
180
179
|
const rendered = await options
|
|
181
180
|
.renderActionFlight?.(
|
|
182
181
|
requestWithAppliedCookies(request, targetUrl, response.headers),
|
|
183
182
|
targetUrl,
|
|
184
183
|
)
|
|
185
|
-
.catch(() => null)
|
|
184
|
+
.catch(() => null)
|
|
186
185
|
if (wantsFlight && rendered?.status === 200) {
|
|
187
186
|
return createActionFlightResponse({
|
|
188
187
|
pageResponse: rendered,
|
|
@@ -190,7 +189,7 @@ export async function serveAction(
|
|
|
190
189
|
actionHeaders: response.headers,
|
|
191
190
|
redirect: targetUrl.pathname + targetUrl.search,
|
|
192
191
|
...(redirectType ? { redirectType } : {}),
|
|
193
|
-
})
|
|
192
|
+
})
|
|
194
193
|
}
|
|
195
194
|
const htmlRendered = rendered
|
|
196
195
|
? rendered
|
|
@@ -199,32 +198,32 @@ export async function serveAction(
|
|
|
199
198
|
requestWithAppliedCookies(request, targetUrl, response.headers),
|
|
200
199
|
targetUrl,
|
|
201
200
|
)
|
|
202
|
-
.catch(() => null)
|
|
201
|
+
.catch(() => null)
|
|
203
202
|
if (htmlRendered?.status === 200) {
|
|
204
|
-
headers.set(ACTION_REDIRECT_HEADER, targetUrl.pathname + targetUrl.search)
|
|
203
|
+
headers.set(ACTION_REDIRECT_HEADER, targetUrl.pathname + targetUrl.search)
|
|
205
204
|
headers.set(
|
|
206
205
|
'content-type',
|
|
207
206
|
htmlRendered.headers.get('content-type') ?? 'text/html; charset=utf-8',
|
|
208
|
-
)
|
|
209
|
-
return new Response(htmlRendered.body, { status: 303, headers })
|
|
207
|
+
)
|
|
208
|
+
return new Response(htmlRendered.body, { status: 303, headers })
|
|
210
209
|
}
|
|
211
210
|
// Same host, but the app cannot render it in-process (a path outside basePath, a rewrite to
|
|
212
211
|
// another origin). Next still answers the action with a redirect status, so send the bare 303
|
|
213
212
|
// plus marker and let the client make the follow-up request.
|
|
214
|
-
headers.set(ACTION_REDIRECT_HEADER, targetUrl.pathname + targetUrl.search)
|
|
215
|
-
return new Response(null, { status: 303, headers })
|
|
213
|
+
headers.set(ACTION_REDIRECT_HEADER, targetUrl.pathname + targetUrl.search)
|
|
214
|
+
return new Response(null, { status: 303, headers })
|
|
216
215
|
}
|
|
217
216
|
|
|
218
217
|
// Fallback: JSON envelope; the client soft-navigates (or hard-navigates
|
|
219
218
|
// for external origins).
|
|
220
|
-
headers.set('content-type', 'application/json; charset=utf-8')
|
|
221
|
-
return new Response(JSON.stringify({ redirect: target }), { status: 200, headers })
|
|
219
|
+
headers.set('content-type', 'application/json; charset=utf-8')
|
|
220
|
+
return new Response(JSON.stringify({ redirect: target }), { status: 200, headers })
|
|
222
221
|
}
|
|
223
222
|
// A non-redirect action returns its plain JSON envelope. When it also revalidated (or mutated
|
|
224
223
|
// cookies), the refresh header rides along and the JS client soft-refreshes the current route in
|
|
225
224
|
// place, preserving hydrated island state. Wrapping the return in a full-page flight envelope here
|
|
226
225
|
// would force the client to rewrite the document, discarding that live state.
|
|
227
|
-
return response
|
|
226
|
+
return response
|
|
228
227
|
}
|
|
229
228
|
|
|
230
229
|
// Progressive success: a useActionState form re-renders the page with the
|
|
@@ -234,15 +233,15 @@ export async function serveAction(
|
|
|
234
233
|
if (response.status === 200) {
|
|
235
234
|
if (promoted?.hasFormState && options.renderPageForFormState) {
|
|
236
235
|
try {
|
|
237
|
-
const body = (await response.clone().json()) as ActionSuccessBody
|
|
236
|
+
const body = (await response.clone().json()) as ActionSuccessBody
|
|
238
237
|
const page = await options.renderPageForFormState(
|
|
239
238
|
request,
|
|
240
239
|
body.data,
|
|
241
240
|
prepared.headers.get(ACTION_ID_HEADER)!,
|
|
242
|
-
)
|
|
241
|
+
)
|
|
243
242
|
if (page) {
|
|
244
|
-
copySetCookies(response.headers, page.headers)
|
|
245
|
-
return page
|
|
243
|
+
copySetCookies(response.headers, page.headers)
|
|
244
|
+
return page
|
|
246
245
|
}
|
|
247
246
|
} catch {
|
|
248
247
|
// fall through to the 303
|
|
@@ -255,43 +254,41 @@ export async function serveAction(
|
|
|
255
254
|
// that observes the just-applied invalidations and cookie mutations) and return it as the POST
|
|
256
255
|
// response, matching the single-pass render the flight client path already performs.
|
|
257
256
|
if (response.headers.has(ACTION_REFRESH_HEADER) && options.renderRedirectTarget) {
|
|
258
|
-
const pageUrl = new URL(request.url)
|
|
259
|
-
const refreshRequest = requestWithAppliedCookies(request, pageUrl, response.headers)
|
|
260
|
-
refreshRequest.headers.set('x-pnext-soft-nav', '1')
|
|
261
|
-
const rendered = await options
|
|
262
|
-
.renderRedirectTarget(refreshRequest, pageUrl)
|
|
263
|
-
.catch(() => null);
|
|
257
|
+
const pageUrl = new URL(request.url)
|
|
258
|
+
const refreshRequest = requestWithAppliedCookies(request, pageUrl, response.headers)
|
|
259
|
+
refreshRequest.headers.set('x-pnext-soft-nav', '1')
|
|
260
|
+
const rendered = await options.renderRedirectTarget(refreshRequest, pageUrl).catch(() => null)
|
|
264
261
|
if (rendered?.status === 200) {
|
|
265
|
-
const headers = new Headers(rendered.headers)
|
|
266
|
-
copySetCookies(response.headers, headers)
|
|
267
|
-
return new Response(rendered.body, { status: 200, headers })
|
|
262
|
+
const headers = new Headers(rendered.headers)
|
|
263
|
+
copySetCookies(response.headers, headers)
|
|
264
|
+
return new Response(rendered.body, { status: 200, headers })
|
|
268
265
|
}
|
|
269
266
|
}
|
|
270
|
-
const headers = new Headers({ location: progressiveReturnTarget(request) })
|
|
271
|
-
copySetCookies(response.headers, headers)
|
|
272
|
-
return new Response(null, { status: 303, headers })
|
|
267
|
+
const headers = new Headers({ location: progressiveReturnTarget(request) })
|
|
268
|
+
copySetCookies(response.headers, headers)
|
|
269
|
+
return new Response(null, { status: 303, headers })
|
|
273
270
|
}
|
|
274
271
|
// Progressive unknown-id: mirror Next's MPA behavior (a plain 500).
|
|
275
272
|
if (response.status === 404 && !response.headers.has(ACTION_NOT_FOUND_HEADER)) {
|
|
276
273
|
console.error(
|
|
277
274
|
'Error: Failed to find Server Action. This request might be from an older or newer deployment',
|
|
278
|
-
)
|
|
275
|
+
)
|
|
279
276
|
return new Response('Internal Server Error', {
|
|
280
277
|
status: 500,
|
|
281
278
|
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
282
|
-
})
|
|
279
|
+
})
|
|
283
280
|
}
|
|
284
|
-
return response
|
|
281
|
+
return response
|
|
285
282
|
}
|
|
286
283
|
|
|
287
284
|
function progressiveReturnTarget(request: Request): string {
|
|
288
|
-
const referer = request.headers.get('referer')
|
|
289
|
-
if (referer) return referer
|
|
290
|
-
return new URL(request.url).pathname || '/'
|
|
285
|
+
const referer = request.headers.get('referer')
|
|
286
|
+
if (referer) return referer
|
|
287
|
+
return new URL(request.url).pathname || '/'
|
|
291
288
|
}
|
|
292
289
|
|
|
293
290
|
function acceptsActionFlight(request: Request): boolean {
|
|
294
|
-
return request.headers.get('accept')?.includes(ACTION_FLIGHT_CONTENT_TYPE) ?? false
|
|
291
|
+
return request.headers.get('accept')?.includes(ACTION_FLIGHT_CONTENT_TYPE) ?? false
|
|
295
292
|
}
|
|
296
293
|
|
|
297
294
|
/**
|
|
@@ -303,73 +300,72 @@ function acceptsActionFlight(request: Request): boolean {
|
|
|
303
300
|
async function promoteFormActionId(
|
|
304
301
|
request: Request,
|
|
305
302
|
): Promise<{ request: Request; hasFormState: boolean }> {
|
|
306
|
-
const contentType = request.headers.get('content-type') ?? ''
|
|
303
|
+
const contentType = request.headers.get('content-type') ?? ''
|
|
307
304
|
const isForm =
|
|
308
|
-
contentType.includes('multipart/form-data') || contentType.includes('form-urlencoded')
|
|
309
|
-
if (!isForm) return { request, hasFormState: false }
|
|
305
|
+
contentType.includes('multipart/form-data') || contentType.includes('form-urlencoded')
|
|
306
|
+
if (!isForm) return { request, hasFormState: false }
|
|
310
307
|
|
|
311
|
-
let form: FormData
|
|
308
|
+
let form: FormData
|
|
312
309
|
try {
|
|
313
|
-
form = await request.clone().formData()
|
|
310
|
+
form = await request.clone().formData()
|
|
314
311
|
} catch {
|
|
315
312
|
// Unparseable body (e.g. a mismatched multipart boundary): no id to promote;
|
|
316
313
|
// the caller treats it as an unrecognized MPA action.
|
|
317
|
-
return { request, hasFormState: false }
|
|
314
|
+
return { request, hasFormState: false }
|
|
318
315
|
}
|
|
319
316
|
// A per-button formAction override (SUBMIT_ACTION_ID_FIELD) wins over the
|
|
320
317
|
// form's default action (ACTION_ID_FIELD), unambiguously and independent of
|
|
321
318
|
// field order — a no-JS submit has no event.submitter to tell them apart, and
|
|
322
319
|
// the form-level hidden field can render either before or after the button.
|
|
323
|
-
const submitId = firstFieldValue(form, SUBMIT_ACTION_ID_FIELD)
|
|
324
|
-
const formActionId = firstFieldValue(form, ACTION_ID_FIELD)
|
|
325
|
-
const id = submitId ?? formActionId
|
|
326
|
-
if (typeof id !== 'string' || id === '') return { request, hasFormState: false }
|
|
327
|
-
form.delete(SUBMIT_ACTION_ID_FIELD)
|
|
320
|
+
const submitId = firstFieldValue(form, SUBMIT_ACTION_ID_FIELD)
|
|
321
|
+
const formActionId = firstFieldValue(form, ACTION_ID_FIELD)
|
|
322
|
+
const id = submitId ?? formActionId
|
|
323
|
+
if (typeof id !== 'string' || id === '') return { request, hasFormState: false }
|
|
324
|
+
form.delete(SUBMIT_ACTION_ID_FIELD)
|
|
328
325
|
// Form state only applies to the form's own useActionState action, never a
|
|
329
326
|
// plain submitter override (which runs as action(formData), not (state, fd)).
|
|
330
|
-
const hasFormState =
|
|
331
|
-
|
|
332
|
-
if (!hasFormState) form.delete(FORM_STATE_FIELD);
|
|
327
|
+
const hasFormState = submitId === undefined && typeof form.get(FORM_STATE_FIELD) === 'string'
|
|
328
|
+
if (!hasFormState) form.delete(FORM_STATE_FIELD)
|
|
333
329
|
|
|
334
|
-
const headers = new Headers(request.headers)
|
|
335
|
-
headers.set(ACTION_ID_HEADER, id)
|
|
330
|
+
const headers = new Headers(request.headers)
|
|
331
|
+
headers.set(ACTION_ID_HEADER, id)
|
|
336
332
|
// Drop the stale content-type/length; the runtime derives a fresh multipart
|
|
337
333
|
// content-type (with a new boundary) from the FormData body below.
|
|
338
|
-
headers.delete('content-type')
|
|
339
|
-
headers.delete('content-length')
|
|
334
|
+
headers.delete('content-type')
|
|
335
|
+
headers.delete('content-length')
|
|
340
336
|
return {
|
|
341
337
|
request: new Request(request.url, { method: 'POST', headers, body: form }),
|
|
342
338
|
hasFormState,
|
|
343
|
-
}
|
|
339
|
+
}
|
|
344
340
|
}
|
|
345
341
|
|
|
346
342
|
function lastFieldValue(form: FormData, name: string) {
|
|
347
|
-
const values = form.getAll(name)
|
|
348
|
-
return values.length > 0 ? values[values.length - 1] : undefined
|
|
343
|
+
const values = form.getAll(name)
|
|
344
|
+
return values.length > 0 ? values[values.length - 1] : undefined
|
|
349
345
|
}
|
|
350
346
|
|
|
351
347
|
function firstFieldValue(form: FormData, name: string) {
|
|
352
|
-
const values = form.getAll(name)
|
|
353
|
-
return values.length > 0 ? values[0] : undefined
|
|
348
|
+
const values = form.getAll(name)
|
|
349
|
+
return values.length > 0 ? values[0] : undefined
|
|
354
350
|
}
|
|
355
351
|
|
|
356
352
|
function copySetCookies(from: Headers, to: Headers) {
|
|
357
|
-
const setCookies = (from as Headers & { getSetCookie?: () => string[] }).getSetCookie?.() ?? []
|
|
358
|
-
for (const cookie of setCookies) to.append('set-cookie', cookie)
|
|
353
|
+
const setCookies = (from as Headers & { getSetCookie?: () => string[] }).getSetCookie?.() ?? []
|
|
354
|
+
for (const cookie of setCookies) to.append('set-cookie', cookie)
|
|
359
355
|
}
|
|
360
356
|
|
|
361
357
|
/** Same-origin redirect target URL, or undefined for external redirects. */
|
|
362
358
|
function resolveRedirectTarget(request: Request, target: string): URL | undefined {
|
|
363
359
|
try {
|
|
364
|
-
const requestUrl = new URL(request.url)
|
|
365
|
-
const relativeBase = new URL(requestUrl)
|
|
360
|
+
const requestUrl = new URL(request.url)
|
|
361
|
+
const relativeBase = new URL(requestUrl)
|
|
366
362
|
if (!target.startsWith('/') && !/^[a-z][a-z0-9+.-]*:/i.test(target)) {
|
|
367
|
-
relativeBase.pathname = `${relativeBase.pathname.replace(/\/+$/, '')}
|
|
363
|
+
relativeBase.pathname = `${relativeBase.pathname.replace(/\/+$/, '')}/`
|
|
368
364
|
}
|
|
369
|
-
const url = new URL(target, relativeBase)
|
|
370
|
-
return url.origin === requestUrl.origin ? url : undefined
|
|
365
|
+
const url = new URL(target, relativeBase)
|
|
366
|
+
return url.origin === requestUrl.origin ? url : undefined
|
|
371
367
|
} catch {
|
|
372
|
-
return undefined
|
|
368
|
+
return undefined
|
|
373
369
|
}
|
|
374
370
|
}
|
|
375
371
|
|
|
@@ -380,35 +376,35 @@ function resolveRedirectTarget(request: Request, target: string): URL | undefine
|
|
|
380
376
|
* browser following the redirect would send.
|
|
381
377
|
*/
|
|
382
378
|
function requestWithAppliedCookies(request: Request, target: URL, actionHeaders: Headers): Request {
|
|
383
|
-
const headers = new Headers(request.headers)
|
|
384
|
-
headers.delete('content-type')
|
|
385
|
-
headers.delete('content-length')
|
|
386
|
-
headers.delete(ACTION_ID_HEADER)
|
|
379
|
+
const headers = new Headers(request.headers)
|
|
380
|
+
headers.delete('content-type')
|
|
381
|
+
headers.delete('content-length')
|
|
382
|
+
headers.delete(ACTION_ID_HEADER)
|
|
387
383
|
|
|
388
384
|
const setCookies =
|
|
389
|
-
(actionHeaders as Headers & { getSetCookie?: () => string[] }).getSetCookie?.() ?? []
|
|
385
|
+
(actionHeaders as Headers & { getSetCookie?: () => string[] }).getSetCookie?.() ?? []
|
|
390
386
|
if (setCookies.length > 0) {
|
|
391
|
-
const jar = new Map<string, string>()
|
|
392
|
-
const existing = headers.get('cookie') ?? ''
|
|
387
|
+
const jar = new Map<string, string>()
|
|
388
|
+
const existing = headers.get('cookie') ?? ''
|
|
393
389
|
for (const part of existing.split(';')) {
|
|
394
|
-
const eq = part.indexOf('=')
|
|
395
|
-
if (eq === -1) continue
|
|
396
|
-
jar.set(part.slice(0, eq).trim(), part.slice(eq + 1).trim())
|
|
390
|
+
const eq = part.indexOf('=')
|
|
391
|
+
if (eq === -1) continue
|
|
392
|
+
jar.set(part.slice(0, eq).trim(), part.slice(eq + 1).trim())
|
|
397
393
|
}
|
|
398
394
|
for (const setCookie of setCookies) {
|
|
399
|
-
const [pair, ...attributes] = setCookie.split(';')
|
|
400
|
-
const eq = (pair ?? '').indexOf('=')
|
|
401
|
-
if (eq === -1) continue
|
|
402
|
-
const name = pair!.slice(0, eq).trim()
|
|
403
|
-
const value = pair!.slice(eq + 1).trim()
|
|
404
|
-
const expired = attributes.some(attr => /^\s*max-age\s*=\s*0*\s*$/i.test(attr))
|
|
405
|
-
if (expired) jar.delete(name)
|
|
406
|
-
else jar.set(name, value)
|
|
395
|
+
const [pair, ...attributes] = setCookie.split(';')
|
|
396
|
+
const eq = (pair ?? '').indexOf('=')
|
|
397
|
+
if (eq === -1) continue
|
|
398
|
+
const name = pair!.slice(0, eq).trim()
|
|
399
|
+
const value = pair!.slice(eq + 1).trim()
|
|
400
|
+
const expired = attributes.some(attr => /^\s*max-age\s*=\s*0*\s*$/i.test(attr))
|
|
401
|
+
if (expired) jar.delete(name)
|
|
402
|
+
else jar.set(name, value)
|
|
407
403
|
}
|
|
408
|
-
const cookieHeader = [...jar.entries()].map(([name, value]) => `${name}=${value}`).join('; ')
|
|
409
|
-
if (cookieHeader) headers.set('cookie', cookieHeader)
|
|
410
|
-
else headers.delete('cookie')
|
|
404
|
+
const cookieHeader = [...jar.entries()].map(([name, value]) => `${name}=${value}`).join('; ')
|
|
405
|
+
if (cookieHeader) headers.set('cookie', cookieHeader)
|
|
406
|
+
else headers.delete('cookie')
|
|
411
407
|
}
|
|
412
408
|
|
|
413
|
-
return new Request(target.href, { headers })
|
|
409
|
+
return new Request(target.href, { headers })
|
|
414
410
|
}
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
* don't retain modules. Best-effort: an untagged action function renders as a
|
|
10
10
|
* normal form (the client stub still POSTs it when JS runs).
|
|
11
11
|
*/
|
|
12
|
-
const actionIds = new WeakMap<object, string>()
|
|
12
|
+
const actionIds = new WeakMap<object, string>()
|
|
13
13
|
|
|
14
14
|
export function tagServerAction(fn: unknown, id: string): void {
|
|
15
|
-
if (typeof fn === 'function') actionIds.set(fn, id)
|
|
15
|
+
if (typeof fn === 'function') actionIds.set(fn, id)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function serverActionId(fn: unknown): string | undefined {
|
|
19
|
-
if (typeof fn !== 'function') return undefined
|
|
20
|
-
return actionIds.get(fn)
|
|
19
|
+
if (typeof fn !== 'function') return undefined
|
|
20
|
+
return actionIds.get(fn)
|
|
21
21
|
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The sliver of the client action runtime both tiers need: the install-time half (
|
|
3
|
-
* and the dispatch half (
|
|
2
|
+
* The sliver of the client action runtime both tiers need: the install-time half (client.ts)
|
|
3
|
+
* and the dispatch half (dispatch.ts, loaded on the first action call). Keep this module small
|
|
4
4
|
* - every byte here ships at first paint on any page that uses a server action.
|
|
5
5
|
*/
|
|
6
|
-
import type { ComponentType } from 'preact'
|
|
6
|
+
import type { ComponentType } from 'preact'
|
|
7
7
|
|
|
8
8
|
// Progressive-enhancement form fields: written by the server renderer, read by
|
|
9
9
|
// the submit listener (initial) and stripped off the wire by the encoder.
|
|
10
|
-
export const ACTION_ID_FIELD = '$pnext_action_id'
|
|
11
|
-
export const SUBMIT_ACTION_ID_FIELD = '$pnext_submit_action_id'
|
|
12
|
-
export const FORM_STATE_FIELD = '$pnext_form_state'
|
|
10
|
+
export const ACTION_ID_FIELD = '$pnext_action_id'
|
|
11
|
+
export const SUBMIT_ACTION_ID_FIELD = '$pnext_submit_action_id'
|
|
12
|
+
export const FORM_STATE_FIELD = '$pnext_form_state'
|
|
13
13
|
|
|
14
14
|
// Binary wire markers: encoded by the dispatch half, revived by both (island
|
|
15
15
|
// props can carry typed arrays before any action has run).
|
|
16
|
-
export const TYPED_ARRAY_MARKER = '$$pnext_typed_array'
|
|
17
|
-
export const ARRAY_BUFFER_MARKER = '$$pnext_array_buffer'
|
|
16
|
+
export const TYPED_ARRAY_MARKER = '$$pnext_typed_array'
|
|
17
|
+
export const ARRAY_BUFFER_MARKER = '$$pnext_array_buffer'
|
|
18
18
|
|
|
19
19
|
export interface ActionFunction {
|
|
20
|
-
(...args: unknown[]): Promise<unknown
|
|
21
|
-
$$pnextActionId?: string
|
|
20
|
+
(...args: unknown[]): Promise<unknown>
|
|
21
|
+
$$pnextActionId?: string
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface ActionRuntime {
|
|
25
|
-
call(id: string, args: unknown[]): Promise<unknown
|
|
26
|
-
createAction(id: string): ActionFunction
|
|
27
|
-
registerClientReference(id: string, component: ComponentType<Record<string, unknown>>): void
|
|
28
|
-
reviveProps<T>(value: T): T
|
|
25
|
+
call(id: string, args: unknown[]): Promise<unknown>
|
|
26
|
+
createAction(id: string): ActionFunction
|
|
27
|
+
registerClientReference(id: string, component: ComponentType<Record<string, unknown>>): void
|
|
28
|
+
reviveProps<T>(value: T): T
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
declare global {
|
|
32
32
|
interface Window {
|
|
33
|
-
__PNEXT_ACTIONS__?: ActionRuntime
|
|
33
|
+
__PNEXT_ACTIONS__?: ActionRuntime
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
interface PNextActionError extends Error {
|
|
38
|
-
$$pnextActionError?: boolean
|
|
38
|
+
$$pnextActionError?: boolean
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function tagActionError(error: unknown): unknown {
|
|
42
|
-
if (error instanceof Error) (error as PNextActionError).$$pnextActionError = true
|
|
43
|
-
return error
|
|
42
|
+
if (error instanceof Error) (error as PNextActionError).$$pnextActionError = true
|
|
43
|
+
return error
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** True for errors produced by an action dispatch (boundary channel filter). */
|
|
47
47
|
export function isActionError(error: unknown): boolean {
|
|
48
|
-
return Boolean(error instanceof Error && (error as PNextActionError).$$pnextActionError)
|
|
48
|
+
return Boolean(error instanceof Error && (error as PNextActionError).$$pnextActionError)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/** Client components an action return may reference by id (element markers). */
|
|
52
52
|
export const actionReturnClientReferences = new Map<
|
|
53
53
|
string,
|
|
54
54
|
ComponentType<Record<string, unknown>>
|
|
55
|
-
>()
|
|
55
|
+
>()
|
|
56
56
|
|
|
57
57
|
const TYPED_ARRAY_CTORS: Record<string, new (buffer: ArrayBuffer) => ArrayBufferView> = {
|
|
58
58
|
Int8Array,
|
|
@@ -66,30 +66,30 @@ const TYPED_ARRAY_CTORS: Record<string, new (buffer: ArrayBuffer) => ArrayBuffer
|
|
|
66
66
|
Float64Array,
|
|
67
67
|
BigInt64Array,
|
|
68
68
|
BigUint64Array,
|
|
69
|
-
}
|
|
69
|
+
}
|
|
70
70
|
|
|
71
71
|
export function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
72
72
|
if (typeof atob === 'function') {
|
|
73
|
-
const binary = atob(base64)
|
|
74
|
-
const bytes = new Uint8Array(binary.length)
|
|
75
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
|
|
76
|
-
return bytes.buffer
|
|
73
|
+
const binary = atob(base64)
|
|
74
|
+
const bytes = new Uint8Array(binary.length)
|
|
75
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
|
|
76
|
+
return bytes.buffer
|
|
77
77
|
}
|
|
78
|
-
const buf = Buffer.from(base64, 'base64')
|
|
79
|
-
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
|
|
78
|
+
const buf = Buffer.from(base64, 'base64')
|
|
79
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/** Revive a typed-array / ArrayBuffer wire marker, else undefined. */
|
|
83
83
|
export function reviveBinaryMarker(record: Record<string, unknown>): unknown {
|
|
84
|
-
const typed = record[TYPED_ARRAY_MARKER]
|
|
84
|
+
const typed = record[TYPED_ARRAY_MARKER]
|
|
85
85
|
if (typed && typeof typed === 'object') {
|
|
86
|
-
const { type, data } = typed as { type?: string; data?: string }
|
|
87
|
-
const Ctor = type ? TYPED_ARRAY_CTORS[type] : undefined
|
|
86
|
+
const { type, data } = typed as { type?: string; data?: string }
|
|
87
|
+
const Ctor = type ? TYPED_ARRAY_CTORS[type] : undefined
|
|
88
88
|
if (Ctor && typeof data === 'string') {
|
|
89
|
-
return new Ctor(base64ToArrayBuffer(data))
|
|
89
|
+
return new Ctor(base64ToArrayBuffer(data))
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
const buffer = record[ARRAY_BUFFER_MARKER]
|
|
93
|
-
if (typeof buffer === 'string') return base64ToArrayBuffer(buffer)
|
|
94
|
-
return undefined
|
|
92
|
+
const buffer = record[ARRAY_BUFFER_MARKER]
|
|
93
|
+
if (typeof buffer === 'string') return base64ToArrayBuffer(buffer)
|
|
94
|
+
return undefined
|
|
95
95
|
}
|