@wular/pnext 0.0.1
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 +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Installs the client-side server-action machinery for Next-compat entries:
|
|
2
|
+
// - `window.__PNEXT_ROUTER__`: minimal navigate/refresh global bridging the soft-navigation runtime,
|
|
3
|
+
// used after action redirects and refresh signals.
|
|
4
|
+
// - `window.__PNEXT_ACTIONS__`: the action RPC runtime - argument encoding, the POST wire,
|
|
5
|
+
// redirect/refresh/error semantics, island prop revival, and <form action={fn}> interception.
|
|
6
|
+
// Only imported into client entries when Next compat is enabled.
|
|
7
|
+
|
|
8
|
+
import { softNavigate } from '../../api/router';
|
|
9
|
+
import { installActionRuntime } from './action-client';
|
|
10
|
+
|
|
11
|
+
interface PNextRouterGlobal {
|
|
12
|
+
navigate: (href: string) => Promise<void>;
|
|
13
|
+
refresh: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
interface Window {
|
|
18
|
+
__PNEXT_ROUTER__?: PNextRouterGlobal;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function installActionRouter() {
|
|
23
|
+
if (!process.browser && typeof window === 'undefined') return;
|
|
24
|
+
installActionRuntime();
|
|
25
|
+
const existing = window.__PNEXT_ROUTER__;
|
|
26
|
+
if (
|
|
27
|
+
existing &&
|
|
28
|
+
typeof existing.navigate === 'function' &&
|
|
29
|
+
typeof existing.refresh === 'function'
|
|
30
|
+
) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
window.__PNEXT_ROUTER__ = {
|
|
34
|
+
navigate: (href: string) => softNavigate(href),
|
|
35
|
+
// A refresh re-fetches the current URL and swaps the body in place, which
|
|
36
|
+
// re-runs the server render (picking up any revalidated data). replace:true
|
|
37
|
+
// keeps history intact; scroll:false avoids jumping to the top.
|
|
38
|
+
refresh: () => softNavigate(location.href, { replace: true, scroll: false }),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sliver of the client action runtime both tiers need: the install-time half (action-client.ts)
|
|
3
|
+
* and the dispatch half (action-dispatch.ts, loaded on the first action call). Keep this module small
|
|
4
|
+
* - every byte here ships at first paint on any page that uses a server action.
|
|
5
|
+
*/
|
|
6
|
+
import type { ComponentType } from 'preact';
|
|
7
|
+
|
|
8
|
+
// Progressive-enhancement form fields: written by the server renderer, read by
|
|
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';
|
|
13
|
+
|
|
14
|
+
// Binary wire markers: encoded by the dispatch half, revived by both (island
|
|
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';
|
|
18
|
+
|
|
19
|
+
export interface ActionFunction {
|
|
20
|
+
(...args: unknown[]): Promise<unknown>;
|
|
21
|
+
$$pnextActionId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
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;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare global {
|
|
32
|
+
interface Window {
|
|
33
|
+
__PNEXT_ACTIONS__?: ActionRuntime;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface PNextActionError extends Error {
|
|
38
|
+
$$pnextActionError?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function tagActionError(error: unknown): unknown {
|
|
42
|
+
if (error instanceof Error) (error as PNextActionError).$$pnextActionError = true;
|
|
43
|
+
return error;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** True for errors produced by an action dispatch (boundary channel filter). */
|
|
47
|
+
export function isActionError(error: unknown): boolean {
|
|
48
|
+
return Boolean(error instanceof Error && (error as PNextActionError).$$pnextActionError);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Client components an action return may reference by id (element markers). */
|
|
52
|
+
export const actionReturnClientReferences = new Map<
|
|
53
|
+
string,
|
|
54
|
+
ComponentType<Record<string, unknown>>
|
|
55
|
+
>();
|
|
56
|
+
|
|
57
|
+
const TYPED_ARRAY_CTORS: Record<string, new (buffer: ArrayBuffer) => ArrayBufferView> = {
|
|
58
|
+
Int8Array,
|
|
59
|
+
Uint8Array,
|
|
60
|
+
Uint8ClampedArray,
|
|
61
|
+
Int16Array,
|
|
62
|
+
Uint16Array,
|
|
63
|
+
Int32Array,
|
|
64
|
+
Uint32Array,
|
|
65
|
+
Float32Array,
|
|
66
|
+
Float64Array,
|
|
67
|
+
BigInt64Array,
|
|
68
|
+
BigUint64Array,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
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;
|
|
77
|
+
}
|
|
78
|
+
const buf = Buffer.from(base64, 'base64');
|
|
79
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Revive a typed-array / ArrayBuffer wire marker, else undefined. */
|
|
83
|
+
export function reviveBinaryMarker(record: Record<string, unknown>): unknown {
|
|
84
|
+
const typed = record[TYPED_ARRAY_MARKER];
|
|
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;
|
|
88
|
+
if (Ctor && typeof data === 'string') {
|
|
89
|
+
return new Ctor(base64ToArrayBuffer(data));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const buffer = record[ARRAY_BUFFER_MARKER];
|
|
93
|
+
if (typeof buffer === 'string') return base64ToArrayBuffer(buffer);
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import type { Plugin } from 'esbuild';
|
|
4
|
+
import { readText } from '../../utils/fs';
|
|
5
|
+
import { escapeRegex } from '../../utils/source';
|
|
6
|
+
import { actionId } from './ids';
|
|
7
|
+
import { moduleActionExports, moduleLevelUseCache, moduleLevelUseServer } from './detect';
|
|
8
|
+
import { clientStubSource } from './client-stub';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Process-level set of `'use server'` module source paths (absolute) that the
|
|
12
|
+
* client bundler must replace with the generated RPC stub. Populated by the
|
|
13
|
+
* build/dev discovery pass before client bundling runs, and consulted by the
|
|
14
|
+
* esbuild plugin below (wired through compatAliasPlugin so it participates in
|
|
15
|
+
* every client build without editing the client build pipeline directly).
|
|
16
|
+
*
|
|
17
|
+
* Keyed by the workspace/project root the ids were computed against so the stub
|
|
18
|
+
* emits ids that match the server registry.
|
|
19
|
+
*/
|
|
20
|
+
interface ClientActionState {
|
|
21
|
+
root: string;
|
|
22
|
+
/** Original (as-discovered) absolute paths — used for import identity. */
|
|
23
|
+
files: Set<string>;
|
|
24
|
+
/** Realpath-canonical paths — used to match esbuild's resolved paths. */
|
|
25
|
+
canonicalFiles: Set<string>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let clientActionState: ClientActionState | undefined;
|
|
29
|
+
|
|
30
|
+
export function setClientActionModules(root: string, files: Iterable<string>) {
|
|
31
|
+
const originals = new Set<string>();
|
|
32
|
+
const canonicalFiles = new Set<string>();
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
originals.add(path.resolve(file));
|
|
35
|
+
canonicalFiles.add(canonical(file));
|
|
36
|
+
}
|
|
37
|
+
clientActionState =
|
|
38
|
+
originals.size > 0 ? { root, files: originals, canonicalFiles } : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// esbuild reports resolved paths through the OS realpath (e.g. /private on
|
|
42
|
+
// macOS), so both the stored set and the lookup canonicalize to match. Also
|
|
43
|
+
// used for id computation so the build, the client stub, and the render-side
|
|
44
|
+
// form tagging all derive the same wire id from the same (realpath) basis.
|
|
45
|
+
export function canonical(file: string) {
|
|
46
|
+
const abs = path.resolve(file);
|
|
47
|
+
try {
|
|
48
|
+
return realpathSync.native(abs);
|
|
49
|
+
} catch {
|
|
50
|
+
return abs;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function clearClientActionModules() {
|
|
55
|
+
clientActionState = undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function hasClientActionModules() {
|
|
59
|
+
return clientActionState !== undefined && clientActionState.files.size > 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isActionModule(file: string) {
|
|
63
|
+
if (!clientActionState) return false;
|
|
64
|
+
// Match both the original and the realpath-canonical form: render passes the
|
|
65
|
+
// original path; esbuild passes the realpath.
|
|
66
|
+
return (
|
|
67
|
+
clientActionState.files.has(path.resolve(file)) ||
|
|
68
|
+
clientActionState.canonicalFiles.has(canonical(file))
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** True when `file` is a discovered client-replaced action module. */
|
|
73
|
+
export function isClientActionModule(file: string) {
|
|
74
|
+
return isActionModule(file);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Root the client action ids were computed against (for stub id parity). */
|
|
78
|
+
export function clientActionModulesRoot() {
|
|
79
|
+
return clientActionState?.root;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** All discovered action module source paths (absolute). */
|
|
83
|
+
export function clientActionModuleFiles(): string[] {
|
|
84
|
+
return clientActionState ? [...clientActionState.files] : [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Generate the client fetch-stub source for an action module file. */
|
|
88
|
+
export function actionStubSourceFor(file: string, root?: string) {
|
|
89
|
+
return stubSourceFor(file, root ?? clientActionState?.root);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* esbuild plugin that swaps a client import of a `'use server'` module for the generated fetch stub.
|
|
94
|
+
* It hooks LOAD, not resolve: the filter is the exact discovered action paths, so esbuild's own
|
|
95
|
+
* resolver decides what a specifier points at and this plugin only sees the handful of files it
|
|
96
|
+
* replaces. A catch-all onResolve here used to re-resolve every import in the graph through
|
|
97
|
+
* `build.resolve`, and perturbed esbuild's CJS-interop decisions on the way.
|
|
98
|
+
*/
|
|
99
|
+
export function actionClientStubPlugin(): Plugin {
|
|
100
|
+
const paths = clientActionState
|
|
101
|
+
? [...clientActionState.files, ...clientActionState.canonicalFiles]
|
|
102
|
+
: [];
|
|
103
|
+
const filter = new RegExp(`^(?:${[...new Set(paths)].map(escapeRegex).join('|')})$`);
|
|
104
|
+
return {
|
|
105
|
+
name: 'pnext-action-client-stub',
|
|
106
|
+
setup(build) {
|
|
107
|
+
if (paths.length === 0) return;
|
|
108
|
+
build.onLoad({ filter, namespace: 'file' }, async args => {
|
|
109
|
+
if (!isActionModule(args.path)) return undefined;
|
|
110
|
+
return {
|
|
111
|
+
contents: await stubSourceFor(args.path, clientActionState?.root),
|
|
112
|
+
loader: 'js',
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function stubSourceFor(file: string, root?: string) {
|
|
120
|
+
const source = await readText(file);
|
|
121
|
+
if (!moduleLevelUseServer(source) && !moduleLevelUseCache(source)) return 'export {};';
|
|
122
|
+
const exports = moduleActionExports(source);
|
|
123
|
+
const actionIds: Record<string, string> = {};
|
|
124
|
+
for (const name of exports) actionIds[name] = canonicalActionId(file, name, root);
|
|
125
|
+
return clientStubSource({ modulePath: file, exports, actionIds });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Compute an action wire id from a realpath-canonical (file, root) basis so every producer -
|
|
130
|
+
* discovery/registry, the client stub, and render-side form tagging - agrees regardless of
|
|
131
|
+
* symlink/realpath differences (e.g. /private on macOS temp dirs).
|
|
132
|
+
*/
|
|
133
|
+
export function canonicalActionId(file: string, exportName: string, root?: string): string {
|
|
134
|
+
return actionId(canonical(file), exportName, root ? canonical(root) : undefined);
|
|
135
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Description of one action module, as the discovery pass hands it to the stub
|
|
3
|
+
* generator: the module's path (for reference/debug), the action export names,
|
|
4
|
+
* and the precomputed wire id for each.
|
|
5
|
+
*/
|
|
6
|
+
export interface ClientStubModule {
|
|
7
|
+
modulePath: string;
|
|
8
|
+
exports: readonly string[];
|
|
9
|
+
actionIds: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate the CLIENT stub SOURCE for a server-action module. On the client, a `'use server'` import
|
|
14
|
+
* is replaced by this module: each export becomes an async function that dispatches through the shared
|
|
15
|
+
* pnext action runtime, installed on `window.__PNEXT_ACTIONS__` by the client entry before any user
|
|
16
|
+
* interaction can call an action.
|
|
17
|
+
*
|
|
18
|
+
* The runtime owns argument encoding, the POST to the current page URL with the next-action header,
|
|
19
|
+
* and redirect/refresh/error handling, keeping every stub tiny. The generated source is a
|
|
20
|
+
* self-contained ES module with no imports, so it can be emitted to disk and bundled as-is.
|
|
21
|
+
*/
|
|
22
|
+
export function clientStubSource(module: ClientStubModule): string {
|
|
23
|
+
const lines: string[] = [];
|
|
24
|
+
|
|
25
|
+
lines.push(`// Generated pnext server-action client stub.`);
|
|
26
|
+
lines.push(`// Module: ${sanitizeComment(module.modulePath)}`);
|
|
27
|
+
lines.push(RUNTIME);
|
|
28
|
+
|
|
29
|
+
for (const name of module.exports) {
|
|
30
|
+
const id = module.actionIds[name];
|
|
31
|
+
if (!id) continue;
|
|
32
|
+
const decl = name === 'default' ? 'export default' : `export const ${name} =`;
|
|
33
|
+
lines.push(`${decl} __pnextAction(${JSON.stringify(id)});`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return lines.join('\n') + '\n';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const RUNTIME = `
|
|
40
|
+
function __pnextAction(id) {
|
|
41
|
+
const action = async function (...args) {
|
|
42
|
+
const runtime = (process.browser || typeof window !== 'undefined') ? window.__PNEXT_ACTIONS__ : undefined;
|
|
43
|
+
if (runtime) {
|
|
44
|
+
return runtime.call(id, args);
|
|
45
|
+
}
|
|
46
|
+
// Called on the SERVER: a client-imported 'use server' function invoked during a server
|
|
47
|
+
// render/action (a bound action a client page's useActionState registered, dispatched in the no-JS
|
|
48
|
+
// progressive path). Route it through the endpoint's server-side dispatcher, which resolves the id
|
|
49
|
+
// to the real compiled action and runs it in the active request scope, instead of throwing as if
|
|
50
|
+
// the browser runtime were missing.
|
|
51
|
+
const server =
|
|
52
|
+
typeof globalThis !== 'undefined' ? globalThis.__PNEXT_SERVER_ACTIONS__ : undefined;
|
|
53
|
+
if (server) {
|
|
54
|
+
return server.call(id, args);
|
|
55
|
+
}
|
|
56
|
+
throw new Error('pnext action runtime is not installed (is Next compat enabled?)');
|
|
57
|
+
};
|
|
58
|
+
action.$$pnextActionId = id;
|
|
59
|
+
return action;
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
function sanitizeComment(value: string): string {
|
|
64
|
+
return value.replace(/\*\//g, '*\\/').replace(/[\r\n]/g, ' ');
|
|
65
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileHref } from '../../config';
|
|
5
|
+
import { DEFAULT_BODY_LIMIT_BYTES } from './endpoint';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Server-actions options read from the app's next.config in compat mode:
|
|
9
|
+
* `experimental.serverActions.bodySizeLimit` (also accepted at the top-level `serverActions` key,
|
|
10
|
+
* where newer Next versions moved it). Invalid limits fail server startup with Next's exact error
|
|
11
|
+
* message - the size-limit e2e suite asserts the text.
|
|
12
|
+
*/
|
|
13
|
+
export const SIZE_LIMIT_ERROR =
|
|
14
|
+
'Server Actions Size Limit must be a valid number or filesize format larger than 1MB';
|
|
15
|
+
|
|
16
|
+
export interface ServerActionsConfig {
|
|
17
|
+
bodySizeLimitBytes: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface NextConfigShape {
|
|
21
|
+
serverActions?: { bodySizeLimit?: unknown };
|
|
22
|
+
experimental?: { serverActions?: { bodySizeLimit?: unknown } | boolean };
|
|
23
|
+
rewrites?: () => unknown;
|
|
24
|
+
redirects?: () => unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function loadServerActionsConfig(root: string): Promise<ServerActionsConfig> {
|
|
28
|
+
const config = await loadResolvedNextConfig(root);
|
|
29
|
+
const experimental = config?.experimental;
|
|
30
|
+
const limit =
|
|
31
|
+
config?.serverActions?.bodySizeLimit ??
|
|
32
|
+
(experimental && typeof experimental === 'object' && experimental.serverActions
|
|
33
|
+
? (experimental.serverActions as { bodySizeLimit?: unknown }).bodySizeLimit
|
|
34
|
+
: undefined);
|
|
35
|
+
if (limit === undefined) return { bodySizeLimitBytes: DEFAULT_BODY_LIMIT_BYTES };
|
|
36
|
+
return { bodySizeLimitBytes: parseBodySizeLimit(limit) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function loadResolvedNextConfig(root: string): Promise<NextConfigShape | undefined> {
|
|
40
|
+
return (await loadNextConfigFromStore()) ?? (await loadNextConfig(root));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read the next.config already loaded by the compat config-loader. At runtime that loader has bundled
|
|
45
|
+
* the app's config with esbuild and stored the fully-evaluated object, so it reflects every authoring
|
|
46
|
+
* style (CJS/ESM/TS) correctly. Prefer it over re-importing next.config.js here: a bare import() of a
|
|
47
|
+
* CommonJS config runs through pnext's runtime module hooks and comes back as an empty object, which
|
|
48
|
+
* silently dropped the configured bodySizeLimit. Returns undefined when the store is empty so the
|
|
49
|
+
* caller falls back to a direct file read.
|
|
50
|
+
*/
|
|
51
|
+
async function loadNextConfigFromStore(): Promise<NextConfigShape | undefined> {
|
|
52
|
+
try {
|
|
53
|
+
const { getNextConfig } = await import('../next/config-loader');
|
|
54
|
+
const store: NextConfigShape = getNextConfig();
|
|
55
|
+
return store && Object.keys(store).length > 0 ? store : undefined;
|
|
56
|
+
} catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function loadNextConfig(root: string): Promise<NextConfigShape | undefined> {
|
|
62
|
+
for (const name of ['next.config.js', 'next.config.mjs', 'next.config.cjs']) {
|
|
63
|
+
const file = path.join(root, name);
|
|
64
|
+
if (!existsSync(file)) continue;
|
|
65
|
+
try {
|
|
66
|
+
if (name.endsWith('.js') || name.endsWith('.cjs')) {
|
|
67
|
+
const requireFrom = createRequire(path.join(root, 'noop.js'));
|
|
68
|
+
delete requireFrom.cache[requireFrom.resolve(file)];
|
|
69
|
+
const required: unknown = requireFrom(file);
|
|
70
|
+
let value =
|
|
71
|
+
required && typeof required === 'object' && 'default' in required
|
|
72
|
+
? required.default
|
|
73
|
+
: required;
|
|
74
|
+
if (typeof value === 'function') {
|
|
75
|
+
value = await (value as (phase: string, ctx: { defaultConfig: object }) => unknown)(
|
|
76
|
+
'phase-production-server',
|
|
77
|
+
{ defaultConfig: {} },
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return value ?? undefined;
|
|
81
|
+
}
|
|
82
|
+
const loaded = (await import(`${pathToFileHref(file)}?v=${Date.now()}`)) as {
|
|
83
|
+
default?: unknown;
|
|
84
|
+
};
|
|
85
|
+
let value: unknown = loaded.default ?? loaded;
|
|
86
|
+
if (typeof value === 'function') {
|
|
87
|
+
value = await (value as (phase: string, ctx: { defaultConfig: object }) => unknown)(
|
|
88
|
+
'phase-production-server',
|
|
89
|
+
{ defaultConfig: {} },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return (value ?? undefined);
|
|
93
|
+
} catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Startup validation of `serverActions.bodySizeLimit`, matching Next's behavior of failing start (and
|
|
102
|
+
* build) with the exact size-limit error when the configured limit is malformed - the suite asserts
|
|
103
|
+
* the message on cliOutput, before any action request is made.
|
|
104
|
+
*
|
|
105
|
+
* Loads the app's CommonJS next.config synchronously (createRequire) so an invalid limit throws inline
|
|
106
|
+
* from the init hook. ESM-only configs and configs that fail to load for any unrelated reason are
|
|
107
|
+
* skipped here - the lazy per-request path still validates.
|
|
108
|
+
*/
|
|
109
|
+
export function validateServerActionsConfigSync(root: string): void {
|
|
110
|
+
let config: NextConfigShape | undefined;
|
|
111
|
+
const requireFrom = createRequire(path.join(root, 'noop.js'));
|
|
112
|
+
for (const name of ['next.config.js', 'next.config.cjs']) {
|
|
113
|
+
const file = path.join(root, name);
|
|
114
|
+
if (!existsSync(file)) continue;
|
|
115
|
+
try {
|
|
116
|
+
delete requireFrom.cache[requireFrom.resolve(file)];
|
|
117
|
+
let value: unknown = requireFrom(file);
|
|
118
|
+
if (value && typeof value === 'object' && 'default' in value) {
|
|
119
|
+
value = (value as { default?: unknown }).default;
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === 'function') {
|
|
122
|
+
value = (value as (phase: string, ctx: { defaultConfig: object }) => unknown)(
|
|
123
|
+
'phase-production-server',
|
|
124
|
+
{ defaultConfig: {} },
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
config = value as NextConfigShape | undefined;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
// A malformed limit surfaced synchronously from require's module eval is
|
|
130
|
+
// unlikely; any load failure means we can't validate here — defer.
|
|
131
|
+
if (error instanceof Error && error.message === SIZE_LIMIT_ERROR) throw error;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (!config) return;
|
|
137
|
+
const experimental = config.experimental;
|
|
138
|
+
const limit =
|
|
139
|
+
config.serverActions?.bodySizeLimit ??
|
|
140
|
+
(experimental && typeof experimental === 'object' && experimental.serverActions
|
|
141
|
+
? (experimental.serverActions as { bodySizeLimit?: unknown }).bodySizeLimit
|
|
142
|
+
: undefined);
|
|
143
|
+
if (limit === undefined) return;
|
|
144
|
+
parseBodySizeLimit(limit);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Parse '2mb' / '500kb' / bytes number into bytes; throw SIZE_LIMIT_ERROR. */
|
|
148
|
+
export function parseBodySizeLimit(value: unknown): number {
|
|
149
|
+
if (typeof value === 'number') {
|
|
150
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(SIZE_LIMIT_ERROR);
|
|
151
|
+
return Math.floor(value);
|
|
152
|
+
}
|
|
153
|
+
if (typeof value === 'string') {
|
|
154
|
+
const match = /^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i.exec(value.trim());
|
|
155
|
+
if (!match) throw new Error(SIZE_LIMIT_ERROR);
|
|
156
|
+
const amount = Number(match[1]);
|
|
157
|
+
if (!Number.isFinite(amount) || amount <= 0) throw new Error(SIZE_LIMIT_ERROR);
|
|
158
|
+
const unit = (match[2] ?? 'b').toLowerCase();
|
|
159
|
+
const factor =
|
|
160
|
+
unit === 'gb' ? 1024 ** 3 : unit === 'mb' ? 1024 ** 2 : unit === 'kb' ? 1024 : 1;
|
|
161
|
+
return Math.floor(amount * factor);
|
|
162
|
+
}
|
|
163
|
+
throw new Error(SIZE_LIMIT_ERROR);
|
|
164
|
+
}
|