@thomas.f/prnext 0.1.19-alpha → 0.1.21-alpha

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.
Files changed (113) hide show
  1. package/build/app.mjs +46 -78
  2. package/build/build-lock.mjs +2 -71
  3. package/build/build-retention.mjs +119 -0
  4. package/build/build-timings.mjs +38 -0
  5. package/build/bundle-runtime.mjs +17 -16
  6. package/build/cache-command.mjs +31 -0
  7. package/build/check.mjs +41 -3
  8. package/build/compilation-cache.mjs +32 -12
  9. package/build/compilation-storage.mjs +190 -0
  10. package/build/compiler.mjs +5 -4
  11. package/build/config.mjs +23 -2
  12. package/build/create.mjs +1 -1
  13. package/build/css-modules.mjs +4 -0
  14. package/build/dev-action-graph.mjs +21 -0
  15. package/build/flight-summary.mjs +64 -0
  16. package/build/font-cache.mjs +9 -6
  17. package/build/font-files.mjs +1 -1
  18. package/build/index.mjs +57 -36
  19. package/build/inspect.mjs +3 -0
  20. package/build/inspection-summary.mjs +7 -2
  21. package/build/native-discovery.mjs +18 -6
  22. package/build/native-ppr.mjs +32 -0
  23. package/build/npm-packages.mjs +2 -2
  24. package/build/og.mjs +121 -1
  25. package/build/parallel-plan.mjs +55 -0
  26. package/build/persistent-cache.mjs +29 -24
  27. package/build/production-check.mjs +34 -0
  28. package/build/react-compiler-report.mjs +31 -8
  29. package/build/react-compiler.mjs +101 -10
  30. package/build/server-config.mjs +35 -0
  31. package/build/source-analysis.mjs +108 -5
  32. package/build/standalone-copy.mjs +48 -0
  33. package/build/standalone.mjs +2 -22
  34. package/build/static-adapter.mjs +7 -3
  35. package/build/transform-cache.mjs +17 -3
  36. package/build/transform.mjs +38 -6
  37. package/build/typescript-dev.mjs +3 -1
  38. package/cli.mjs +27 -5
  39. package/compat/app-layout-context.cjs +7 -3
  40. package/compat/diagnostics.cjs +5 -4
  41. package/compat/flight-delta.cjs +47 -12
  42. package/compat/index.d.ts +24 -1
  43. package/compat/navigation-authorization.cjs +8 -1
  44. package/compat/runtime-sample.cjs +56 -0
  45. package/compat/use-cache.cjs +5 -2
  46. package/docs/ai/compatibility.md +113 -10
  47. package/docs/ai/deployment.md +144 -0
  48. package/docs/ai/quickstart.md +5 -0
  49. package/docs/ai/troubleshooting.md +22 -0
  50. package/package.json +25 -22
  51. package/release/fontkit.mjs +31 -0
  52. package/release/verify.mjs +3 -0
  53. package/runtime/api.mjs +5 -2
  54. package/runtime/app-client.mjs +246 -54
  55. package/runtime/app-partial.mjs +2 -10
  56. package/runtime/app-prefetch.mjs +2 -2
  57. package/runtime/app-render.mjs +42 -13
  58. package/runtime/app-stream-channel.mjs +20 -6
  59. package/runtime/app-stream-html.mjs +30 -11
  60. package/runtime/app-styles.mjs +22 -4
  61. package/runtime/cache-components-rsc.mjs +26 -6
  62. package/runtime/cache-result.mjs +58 -0
  63. package/runtime/compile-cache-retention.mjs +53 -0
  64. package/runtime/compile-cache.mjs +28 -1
  65. package/runtime/dev-client.mjs +32 -8
  66. package/runtime/dev-compiler-broker.mjs +20 -23
  67. package/runtime/dev-gateway.mjs +23 -6
  68. package/runtime/dev-route-selection.mjs +64 -0
  69. package/runtime/dev-runtime-pool.mjs +3 -2
  70. package/runtime/dev-runtime-spare.mjs +1 -1
  71. package/runtime/dev-session-retention.mjs +154 -0
  72. package/runtime/development-flight.mjs +59 -0
  73. package/runtime/development-queue.mjs +33 -0
  74. package/runtime/development.mjs +37 -16
  75. package/runtime/env.mjs +9 -0
  76. package/runtime/flight-delta.mjs +31 -5
  77. package/runtime/flight-diagnostics.mjs +48 -0
  78. package/runtime/flight-tap.mjs +10 -5
  79. package/runtime/html-envelope.mjs +34 -0
  80. package/runtime/loopback-rpc.mjs +92 -0
  81. package/runtime/native-ppr.mjs +88 -0
  82. package/runtime/og/NOTICE.txt +1 -1
  83. package/runtime/og/index.node.mjs +76 -31216
  84. package/runtime/og/upstream.json +115 -0
  85. package/runtime/persistent/client.mjs +5 -3
  86. package/runtime/persistent/common.mjs +7 -18
  87. package/runtime/persistent/daemon.mjs +4 -12
  88. package/runtime/persistent/health.mjs +58 -0
  89. package/runtime/persistent/native-proxy.mjs +1 -1
  90. package/runtime/persistent/watchdog.mjs +12 -27
  91. package/runtime/prewarm.mjs +35 -0
  92. package/runtime/project-lock.mjs +71 -0
  93. package/runtime/request-transport.mjs +112 -32
  94. package/runtime/rsc-worker.mjs +58 -8
  95. package/runtime/storage-path.mjs +36 -0
  96. package/runtime/trace.mjs +44 -4
  97. package/runtime/worker.mjs +37 -17
  98. package/vendor/fontkit/NOTICE.txt +5 -0
  99. package/vendor/fontkit/SOURCE-NOTICES.txt +209 -0
  100. package/vendor/fontkit/index.mjs +3 -0
  101. package/vendor/fontkit/licenses/@swc__helpers/LICENSE +201 -0
  102. package/vendor/fontkit/licenses/base64-js/LICENSE +21 -0
  103. package/vendor/fontkit/licenses/brotli/readme.md +61 -0
  104. package/vendor/fontkit/licenses/clone/LICENSE +18 -0
  105. package/vendor/fontkit/licenses/dfa/README.md +83 -0
  106. package/vendor/fontkit/licenses/fast-deep-equal/LICENSE +21 -0
  107. package/vendor/fontkit/licenses/fontkit/README.md +266 -0
  108. package/vendor/fontkit/licenses/restructure/LICENSE +21 -0
  109. package/vendor/fontkit/licenses/tiny-inflate/LICENSE +21 -0
  110. package/vendor/fontkit/licenses/tslib/LICENSE.txt +12 -0
  111. package/vendor/fontkit/licenses/unicode-properties/LICENSE +8 -0
  112. package/vendor/fontkit/licenses/unicode-trie/LICENSE +8 -0
  113. package/vendor/fontkit/upstream.json +215 -0
@@ -3,7 +3,7 @@
3
3
  const WINDOW = 32, MAX_VALUE = 2 * 1024 * 1024, MAX_RESPONSE = 16 * 1024 * 1024;
4
4
  const TYPE = 'application/x-prnext-flight-delta';
5
5
  const MAGIC = new Uint8Array([80,82,68,49]);
6
- const BLOCK_BYTES = 32 * 1024, MAX_COMMANDS_PER_CHUNK = 256, MAX_COMMANDS = 2048;
6
+ const BLOCK_BYTES = 32 * 1024, MAX_COMMANDS_PER_CHUNK = 256, MAX_COMMANDS = 2048, MAX_CAPTURE_PARTS = 2048;
7
7
  let POWER = 1; for(let n=1;n<WINDOW;n++) POWER = Math.imul(POWER,31) >>> 0;
8
8
  function hash(bytes,offset) { let result=0; for(let n=0;n<WINDOW;n++) result=(Math.imul(result,31)+bytes[offset+n])>>>0; return result; }
9
9
  function createIndex(base) {
@@ -44,12 +44,47 @@ function blocks(parts) {
44
44
  for(const part of parts)for(let offset=0;offset<part.length;){const n=Math.min(BLOCK_BYTES-size,part.length-offset);pending.push(part.subarray(offset,offset+n));size+=n;offset+=n;if(size===BLOCK_BYTES)flush();}
45
45
  flush();return output;
46
46
  }
47
- function collectStream(source,{onComplete,onCancel,onFinally,capture=true}={}) {
48
- const reader=source.getReader(), parts=[];let size=0,complete=false,finished=false;
47
+ // Only the worker bridge registers streams here. Their backing allocations
48
+ // crossed a transfer boundary, have no producer aliases, and the framework's
49
+ // outgoing transport does not mutate or transfer them again. Arbitrary React,
50
+ // fetch and application streams retain the defensive per-chunk copy.
51
+ // Even an owned partial view is copied: retaining its much larger backing
52
+ // allocation would defeat the byte-based capture limit.
53
+ const ownedStreams=new WeakSet();
54
+ function ownFlightStream(source){ownedStreams.add(source);return source;}
55
+ function collectStream(source,{onComplete,onEnd,onCancel,onFinally,capture=true}={}) {
56
+ const owned=ownedStreams.has(source);
57
+ const reader=source.getReader(), parts=[];let size=0,complete=false,finished=false,cancelled=false;
49
58
  const finish=()=>{if(!finished){finished=true;onFinally?.();}};
50
59
  return new ReadableStream({
51
- async pull(controller) {try{const next=await reader.read();if(next.done){complete=true;if(capture&&size<=MAX_VALUE)onComplete?.(join(parts,size));parts.length=0;finish();controller.close();return;}if(!(next.value instanceof Uint8Array))throw new Error('Invalid Flight bytes');size+=next.value.length;if(size>MAX_RESPONSE)throw new Error('Flight exceeds transport budget');if(capture&&size<=MAX_VALUE)parts.push(next.value.slice());else parts.length=0;controller.enqueue(next.value);}catch(error){parts.length=0;finish();controller.error(error);await reader.cancel(error).catch(()=>{});}},
52
- async cancel(reason){parts.length=0;finish();if(!complete)onCancel?.();await reader.cancel(reason);},
60
+ async pull(controller) {
61
+ try {
62
+ const next=await reader.read();
63
+ if(cancelled)return;
64
+ if(next.done){
65
+ complete=true;
66
+ if(capture)onComplete?.(join(parts,size));
67
+ parts.length=0;onEnd?.();finish();controller.close();return;
68
+ }
69
+ const value=next.value;
70
+ if(!(value instanceof Uint8Array))throw new Error('Invalid Flight bytes');
71
+ size+=value.length;
72
+ if(size>MAX_RESPONSE)throw new Error('Flight exceeds transport budget');
73
+ if(capture){
74
+ // Limits include the number of retained allocations, not just their
75
+ // payload bytes. An abandoned capture never interrupts the response.
76
+ if(size>MAX_VALUE||value.length&&parts.length>=MAX_CAPTURE_PARTS){capture=false;parts.length=0;}
77
+ else if(value.length){
78
+ const wholeOwned=owned&&value.buffer instanceof ArrayBuffer&&value.byteOffset===0&&value.byteLength===value.buffer.byteLength;
79
+ // Buffer.slice() aliases its producer's backing allocation. The
80
+ // plain Uint8Array constructor always copies just the visible bytes.
81
+ parts.push(wholeOwned?value:new Uint8Array(value));
82
+ }
83
+ }
84
+ controller.enqueue(value);
85
+ }catch(error){parts.length=0;finish();if(cancelled)return;controller.error(error);await reader.cancel(error).catch(()=>{});}
86
+ },
87
+ async cancel(reason){cancelled=true;parts.length=0;finish();if(!complete)onCancel?.();await reader.cancel(reason);},
53
88
  },{highWaterMark:0});
54
89
  }
55
90
  async function deltaStream(source,base,{assess}={}) {
@@ -58,10 +93,10 @@ async function deltaStream(source,base,{assess}={}) {
58
93
  // A short first chunk always selects ordinary Flight. Do not build a base
59
94
  // index that this response cannot use, or wait for a later streaming chunk.
60
95
  let index=first.value.length>=WINDOW&&base instanceof Uint8Array&&base.length<=MAX_VALUE?createIndex(base):null;
61
- const profitable=(raw,frames,initial)=>frames&&frames.reduce((n,b)=>n+b.length,0)+(initial?5:1)<raw.length*0.9&&(!assess||assess(raw,join(initial?[MAGIC,...frames,new Uint8Array([2])]:frames,frames.reduce((n,b)=>n+b.length,0)+(initial?5:0)))===true);
96
+ const profitable=async(raw,frames,initial)=>frames&&frames.reduce((n,b)=>n+b.length,0)+(initial?5:1)<raw.length*0.9&&(!assess||await assess(raw,join(initial?[MAGIC,...frames,new Uint8Array([2])]:frames,frames.reduce((n,b)=>n+b.length,0)+(initial?5:0)))===true);
62
97
  const firstFrames=index&&first.value.length>=WINDOW?encodeChunk(first.value,base,index):null;
63
98
  let useDelta;
64
- try{useDelta=!!profitable(first.value,firstFrames,true);}catch(error){await reader.cancel(error).catch(()=>{});throw error;}
99
+ try{useDelta=!!(await profitable(first.value,firstFrames,true));}catch(error){await reader.cancel(error).catch(()=>{});throw error;}
65
100
  let pending=useDelta?blocks([MAGIC,...firstFrames]):[first.value], pendingAt=0,ended=false,tail=false,commands=firstFrames?.commands||0;
66
101
  if(!useDelta){base=undefined;index=undefined;}
67
102
  return {delta:!!useDelta,body:new ReadableStream({
@@ -69,7 +104,7 @@ async function deltaStream(source,base,{assess}={}) {
69
104
  if(pendingAt===pending.length&&!ended){pending=[];pendingAt=0;const next=await reader.read();
70
105
  if(next.done){ended=true;if(useDelta&&!tail)pending=[new Uint8Array([2])];}
71
106
  else if(useDelta&&!tail){const encoded=encodeChunk(next.value,base,index);commands+=encoded?.commands||0;
72
- if(commands>MAX_COMMANDS||!profitable(next.value,encoded,false)){tail=true;index=undefined;base=undefined;pending=blocks([new Uint8Array([3]),next.value]);}
107
+ if(commands>MAX_COMMANDS||!await profitable(next.value,encoded,false)){tail=true;index=undefined;base=undefined;pending=blocks([new Uint8Array([3]),next.value]);}
73
108
  else pending=blocks(encoded);
74
109
  }else pending=[next.value];
75
110
  }
@@ -134,19 +169,19 @@ function createFlightTransport() {
134
169
  let current,serial=0;
135
170
  return {
136
171
  snapshot(){return current;},
137
- async response(response,base) {
172
+ async response(response,base,{onEnd}={}) {
138
173
  const version=++serial;
139
174
  const token=response.headers.get('x-prnext-flight-token'), headers=new Headers(response.headers);
140
175
  let source=response.body;
141
- if(!source)return response;
176
+ if(!source){onEnd?.();return response;}
142
177
  if(headers.get('content-type')?.startsWith(TYPE)) {
143
178
  if(!base||headers.get('x-prnext-flight-base')!==base.token){await source.cancel();throw new Error('Missing Flight delta base');}
144
179
  source=decodeStream(source,base.bytes);
145
180
  }
146
181
  headers.set('content-type','text/x-component; charset=utf-8');headers.delete('content-length');headers.delete('content-encoding');
147
- return new Response(collectStream(source,{capture:!!token&&/^[a-f0-9]{48}$/.test(token),onComplete:bytes=>{if(version===serial&&token&&/^[a-f0-9]{48}$/.test(token))current={token,bytes};}}),{status:response.status,statusText:response.statusText,headers});
182
+ return new Response(collectStream(source,{capture:!!token&&/^[a-f0-9]{48}$/.test(token),onEnd,onComplete:bytes=>{if(version===serial&&token&&/^[a-f0-9]{48}$/.test(token))current={token,bytes};}}),{status:response.status,statusText:response.statusText,headers});
148
183
  },
149
184
  clear(){serial++;current=undefined;},
150
185
  };
151
186
  }
152
- module.exports={TYPE,MAX_VALUE,createIndex,encodeChunk,deltaStream,decodeStream,collectStream,createFlightTransport};
187
+ module.exports={TYPE,MAX_VALUE,createIndex,encodeChunk,deltaStream,decodeStream,collectStream,createFlightTransport,ownFlightStream};
package/compat/index.d.ts CHANGED
@@ -14,6 +14,8 @@ export interface InstantSample {
14
14
  export type InstantConfig = boolean | { level?: 'warning' | 'experimental-error'; unstable_samples?: InstantSample[]; unstable_disableValidation?: true; unstable_disableDevValidation?: true; unstable_disableBuildValidation?: true };
15
15
 
16
16
  export interface ReactCompilerOptions {
17
+ /** Babel by default. The native Oxc backend is experimental and opt-in. */
18
+ backend?: 'babel' | 'oxc';
17
19
  compilationMode?: 'infer' | 'annotation';
18
20
  panicThreshold?: 'none' | 'critical_errors' | 'all_errors';
19
21
  /** PRNext requires matching React 19 peers. */
@@ -121,6 +123,8 @@ export interface Rewrite extends CustomRoute { destination: string }
121
123
  export type ConfigRedirect = CustomRoute & Redirect;
122
124
  /** Implemented project options. Unsupported behavior-changing options fail the build. */
123
125
  export interface NextConfig {
126
+ /** Public origin and transport peers permitted to set X-Forwarded-Host/Proto. */
127
+ server?: {publicOrigin?: string; trustedProxies?: readonly string[]};
124
128
  /** PRNext only. CLI > environment > build configuration > profile.
125
129
  * 0 disables the corresponding retirement; keepWarm retains one lane per used host.
126
130
  */
@@ -131,6 +135,10 @@ export interface NextConfig {
131
135
  workerPreload?: false | { routes: string[]; maxRssMb?: number; timeoutMs?: number };
132
136
  /** Keep bounded previous public hashed assets. false disables carry-over. Defaults: one hour, two generations, 64 MiB. */
133
137
  deploymentRetention?: false | {maxAge?: number; maxGenerations?: number; maxBytes?: number};
138
+ /** Common disk budget for regenerable compiler caches, in bytes (default 256 MiB).
139
+ * Applied after builds; active dev sessions/workers can defer reclamation.
140
+ * Runtime data, invalidations, page and image caches are excluded. */
141
+ compilationCache?: {maxDiskSize?: number};
134
142
  /** PRNext production prerender processes; maxMemoryMb bounds their combined observed memory. */
135
143
  prerender?: {workers?: number; maxMemoryMb?: number};
136
144
  /** Explicit App route patterns that must produce complete static HTML without browser JavaScript. */
@@ -167,7 +175,20 @@ export interface NextConfig {
167
175
  implementation?: 'sass' | 'sass-embedded';
168
176
  [option: string]: unknown;
169
177
  };
170
- experimental?: { nextScriptWorkers?: boolean; /** App Router development only; compile literal client imports on first use. */ lazyDynamicImports?: boolean; optimizePackageImports?: string[]; proxyClientMaxBodySize?: number | string; serverActions?: true | { allowedOrigins?: string[]; bodySizeLimit?: number | string } };
178
+ experimental?: {
179
+ nextScriptWorkers?: boolean;
180
+ /** App Router development only; compile literal client imports on first use. */
181
+ lazyDynamicImports?: boolean;
182
+ /** Start App Route Handlers before upload EOF; mutations must handle aborted uploads. */
183
+ streamingRequestBodies?: boolean;
184
+ /** Serve certified public PPR document shells before starting Node. Requires cacheComponents. */
185
+ nativePprShell?: boolean;
186
+ /** Bound independent production CSS builds after graph discovery. Disabled by default. */
187
+ parallelBuilds?: boolean | {maxWorkers?: number; maxMemoryMb?: number};
188
+ optimizePackageImports?: string[];
189
+ proxyClientMaxBodySize?: number | string;
190
+ serverActions?: true | {allowedOrigins?: string[]; bodySizeLimit?: number | string};
191
+ };
171
192
  trailingSlash?: boolean;
172
193
  skipTrailingSlashRedirect?: boolean;
173
194
  skipMiddlewareUrlNormalize?: boolean;
@@ -216,6 +237,7 @@ export interface StaticBuildOptions {
216
237
  }
217
238
  export interface ReactCompilerReport {
218
239
  version: 1;
240
+ backend?: 'babel' | 'oxc';
219
241
  compilerVersion: string | null;
220
242
  enabled: boolean;
221
243
  buildId: string | null;
@@ -227,6 +249,7 @@ export interface ReactCompilerReport {
227
249
  summary: {files: number; functions: number; optimized: number; skipped: number; errors: number; diagnostics: number; mixed: number; transforms: number; cacheReuses: number};
228
250
  files: Array<{
229
251
  file: string; transforms: number; cacheReuses: number; targets: string[]; notes: string[];
252
+ diagnostics?: Array<{kind: 'CompileError' | 'CompileDiagnostic'; status: 'error' | 'diagnostic'; reason: string | null; category: string | null; targets: Array<'browser' | 'node' | 'edge' | 'unspecified'>}>;
230
253
  functions: Array<{
231
254
  name: string | null;
232
255
  start: {line: number; column: number} | null;
@@ -1,9 +1,16 @@
1
1
  'use strict';
2
2
  const { createHmac } = require('node:crypto');
3
+ function developmentNavigationSecret(actionKey) {
4
+ if (typeof actionKey !== 'string' || !actionKey) return undefined;
5
+ // The compiler retains this private key across generations of one dev
6
+ // session. Derive a separate purpose key so worker replacement cannot look
7
+ // like an account change, without publishing or reusing the encryption key.
8
+ return createHmac('sha256', actionKey).update('prnext:development:navigation-authorization:v1').digest('hex');
9
+ }
3
10
  function navigationAuthorization(context, secret, cookieNames) {
4
11
  if (!secret || context?.staticState) return undefined;
5
12
  const names = cookieNames === undefined ? null : new Set(cookieNames);
6
13
  const cookies = context.cookies.getAll().filter(cookie => !names || names.has(cookie.name)).map(({ name, value }) => [name, value]).sort((a, b) => a[0].localeCompare(b[0]));
7
14
  return createHmac('sha256', secret).update(JSON.stringify([cookies, context.headers.get('authorization') || ''])).digest('hex');
8
15
  }
9
- module.exports = { navigationAuthorization };
16
+ module.exports = { navigationAuthorization, developmentNavigationSecret };
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ // Constructed only for opt-in diagnostics. Samples contain numeric runtime
4
+ // counters, never application values, request context or allocation stacks.
5
+ function createRuntimeSampler() {
6
+ const { PerformanceObserver, performance } = require('node:perf_hooks');
7
+ const { threadId, resourceLimits } = require('node:worker_threads');
8
+ const { getHeapSpaceStatistics } = require('node:v8');
9
+ let count = 0, durationMs = 0, maxPauseMs = 0;
10
+ let previousTime = performance.now();
11
+ let previousLoop = performance.eventLoopUtilization();
12
+ let previousCpu = threadId === 0 ? process.cpuUsage() : undefined;
13
+ const consume = entries => {
14
+ for (const entry of entries) {
15
+ count++;
16
+ durationMs += entry.duration;
17
+ maxPauseMs = Math.max(maxPauseMs, entry.duration);
18
+ }
19
+ };
20
+ const observer = new PerformanceObserver(list => consume(list.getEntries()));
21
+ observer.observe({ entryTypes: ['gc'] });
22
+ return {
23
+ sample() {
24
+ consume(observer.takeRecords());
25
+ const now = performance.now();
26
+ const loop = performance.eventLoopUtilization();
27
+ const eventLoop = performance.eventLoopUtilization(loop, previousLoop);
28
+ const { rss, heapUsed, heapTotal, external, arrayBuffers } = process.memoryUsage();
29
+ const young = getHeapSpaceStatistics().find(space => space.space_name === 'new_space');
30
+ const value = {
31
+ node: process.version, v8: process.versions.v8, threadId,
32
+ intervalMs: now - previousTime,
33
+ rssBytes: rss, heapUsedBytes: heapUsed, heapTotalBytes: heapTotal,
34
+ externalBytes: external, arrayBufferBytes: arrayBuffers,
35
+ youngSpaceBytes: young?.space_size ?? 0,
36
+ workerLimits: { ...resourceLimits },
37
+ gc: { count, durationMs, maxPauseMs },
38
+ eventLoop,
39
+ };
40
+ // CPU usage is process-wide, including all worker threads. Emit it only
41
+ // in the main isolate so aggregators cannot count the RSC CPU twice.
42
+ if (previousCpu) {
43
+ const cpu = process.cpuUsage();
44
+ value.cpu = { scope: 'process', userMicros: cpu.user - previousCpu.user,
45
+ systemMicros: cpu.system - previousCpu.system };
46
+ previousCpu = cpu;
47
+ }
48
+ previousLoop = loop; previousTime = now;
49
+ count = durationMs = maxPauseMs = 0;
50
+ return value;
51
+ },
52
+ close() { observer.disconnect(); },
53
+ };
54
+ }
55
+
56
+ module.exports = { createRuntimeSampler };
@@ -93,14 +93,17 @@ async function invokeCache(id, kind, captures, args, callback, segment = '') {
93
93
  then(_resolve, reject) { reject(new Error("searchParams cannot be read inside 'use cache'; read them outside and pass their values as arguments")); },
94
94
  };
95
95
  if (encoded.key.length > MAX_BYTES) throw new Error("'use cache' arguments exceed 2 MiB");
96
- const key = createHash('sha256').update(id).update(kind).update(codec === plainCodec ? ':v8:' : ':flight:').update(encoded.key).digest('hex');
96
+ const format=codec===plainCodec?':v8:':codec.cacheKeyVersion==='flight-primitives-v2'?':flight-primitives-v2:':':flight:';
97
+ const key = createHash('sha256').update(id).update(kind).update(format).update(encoded.key).digest('hex');
97
98
  const profiles = context.cacheLife || context.manifest?.config?.cacheLife || defaults;
98
99
  const scope = { kind, profiles, life: { ...profiles.default }, explicit: new Set(), tags: new Set() };
99
100
  let producedMetadata;
100
101
  const producer = async () => runCacheScope(async () => {
101
102
  const value = await codec.render(() => callback(...encoded.values), encoded, context);
102
103
  producedMetadata = metadata(scope, context);
103
- return envelope(producedMetadata, Buffer.from(value));
104
+ // envelope owns its result immediately; an already owned codec Buffer need
105
+ // not be copied into another temporary Buffer before this synchronous copy.
106
+ return envelope(producedMetadata, Buffer.isBuffer(value)?value:Buffer.from(value));
104
107
  }, scope);
105
108
  let bytes, handlerMetadata;
106
109
  const custom = kind !== 'private' && !context.draftMode && Object.hasOwn(configured(context), kind);
@@ -18,6 +18,13 @@ transpilation, image/font tooling and caching deserve targeted build and browser
18
18
  tests. Support for one configuration shape does not imply support for all
19
19
  upstream plugin behavior.
20
20
 
21
+ When moving from 0.1.19-alpha, use the installed
22
+ [upgrade checklist](deployment.md#upgrading-from-0119-alpha): rebuild matching
23
+ artifacts, configure the public HTTPS origin and proxy trust, verify strict
24
+ account transitions and review saved persistent health routes. Storage cleanup
25
+ must preserve runtime data and invalidation history. Experimental performance
26
+ options remain independent and opt-in.
27
+
21
28
  ## Instrumentation and tracing
22
29
 
23
30
  Use a single `instrumentation.ts`, `.js`, `.mjs` or `.cjs` at the project root
@@ -97,7 +104,7 @@ implemented through PRNext's generated route registry. Do not copy a full
97
104
  Next.js configuration template into a new application.
98
105
 
99
106
  The repository's reference tests build and run matching applications against
100
- Next.js 16.4.3, including mounted App navigation, form Actions and cookies,
107
+ Next.js 16.4.4, including mounted App navigation, form Actions and cookies,
101
108
  metadata, Pages locale routing, redirects, APIs and selected webpack loader
102
109
  contracts. These executable examples cover specific behavior; they do not
103
110
  certify every Next.js application or plugin. `NextRequest.nextUrl` understands
@@ -105,10 +112,10 @@ data-route build IDs and locales, and request-cookie mutations synchronize the
105
112
  request header. Request cookies have different mutation results from response
106
113
  cookies; do not use them interchangeably.
107
114
 
108
- A locked real-application fixture additionally exercises Better Auth 1.7.14
115
+ A locked real-application fixture additionally exercises Better Auth 1.7.15
109
116
  (email/password, session cookies and logout), Drizzle ORM 0.45.4 over SQLite
110
117
  through its proxy driver, next-intl 4.14.9 (server and client translations),
111
- and the MDX 3.1.1 webpack loader against Next.js 16.4.3. It covers authenticated
118
+ and the MDX 3.1.1 webpack loader against Next.js 16.4.4. It covers authenticated
112
119
  Server Actions, user-owned rows, cookie/locale isolation, client navigation and
113
120
  Server Component `after()` callbacks. This is evidence for those workflows,
114
121
  not a guarantee for every plugin, provider or deployment configuration.
@@ -132,6 +139,66 @@ new source files and configuration changes restart the native watch generation.
132
139
  adding its generated declaration to the config. Compiler support does not imply
133
140
  that every application is ready for TypeScript 7's own language/config changes.
134
141
 
142
+ ## Experimental performance paths
143
+
144
+ These options are independent and disabled by default. Enable them in the
145
+ application's existing configuration, then qualify its actual workflows:
146
+
147
+ ```js
148
+ export default {
149
+ cacheComponents: true,
150
+ experimental: {
151
+ nativePprShell: true,
152
+ streamingRequestBodies: true,
153
+ parallelBuilds: {maxWorkers: 4, maxMemoryMb: 1024},
154
+ },
155
+ };
156
+ ```
157
+
158
+ `nativePprShell` lets Rust send the exact certified build shell before starting
159
+ Node, then resume its React continuation. The initial implementation covers
160
+ concrete App PPR document GETs without query strings, cookies, authorization,
161
+ middleware, rewrites, base paths, router-state/Flight/prefetch headers or CSP
162
+ nonces. Generic parameter shells, Edge, external caches and unsupported requests
163
+ keep the ordinary Node path. Prefixes and artifacts have checked hashes and
164
+ bounded sizes; continuations use a private transport field, never an HTTP flag.
165
+ Responses remain private and no-store. The cache generation must still be zero
166
+ and the build artifact unexpired: any persisted invalidation disables this build
167
+ snapshot optimization, including across server restarts. It does not disable
168
+ PPR itself or invalidate an already committed response. Requests already using
169
+ a snapshot finish with that same artifact when an invalidation races them.
170
+
171
+ `streamingRequestBodies` makes `Request.body` available to Node App Route
172
+ Handlers before upload EOF. It negotiates its own protocol capability and sends
173
+ at most one 64 KiB frame per reader credit. Unsupported workers use the existing
174
+ buffered path. Server Actions, Pages APIs and middleware-buffered requests retain
175
+ their previous buffering behavior. Streaming handlers can begin application
176
+ effects before a truncated or excessive upload is rejected: validate the input
177
+ and complete authorization before committing a mutation. The total request limit
178
+ remains 32 MiB. Existing ingress byte reservations are preserved; this feature
179
+ does not promise a smaller V8 heap or higher memory admission concurrency. A
180
+ cancelled or early-returned upload closes its single-use lane without replaying
181
+ the mutation, while retaining the healthy Node host.
182
+
183
+ `parallelBuilds` freezes App stylesheet plans after dependency discovery,
184
+ deduplicates outputs and emits independent jobs with deterministic linking.
185
+ Graph discovery itself remains ordered. Concurrency is bounded by CPU count,
186
+ available memory and the configured worker/budget limits. `maxWorkers` accepts
187
+ 1–8; `maxMemoryMb` accepts 256–8192 and is an admission estimate, not a hard RSS
188
+ limit for third-party code. Custom webpack/loaders, custom PostCSS pipelines and
189
+ Sass callbacks retain serial execution. A failed job stops new admission and
190
+ settles started jobs before the staging build is discarded; the last published
191
+ build is preserved. An enabled build records its actual stylesheet job count
192
+ and selected concurrency in `manifest.buildParallelism`.
193
+
194
+ Development refreshes can also target the routes that import a changed Server
195
+ Action or its transitive dependencies. Certification uses both dependency
196
+ graphs before and after a change. An unrelated tab still applies a deferred
197
+ generation before navigating to a changed route. Unknown or incomplete graphs,
198
+ binding/runtime/route changes, global configuration, rewrites/middleware,
199
+ internationalized or parallel/intercepting routing retain a global refresh.
200
+ Server generations still publish as a whole; pending mutations are never replayed.
201
+
135
202
  ## React Compiler
136
203
 
137
204
  Enable the bundled official React Compiler in the project's existing config:
@@ -142,7 +209,7 @@ export default {
142
209
  };
143
210
  ```
144
211
 
145
- No separate compiler installation is required. The default is disabled;
212
+ The default Babel backend needs no separate compiler installation. The default is disabled;
146
213
  `reactCompiler: false` preserves the uncompiled component path. Enabled builds
147
214
  optimize project-owned browser components in both Pages and App applications,
148
215
  in development and production. App Client Components also use the transform in
@@ -153,17 +220,43 @@ Components, Server Action modules, PRNext internals, generated entrypoints and
153
220
  npm packages are not compiled. Keep the matching React 19 peer versions required
154
221
  by the installed framework.
155
222
 
156
- Ordinary `.mts`/`.cts` hook helpers can be optimized; server/client directives
157
- in those extensions remain unsupported. Decorated `.mts`/`.cts` utility modules
223
+ With the default Babel backend, ordinary `.mts`/`.cts` hook helpers can be
224
+ optimized; server/client directives in those extensions remain unsupported.
225
+ Decorated `.mts`/`.cts` utility modules
158
226
  keep their native compilation path while importing React components can still
159
227
  be optimized.
160
228
 
161
229
  For gradual adoption, use `reactCompiler: { compilationMode: 'annotation' }`, then
162
230
  put `"use memo";` at the beginning of each component or hook to optimize. In
163
231
  either mode, `"use no memo";` excludes an individual function. Supported object
164
- options are `compilationMode: 'infer' | 'annotation'`,
232
+ options are `backend: 'babel' | 'oxc'`, `compilationMode: 'infer' | 'annotation'`,
165
233
  `panicThreshold: 'none' | 'critical_errors' | 'all_errors'` and `target: '19'`.
166
- Defaults are `'infer'`, `'none'` and `'19'`; other fields are rejected.
234
+ Defaults are Babel, `'infer'`, `'none'` and `'19'`; other fields are rejected.
235
+
236
+ The experimental native Rust backend is an explicit alternative:
237
+
238
+ ```sh
239
+ npm install --save-dev oxc-transform-react@0.157.0
240
+ ```
241
+
242
+ ```js
243
+ export default {
244
+ reactCompiler: { backend: 'oxc' },
245
+ };
246
+ ```
247
+
248
+ Oxc is an optional peer, so ordinary Babel builds do not install its compiler,
249
+ WASM distribution or native binding. Run the installation above in the application
250
+ project on the build machine, with optional platform dependencies enabled. A
251
+ global CLI also resolves this project-local peer. This option
252
+ requires the native binding; an absent or incompatible compiler, missing binding
253
+ or compilation failure fails explicitly. PRNext never silently changes the backend. JSX remains available
254
+ to the existing graph compiler, while this backend removes TypeScript syntax.
255
+ App Client Component server rendering uses Oxc's SSR output; browser graphs use
256
+ its client output. The same project boundaries and Server Action exclusions
257
+ apply. Source maps, loader maps and the existing Fast Refresh pipeline remain
258
+ part of the compilation path. Unsupported options are rejected rather than
259
+ passed through to the native compiler.
167
260
 
168
261
  After a successful build, inspect the compiler's actual results:
169
262
 
@@ -172,8 +265,8 @@ prnext compiler
172
265
  prnext compiler --json
173
266
  ```
174
267
 
175
- The report names observed functions, their project-relative files, optimization
176
- or skip status, and the compiler's reasons. These are transform decisions;
268
+ For Babel, the report names observed functions, their project-relative files,
269
+ optimization or skip status, and the compiler's reasons. These are transform decisions;
177
270
  server-code removal and tree shaking can later omit a function from the browser
178
271
  bundle. Cached transforms retain their
179
272
  diagnostics and are identified as reused. Browser and server graphs do not
@@ -183,6 +276,12 @@ complete application coverage. Disabled compilation is reported explicitly.
183
276
  The report describes the last successful build, stays outside public assets and
184
277
  does not include source code. A failed build preserves the previous report.
185
278
 
279
+ The Oxc report identifies the backend and package version, records native
280
+ diagnostics at file scope, and states that function optimization totals are
281
+ unavailable: its API does not expose Babel's function-success events. This is
282
+ not a count of zero optimized functions. Cached native diagnostics are replayed;
283
+ cache identities include the backend, compiler version and client/SSR mode.
284
+
186
285
  The compiler can skip code that cannot be safely optimized. Follow the Rules of
187
286
  React and check actual behavior rather than assuming every component was
188
287
  compiled. It reduces repeated component work; it does not guarantee faster
@@ -192,6 +291,10 @@ production build with the option enabled and disabled. Compilation adds build
192
291
  work; PRNext caches pure transforms with their compiler version and settings.
193
292
 
194
293
  See the [official React Compiler documentation](https://react.dev/learn/react-compiler).
294
+ The [Oxc React Compiler guide](https://oxc.rs/docs/guide/usage/transformer/react-compiler.html)
295
+ describes the experimental backend. Measure compilation and application behavior
296
+ with the selected backend before adopting it; selecting Rust alone is not a
297
+ measured speedup for a project.
195
298
 
196
299
  Only explicit private Next.js import adapters are supported. Do not add arbitrary
197
300
  `next/dist/...` imports or install another Next runtime to satisfy one: isolate
@@ -8,12 +8,93 @@ Build with the installed PRNext and run the resulting trusted output with its
8
8
  matching runtime:
9
9
 
10
10
  ```sh
11
+ prnext check --production
11
12
  prnext typecheck
12
13
  prnext build
13
14
  prnext output --json
14
15
  prnext start --hostname 127.0.0.1 --port 3000
15
16
  ```
16
17
 
18
+ ### Upgrading from an earlier alpha
19
+
20
+ Install matching framework/native versions, then rebuild and restart the complete
21
+ application or standalone output. Keep Node and the React protocol peers aligned
22
+ with this package's metadata. Existing compiler entries are reused only when
23
+ their identities match; never delete `data.sqlite3` or invalidation history to
24
+ perform an upgrade. Read the storage section below before pruning compiler caches.
25
+
26
+ Persistent apps need a supervisor restart too. In a maintenance window for the
27
+ whole `PRNEXT_PM_HOME`, run the old installation's `prnext pdown` with that same
28
+ home before replacing packages. The 0.1.21 local protocol rejects an old
29
+ supervisor rather than silently taking it over. Shutdown stops all managed apps
30
+ and preserves saved settings and enabled
31
+ entries; `prestart` alone keeps the already loaded supervisor code. Update and
32
+ rebuild the installations that must return with the new version, then launch `pstart` or
33
+ `prestart` through the new CLI. It also restores all enabled entries, with their
34
+ saved binary paths and health routes. Check `pstatus` and real health responses;
35
+ a global CLI update does not replace each app's package or binary. Keep independent
36
+ lifecycles in separate homes. Do not stop unrelated Node processes or external
37
+ databases, Redis, proxies and OS services as an automatic upgrade fallback.
38
+
39
+ Set `server.publicOrigin` to the external HTTPS origin and `server.trustedProxies`
40
+ to the immediate proxy's IP/CIDR addresses. The configured origin wins; forwarded
41
+ host/protocol headers are trusted only from those peers. Rebuild after config
42
+ changes and check redirects, secure cookies and authentication callbacks over
43
+ the actual public URL. This does not establish a forwarded client-IP chain.
44
+
45
+ Run production preflight, then test account A → sign out → account B with the
46
+ actual `navigationCache.authCookies`, including shared layouts and browser
47
+ history. Changed or unknown strict identities require a fresh document; cached
48
+ PPR previews wait for fresh identity. Authorize protected reads and writes in
49
+ the application as before.
50
+
51
+ Use a dedicated complete HTTP 200 application health response for database/service
52
+ readiness. `/_prnext/health` checks native readiness only; redirects no longer
53
+ pass persistent health checks. Previously saved applications keep their health
54
+ path, so a redirecting `/` must be replaced in the saved persistent settings.
55
+ Observe the post-switch rollback window and exercise an explicit idempotent
56
+ retry; a lost response can follow a successful write, and mutations are never
57
+ automatically replayed. Keep schema changes compatible with the previous server.
58
+
59
+ ### Production preflight
60
+
61
+ Run `prnext check --production --json` to collect deployment diagnostics without
62
+ building or rewriting the application. It evaluates the production config and
63
+ environment files, restores the calling process environment afterward, and checks
64
+ installed dependencies, route conventions, the public origin, proxy trust,
65
+ authentication retention settings and active experimental options. JSON diagnostics
66
+ include `code`, `path`, `severity` and an action where one is needed. Errors fail
67
+ the command; warnings require review and do not change its exit status.
68
+
69
+ If a build already exists, preflight also compares its public origin, proxy and
70
+ authentication retention settings with the current config and rejects a development artifact. Rebuild after
71
+ changing deployment settings: `prnext start` reads the selected artifact and does
72
+ not reevaluate the source configuration. Missing output is informational before
73
+ the first build; unreadable existing metadata requires review.
74
+
75
+ For authenticated App Router applications, explicitly list every session cookie
76
+ name, including HTTPS variants used by the authentication library:
77
+
78
+ ```js
79
+ export default {
80
+ navigationCache: {
81
+ authCookies: ['session-cookie-name', '__Secure-session-cookie-name'],
82
+ },
83
+ };
84
+ ```
85
+
86
+ Replace these example names with the application's actual cookies; PRNext does
87
+ not infer authentication from cookie names. This enables strict retained-page
88
+ state resets when the server's authorization identity changes. Test account A →
89
+ sign out → account B with both active and retained pages, and confirm that private
90
+ drafts disappear. Public applications can review the guidance or disable page
91
+ retention with `navigationCache: false`.
92
+
93
+ A successful preflight checks configuration, not external services or application
94
+ authorization. Qualify the final build over public HTTPS, including authentication,
95
+ health checks, failure recovery and rollback. Experimental warnings identify the
96
+ options that need workload-specific validation; they are not performance claims.
97
+
17
98
  The default build directory is `.prnext`; `distDir` can change it. The private
18
99
  project output selector is `.prnext-output.json`. Do not assume `.next` layout,
19
100
  run `next start` on a PRNext build or hand-edit its manifests to bypass runtime
@@ -254,6 +335,69 @@ an explicit choice, not a reason to print credentials or copy `.env` into assets
254
335
 
255
336
  ## Observe before tuning
256
337
 
338
+ ### Installation and disk storage
339
+
340
+ The default installation includes Babel React Compiler support. The Oxc React
341
+ Compiler backend is an optional peer installed explicitly; read the
342
+ [compatibility guide](compatibility.md) before selecting it. Keep optional native
343
+ platform dependencies enabled. The framework's internal Fontkit bundle includes
344
+ its provenance and dependency licenses under `vendor/fontkit`; applications do
345
+ not need a separate Fontkit dependency for `next/font`.
346
+
347
+ Production builds retain one OpenGraph renderer when needed. A bounded analysis
348
+ of the compiled server graph may omit it only when the application can be
349
+ certified not to use `ImageResponse`. Development, Edge, unknown external modules
350
+ and computed loading keep it. No configuration change is needed. Deploy the
351
+ standalone output alone when using `output: 'standalone'`; copying the complete
352
+ ordinary build beside it duplicates those artifacts.
353
+
354
+ Compiler caches share a **256 MiB** retention budget per project:
355
+
356
+ ```js
357
+ export default {
358
+ compilationCache: {maxDiskSize: 128 * 1024 * 1024}, // bytes, 0–4 GiB
359
+ };
360
+ ```
361
+
362
+ PRNext applies the policy after a build, including failures, and after a clean
363
+ development shutdown. It is a retention target, not a hard filesystem quota:
364
+ active development, pinned V8 workers, busy files or an incomplete bounded scan
365
+ can defer reclamation. Existing per-cache limits still apply. Eviction can make
366
+ the next build slower; evicted downloaded fonts need network access again.
367
+ Large JSON entries use bounded, checksummed compression; old raw records remain
368
+ readable, and corrupt/incompatible records are cache misses.
369
+
370
+ Inspect and preview changes without executing application configuration:
371
+
372
+ ```sh
373
+ prnext cache --json
374
+ prnext cache --prune --dry-run --max-bytes 67108864 --json
375
+ prnext cache --prune --max-bytes 67108864
376
+ ```
377
+
378
+ The CLI reads the policy saved by the last build. `--max-bytes` overrides only
379
+ that purge; `0` removes eligible compiler records. Statistics cover recognized
380
+ framework cache files, excluding unknown paths; `scanComplete: false` means a
381
+ lower bound and prevents purging from an incomplete snapshot. Stop development
382
+ before purging. Concurrent builds are protected by the publisher lock, active
383
+ managed workers pin their namespace, and failed deletions can be retried.
384
+
385
+ These commands never remove runtime data, `data.sqlite3`, page/image indexes,
386
+ invalidation generations, tags or fill leases. Never delete/reset the data
387
+ database to reclaim compiler storage: that can lose invalidation history. The
388
+ runtime instead performs small incremental SQLite reclamation steps and bounded
389
+ orphan-file maintenance on its own timer. Existing SQLite databases without
390
+ incremental auto-vacuum retain their existing format; no full VACUUM runs on a
391
+ request. Older page files without a verifiable reader lease are retained.
392
+
393
+ Builds and dev sessions also journal their owned temporary directories. After a
394
+ crash, later starts reclaim eligible entries only after a grace period and proof
395
+ that their local owner/workers have exited. Unknown directories, foreign-host or
396
+ unverifiable owners and uncommitted build backups are kept for recovery. Do not
397
+ remove a recovery backup until the selected build has been checked.
398
+
399
+ ### Runtime observations
400
+
257
401
  Production rendering, API and regeneration admission use bounded queues grouped
258
402
  by resolved route pattern. Different parameter values share one pattern's
259
403
  waiting budget. Waiting patterns take turns as capacity becomes available;
@@ -13,6 +13,11 @@ react-server-dom-webpack 19.3.1**. Its Node engine range is
13
13
  `^22.23.4 || ^24.22.1 || >=26.12.1`. Check the installed package manifest when
14
14
  upgrading; these requirements may change between releases.
15
15
 
16
+ Optional peers are installed only for the features that use them:
17
+ `@redis/client` for [shared Redis caching](data.md), and `oxc-transform-react`
18
+ for the [experimental Oxc React Compiler](compatibility.md#react-compiler).
19
+ The default Babel compiler needs no additional compiler installation.
20
+
16
21
  Published releases include a matching native component through optional
17
22
  dependencies for Windows, macOS and glibc Linux, on x64 and ARM64. Keep optional
18
23
  dependencies enabled. Consumers do not need Rust; building PRNext from its source