@thomas.f/prnext 0.1.16-alpha → 0.1.18-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 (215) hide show
  1. package/AI.md +63 -0
  2. package/README.md +35 -503
  3. package/build/ADAPTERS.md +27 -2
  4. package/build/actions.mjs +6 -1
  5. package/build/analyze.mjs +340 -0
  6. package/build/app-config.mjs +20 -8
  7. package/build/app-static.mjs +41 -14
  8. package/build/app-styles.mjs +79 -0
  9. package/build/app.mjs +99 -31
  10. package/build/build-lock.mjs +6 -6
  11. package/build/bundle-runtime.mjs +39 -11
  12. package/build/cache-components.mjs +1 -1
  13. package/build/cache-handlers.mjs +1 -1
  14. package/build/check.mjs +56 -11
  15. package/build/compact-css.mjs +53 -0
  16. package/build/compilation-cache.mjs +269 -0
  17. package/build/compilation-store.mjs +142 -0
  18. package/build/compiler.mjs +51 -4
  19. package/build/concurrent.mjs +12 -0
  20. package/build/config.mjs +50 -13
  21. package/build/create.mjs +239 -0
  22. package/build/css-modules.mjs +65 -10
  23. package/build/deployment-assets.mjs +89 -0
  24. package/build/deployment-config.mjs +18 -0
  25. package/build/dev-compiler-health.mjs +11 -0
  26. package/build/dev-css-update.mjs +85 -0
  27. package/build/dev-dependencies.mjs +44 -0
  28. package/build/dev-discovery.mjs +28 -0
  29. package/build/dev-worker.mjs +14 -5
  30. package/build/dev.mjs +64 -6
  31. package/build/dynamic.mjs +1 -1
  32. package/build/edge.mjs +5 -4
  33. package/build/fonts.mjs +1 -1
  34. package/build/framework-imports.mjs +50 -0
  35. package/build/html-only-config.mjs +53 -0
  36. package/build/html-only-graph.mjs +67 -0
  37. package/build/incremental.mjs +8 -4
  38. package/build/index.mjs +140 -112
  39. package/build/inspect.mjs +3 -0
  40. package/build/inspection-summary.mjs +37 -4
  41. package/build/instrumentation.mjs +30 -0
  42. package/build/loader-cache.mjs +53 -26
  43. package/build/metadata-routes.mjs +3 -4
  44. package/build/migrate.mjs +60 -57
  45. package/build/migration-audit.mjs +1 -1
  46. package/build/native-type-config.mjs +37 -0
  47. package/build/npm-packages.mjs +3 -18
  48. package/build/package-manager.mjs +308 -0
  49. package/build/persistent-cache.mjs +112 -0
  50. package/build/precompress.mjs +62 -30
  51. package/build/prerender-cache.mjs +70 -0
  52. package/build/prerender-config.mjs +29 -0
  53. package/build/prerender-pages.mjs +100 -0
  54. package/build/prerender-pool.mjs +165 -0
  55. package/build/prerender-process-worker.mjs +58 -0
  56. package/build/prerender-worker.mjs +10 -0
  57. package/build/production-builder.mjs +129 -0
  58. package/build/react-compiler-report.mjs +225 -0
  59. package/build/react-compiler.mjs +139 -0
  60. package/build/runtime-contract.mjs +2 -2
  61. package/build/scan.mjs +4 -4
  62. package/build/source-analysis.mjs +42 -0
  63. package/build/static-adapter.mjs +13 -5
  64. package/build/static-export.mjs +4 -4
  65. package/build/static-input.mjs +48 -0
  66. package/build/styles.mjs +17 -3
  67. package/build/transform-cache.mjs +37 -4
  68. package/build/transform.mjs +1 -1
  69. package/build/type-command.mjs +41 -0
  70. package/build/typed-routes.mjs +95 -0
  71. package/build/typescript-dev-worker.mjs +18 -0
  72. package/build/typescript-dev.mjs +49 -0
  73. package/build/typescript-native-worker.mjs +55 -0
  74. package/build/typescript-native.mjs +18 -0
  75. package/build/typescript-toolchain.mjs +19 -0
  76. package/build/typescript-watch.mjs +22 -0
  77. package/build/typescript.mjs +11 -8
  78. package/build/webpack-graph.mjs +170 -30
  79. package/build/worker-memory.mjs +9 -0
  80. package/build/worker-preload.mjs +9 -0
  81. package/cli.mjs +162 -122
  82. package/compat/activity-cache.cjs +26 -0
  83. package/compat/after.cjs +6 -4
  84. package/compat/app-layout-context.cjs +52 -2
  85. package/compat/base64.cjs +10 -0
  86. package/compat/cache-handlers.cjs +12 -2
  87. package/compat/cache-wire.cjs +28 -0
  88. package/compat/cache.cjs +2 -1
  89. package/compat/cache.d.cts +4 -0
  90. package/compat/cookies.cjs +21 -5
  91. package/compat/data-cache.cjs +12 -8
  92. package/compat/flight-delta.cjs +150 -0
  93. package/compat/headers.cjs +4 -2
  94. package/compat/incremental-cache.cjs +20 -7
  95. package/compat/index.d.ts +89 -10
  96. package/compat/link.cjs +12 -9
  97. package/compat/link.d.cts +4 -3
  98. package/compat/navigation-authorization.cjs +9 -0
  99. package/compat/navigation-stages.cjs +19 -0
  100. package/compat/navigation.d.cts +4 -3
  101. package/compat/offline-store.cjs +11 -0
  102. package/compat/offline.cjs +5 -0
  103. package/compat/offline.d.cts +2 -0
  104. package/compat/redis-cache.cjs +201 -0
  105. package/compat/redis-cache.d.cts +37 -0
  106. package/compat/router.d.cts +4 -3
  107. package/compat/server-primitives.cjs +31 -11
  108. package/compat/server.d.cts +15 -2
  109. package/compat/static-generation.cjs +7 -0
  110. package/compat/typed-route.d.cts +17 -0
  111. package/compat/use-cache.cjs +3 -0
  112. package/docs/ai/compatibility.md +275 -0
  113. package/docs/ai/data.md +288 -0
  114. package/docs/ai/deployment.md +269 -0
  115. package/docs/ai/manifest.json +11 -0
  116. package/docs/ai/quickstart.md +171 -0
  117. package/docs/ai/routing.md +214 -0
  118. package/docs/ai/troubleshooting.md +155 -0
  119. package/index.mjs +2 -0
  120. package/native/diagnostics.mjs +34 -0
  121. package/native/resolve.mjs +7 -2
  122. package/package.json +44 -7
  123. package/release/ai-docs.mjs +48 -0
  124. package/release/verify.mjs +2 -0
  125. package/runtime/action-client.mjs +10 -4
  126. package/runtime/action-server.mjs +2 -0
  127. package/runtime/ai-docs.mjs +202 -0
  128. package/runtime/app-client.mjs +226 -68
  129. package/runtime/app-content.mjs +4 -3
  130. package/runtime/app-partial-client.mjs +3 -0
  131. package/runtime/app-partial.mjs +1 -1
  132. package/runtime/app-prefetch-constants.mjs +4 -0
  133. package/runtime/app-prefetch.mjs +2 -3
  134. package/runtime/app-render-key.mjs +5 -0
  135. package/runtime/app-render.mjs +100 -28
  136. package/runtime/app-routing.mjs +22 -9
  137. package/runtime/app-static.mjs +18 -8
  138. package/runtime/app-stream-html.mjs +10 -53
  139. package/runtime/app-style-selection.mjs +34 -0
  140. package/runtime/app-styles.mjs +154 -0
  141. package/runtime/cache-components-rsc.mjs +4 -1
  142. package/runtime/cache-request.mjs +2 -1
  143. package/runtime/cloud/archive.mjs +5 -6
  144. package/runtime/compile-cache.mjs +8 -0
  145. package/runtime/deployment-client.mjs +19 -0
  146. package/runtime/dev-ai-brands.mjs +26 -0
  147. package/runtime/dev-ai-style.mjs +99 -0
  148. package/runtime/dev-build-reasons.mjs +25 -0
  149. package/runtime/dev-cache-control.mjs +53 -0
  150. package/runtime/dev-client.mjs +245 -46
  151. package/runtime/dev-compiler-broker.mjs +42 -0
  152. package/runtime/dev-console-copy.mjs +19 -0
  153. package/runtime/dev-console.mjs +106 -0
  154. package/runtime/dev-dependency-graph.mjs +40 -0
  155. package/runtime/dev-element-picker-style.mjs +27 -0
  156. package/runtime/dev-element-picker.mjs +213 -0
  157. package/runtime/dev-error-overlay-style.mjs +41 -0
  158. package/runtime/dev-error-overlay.mjs +167 -0
  159. package/runtime/dev-error-report.mjs +121 -0
  160. package/runtime/dev-file-changes.mjs +1 -1
  161. package/runtime/dev-gateway.mjs +44 -0
  162. package/runtime/dev-host-relay.mjs +66 -0
  163. package/runtime/dev-indicator.mjs +191 -0
  164. package/runtime/dev-issue-motion.mjs +214 -0
  165. package/runtime/dev-lazy-tool.mjs +34 -0
  166. package/runtime/dev-logo.mjs +2 -0
  167. package/runtime/dev-notification.mjs +81 -0
  168. package/runtime/dev-popout.mjs +34 -0
  169. package/runtime/dev-position.mjs +107 -0
  170. package/runtime/dev-preview-touch.mjs +122 -0
  171. package/runtime/dev-runtime-pool.mjs +45 -0
  172. package/runtime/dev-runtime-spare.mjs +57 -0
  173. package/runtime/dev-tools-client.mjs +155 -0
  174. package/runtime/dev-tools-mcp.mjs +126 -0
  175. package/runtime/dev-tools-metrics.mjs +31 -0
  176. package/runtime/dev-tools-model.mjs +42 -0
  177. package/runtime/dev-tools-panel.mjs +341 -0
  178. package/runtime/dev-tools-result.mjs +37 -0
  179. package/runtime/dev-tools-server.mjs +256 -0
  180. package/runtime/dev-tools-source.mjs +104 -0
  181. package/runtime/dev-tooltip.mjs +94 -0
  182. package/runtime/dev-ui-icons.mjs +8 -0
  183. package/runtime/dev-workbench-style.mjs +106 -0
  184. package/runtime/dev-workbench.mjs +428 -0
  185. package/runtime/dev-worker-logs.mjs +31 -0
  186. package/runtime/development.mjs +324 -0
  187. package/runtime/document.mjs +1 -1
  188. package/runtime/edge.mjs +6 -5
  189. package/runtime/env.mjs +2 -1
  190. package/runtime/fetch-cache.mjs +10 -8
  191. package/runtime/flight-delta.mjs +50 -0
  192. package/runtime/flight-tap.mjs +28 -0
  193. package/runtime/html-only.mjs +27 -0
  194. package/runtime/http.mjs +2 -0
  195. package/runtime/incremental-cache.mjs +25 -4
  196. package/runtime/instrumentation.mjs +49 -0
  197. package/runtime/navigation-recovery.mjs +36 -0
  198. package/runtime/navigation.mjs +7 -0
  199. package/runtime/node-html-stream.mjs +47 -0
  200. package/runtime/pages-client.mjs +24 -8
  201. package/runtime/pages-navigation.mjs +54 -64
  202. package/runtime/persistent/client.mjs +6 -5
  203. package/runtime/persistent/daemon.mjs +66 -56
  204. package/runtime/persistent/native-proxy.mjs +63 -0
  205. package/runtime/prefetch-scheduler.mjs +110 -0
  206. package/runtime/preload.mjs +12 -0
  207. package/runtime/profile.mjs +14 -0
  208. package/runtime/profiles.json +2 -2
  209. package/runtime/render.mjs +3 -0
  210. package/runtime/route-pattern.mjs +44 -0
  211. package/runtime/rsc-worker.mjs +41 -12
  212. package/runtime/trace.mjs +78 -0
  213. package/runtime/typed-route.mjs +19 -0
  214. package/runtime/watch-project.mjs +29 -18
  215. package/runtime/worker.mjs +102 -24
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- function parseCookieHeader(value = '') {
3
+ function parseCookieHeader(value = '', { ignoreMalformed = false } = {}) {
4
4
  const values = new Map();
5
5
  for (const part of String(value).split(';')) {
6
6
  const separator = part.indexOf('=');
7
7
  if (separator < 1) continue;
8
8
  const name = part.slice(0, separator).trim();
9
9
  let value = part.slice(separator + 1).trim();
10
- try { value = decodeURIComponent(value); } catch { /* Preserve malformed user input. */ }
10
+ try { value = decodeURIComponent(value); } catch { if (ignoreMalformed) continue; }
11
11
  values.set(name, { name, value });
12
12
  }
13
13
  return values;
@@ -50,7 +50,7 @@ function serializeCookie(cookie) {
50
50
 
51
51
  function parseResponseCookie(header) {
52
52
  const [pair, ...attributes] = header.split(';');
53
- const cookie = parseCookieHeader(pair).values().next().value;
53
+ const cookie = parseCookieHeader(pair, { ignoreMalformed: true }).values().next().value;
54
54
  if (!cookie) return;
55
55
  for (const attribute of attributes) {
56
56
  const separator = attribute.indexOf('=');
@@ -71,7 +71,7 @@ function parseResponseCookie(header) {
71
71
 
72
72
  class CookieStore {
73
73
  constructor(header, { mutable = false, onChange } = {}) {
74
- this._cookies = parseCookieHeader(header);
74
+ this._cookies = parseCookieHeader(header, { ignoreMalformed: true });
75
75
  this._mutable = mutable;
76
76
  this._onChange = onChange;
77
77
  }
@@ -99,6 +99,22 @@ class CookieStore {
99
99
  *[Symbol.iterator]() { for (const [name, cookie] of this._cookies) yield [name, { ...cookie }]; }
100
100
  }
101
101
 
102
+ class RequestCookieStore extends CookieStore {
103
+ constructor(headers) {
104
+ super(headers.get('cookie') || '', { mutable: true, onChange: () => headers.set('cookie', this.toString()) });
105
+ }
106
+ set(name, value) {
107
+ const cookie = typeof name === 'object' ? { name: name.name, value: name.value } : { name, value };
108
+ return super.set(cookie);
109
+ }
110
+ delete(names) {
111
+ const result = Array.isArray(names) ? names.map(name => this._cookies.delete(name)) : this._cookies.delete(names);
112
+ this._onChange?.();
113
+ return result;
114
+ }
115
+ clear() { this.delete([...this._cookies.keys()]); return this; }
116
+ }
117
+
102
118
  class ResponseCookieStore extends CookieStore {
103
119
  constructor(headers) {
104
120
  super('', { mutable: true });
@@ -126,4 +142,4 @@ class ResponseCookieStore extends CookieStore {
126
142
  toString() { return [...this._cookies.values()].map(serializeCookie).join('; '); }
127
143
  }
128
144
 
129
- module.exports = { CookieStore, ResponseCookieStore, parseCookieHeader, serializeCookie };
145
+ module.exports = { CookieStore, RequestCookieStore, ResponseCookieStore, parseCookieHeader, serializeCookie };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
  const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks');
3
3
  const http = require('node:http');
4
+ const cacheWire = require('./cache-wire.cjs');
4
5
  const { setTimeout: delay } = require('node:timers/promises');
5
6
  const { trackStaticDependency } = require('./static-generation.cjs');
6
7
  const diagnostics = require('./diagnostics.cjs');
@@ -21,7 +22,8 @@ const rpcScope = new AsyncResource('prnext-cache-transport');
21
22
  // idle sockets churns ephemeral ports under concurrent PPR traffic. Idle
22
23
  // connections expire after 30 seconds and retain no request context.
23
24
  const rpcAgent = new http.Agent({ keepAlive: true, maxSockets: 32, maxTotalSockets: 32, maxFreeSockets: 32, timeout: 30_000 });
24
- function localRpc(target, body, signal) {
25
+ function localRpc(target, parts, signal, binary) {
26
+ const length = parts.reduce((sum,part)=>sum+Buffer.byteLength(part),0);
25
27
  return rpcScope.runInAsyncScope(() => new Promise((resolve, reject) => {
26
28
  let settled = false;
27
29
  const finish = (error, value) => {
@@ -31,7 +33,7 @@ function localRpc(target, body, signal) {
31
33
  if (error) reject(error); else resolve(value);
32
34
  };
33
35
  const request = http.request(target.url, { method: 'POST', agent: rpcAgent,
34
- headers: { 'content-type': 'application/json', authorization: `Bearer ${target.token}`, 'content-length': Buffer.byteLength(body) },
36
+ headers: { 'content-type': binary ? cacheWire.TYPE : 'application/json', authorization: `Bearer ${target.token}`, 'content-length': length },
35
37
  }, response => {
36
38
  let size = 0;
37
39
  const chunks = [];
@@ -43,14 +45,14 @@ function localRpc(target, body, signal) {
43
45
  });
44
46
  response.on('end', () => {
45
47
  const ok = response.statusCode >= 200 && response.statusCode < 300;
46
- try { finish(null, { ok, status: response.statusCode, value: ok ? JSON.parse(Buffer.concat(chunks, size).toString()) : undefined }); }
48
+ try { finish(null, { ok, status: response.statusCode, value: ok ? (binary ? (response.headers['content-type'] === cacheWire.TYPE ? cacheWire.decode(Buffer.concat(chunks,size)) : (()=>{throw new Error('Cache protocol mismatch')})()) : JSON.parse(Buffer.concat(chunks, size).toString())) : undefined }); }
47
49
  catch (error) { finish(error); }
48
50
  });
49
51
  });
50
52
  const abort = () => request.destroy(signal.reason instanceof Error ? signal.reason : new Error('Cache RPC aborted'));
51
53
  request.on('error', error => finish(error));
52
54
  signal?.addEventListener('abort', abort, { once: true });
53
- if (signal?.aborted) abort(); else request.end(body);
55
+ if (signal?.aborted) abort(); else { request.cork(); for(const part of parts) request.write(part); request.end(); request.uncork(); }
54
56
  }));
55
57
  }
56
58
 
@@ -149,9 +151,10 @@ async function acquireExternalCacheLease(key, signal) {
149
151
  throw new Error('Timed out waiting for a native incremental cache lease');
150
152
  }
151
153
  async function rpc(target, operation, signal) {
152
- const body = JSON.stringify(operation);
153
154
  const url = new URL(target.url);
154
155
  const local = url.protocol === 'http:' && ['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname);
156
+ const binary = local && process.env.PRNEXT_CACHE_PROTOCOL === '2';
157
+ const body = binary ? cacheWire.encode(operation) : [JSON.stringify(operation.value === undefined ? operation : { ...operation, value: bufferValue(operation.value).toString('base64') })];
155
158
  const controller = new AbortController();
156
159
  const timer = setTimeout(() => controller.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError')), 5000);
157
160
  timer.unref();
@@ -159,10 +162,10 @@ async function rpc(target, operation, signal) {
159
162
  const operationSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
160
163
  let retryMs = 10;
161
164
  while (true) {
162
- const response = local ? await localRpc(target, body, operationSignal) : await nativeFetch(target.url, {
165
+ const response = local ? await localRpc(target, body, operationSignal, binary) : await nativeFetch(target.url, {
163
166
  method: 'POST',
164
167
  headers: { 'content-type': 'application/json', authorization: `Bearer ${target.token}` },
165
- body,
168
+ body: body[0],
166
169
  signal: operationSignal,
167
170
  });
168
171
  // The deadline covers the JSON body as well as response headers. A
@@ -198,6 +201,7 @@ function bufferValue(value) {
198
201
  return Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
199
202
  }
200
203
  function decodeValue(value) {
204
+ if (Buffer.isBuffer(value)) { if(value.length > MAX_CACHE_VALUE_BYTES) throw new Error('Invalid PRNext data cache value'); return value; }
201
205
  if (typeof value !== 'string' || value.length > Math.ceil(MAX_CACHE_VALUE_BYTES / 3) * 4) throw new Error('Invalid PRNext data cache value');
202
206
  const bytes = Buffer.from(value, 'base64');
203
207
  if (bytes.length > MAX_CACHE_VALUE_BYTES) throw new Error('Invalid PRNext data cache value');
@@ -247,7 +251,7 @@ async function compute(target, key, lease, producer, options) {
247
251
  try {
248
252
  const metadata = options.resolveMetadata?.();
249
253
  if (metadata?.externalCache) { await release(target, key, lease); return; }
250
- const result = await rpc(target, { op: 'commit', key, lease, value: bytes.toString('base64'), revalidate: options.revalidate,
254
+ const result = await rpc(target, { op: 'commit', key, lease, value: bytes, revalidate: options.revalidate,
251
255
  ...(metadata ? { tags: validateTags(metadata.tags), paths: metadata.paths || [], revalidate: validateRevalidate(metadata.revalidate, { allowZero: true }), expire: metadata.expire } : {}) }, signal);
252
256
  if (!result.stored) await release(target, key, lease);
253
257
  } catch {
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const WINDOW = 32, MAX_VALUE = 2 * 1024 * 1024, MAX_RESPONSE = 16 * 1024 * 1024;
4
+ const TYPE = 'application/x-prnext-flight-delta';
5
+ const MAGIC = new Uint8Array([80,82,68,49]);
6
+ const BLOCK_BYTES = 32 * 1024, MAX_COMMANDS_PER_CHUNK = 256, MAX_COMMANDS = 2048;
7
+ let POWER = 1; for(let n=1;n<WINDOW;n++) POWER = Math.imul(POWER,31) >>> 0;
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
+ function createIndex(base) {
10
+ const index=new Map();
11
+ for(let offset=0;offset+WINDOW<=base.length;offset+=WINDOW) {
12
+ const key=hash(base,offset), candidates=index.get(key)||[];
13
+ if(candidates.length<4)candidates.push(offset); index.set(key,candidates);
14
+ }
15
+ return index;
16
+ }
17
+ function frame(tag,a,b) { const value=new Uint8Array(tag===1?9:5), view=new DataView(value.buffer); value[0]=tag;view.setUint32(1,a);if(tag===1)view.setUint32(5,b);return value; }
18
+ function encodeChunk(bytes,base,index) {
19
+ const output=[];let cursor=0,literal=0,key=bytes.length>=WINDOW?hash(bytes,0):0,work=0,commands=0;
20
+ const budget=bytes.length*8+32768;
21
+ const emit=end=>{if(end>literal){commands++;output.push(frame(0,end-literal),bytes.subarray(literal,end));}};
22
+ while(cursor+WINDOW<=bytes.length) {
23
+ if(work>budget||commands>=MAX_COMMANDS_PER_CHUNK)return null;
24
+ let match=-1;
25
+ for(const offset of index.get(key)||[]) {let n=0;while(n<WINDOW&&bytes[cursor+n]===base[offset+n]){n++;work++;}if(n===WINDOW){match=offset;break;}}
26
+ if(match!==-1) {
27
+ emit(cursor);let length=WINDOW;
28
+ while(cursor+length<bytes.length&&match+length<base.length&&bytes[cursor+length]===base[match+length]){length++;work++;}
29
+ commands++;output.push(frame(1,match,length));cursor+=length;literal=cursor;
30
+ if(cursor+WINDOW<=bytes.length)key=hash(bytes,cursor);
31
+ } else {
32
+ key=(Math.imul((key-Math.imul(bytes[cursor],POWER))>>>0,31)+bytes[cursor+WINDOW])>>>0;cursor++;
33
+ }
34
+ }
35
+ emit(bytes.length);if(commands>MAX_COMMANDS_PER_CHUNK)return null;output.commands=commands;return output;
36
+ }
37
+ function join(parts,size) { const result=new Uint8Array(size);let offset=0;for(const part of parts){result.set(part,offset);offset+=part.length;}return result; }
38
+ // Commands are a wire format, not scheduling units. Thousands of tiny writes
39
+ // defeat gzip and multiply worker IPC/socket work even when the raw delta saves
40
+ // bytes. Bound each outgoing block without reading ahead into a delayed chunk.
41
+ function blocks(parts) {
42
+ const output=[];let pending=[],size=0;
43
+ const flush=()=>{if(size)output.push(pending.length===1?pending[0]:join(pending,size));pending=[];size=0;};
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
+ flush();return output;
46
+ }
47
+ function collectStream(source,{onComplete,onCancel,onFinally,capture=true}={}) {
48
+ const reader=source.getReader(), parts=[];let size=0,complete=false,finished=false;
49
+ const finish=()=>{if(!finished){finished=true;onFinally?.();}};
50
+ 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);},
53
+ },{highWaterMark:0});
54
+ }
55
+ async function deltaStream(source,base,{assess}={}) {
56
+ const reader=source.getReader(), first=await reader.read();
57
+ if(first.done)return {delta:false,body:new ReadableStream({start(c){c.close();}})};
58
+ let index=base instanceof Uint8Array&&base.length<=MAX_VALUE?createIndex(base):null;
59
+ 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);
60
+ const firstFrames=index&&first.value.length>=WINDOW?encodeChunk(first.value,base,index):null;
61
+ let useDelta;
62
+ try{useDelta=!!profitable(first.value,firstFrames,true);}catch(error){await reader.cancel(error).catch(()=>{});throw error;}
63
+ let pending=useDelta?blocks([MAGIC,...firstFrames]):[first.value], pendingAt=0,ended=false,tail=false,commands=firstFrames?.commands||0;
64
+ if(!useDelta){base=undefined;index=undefined;}
65
+ return {delta:!!useDelta,body:new ReadableStream({
66
+ async pull(controller){try{
67
+ if(pendingAt===pending.length&&!ended){pending=[];pendingAt=0;const next=await reader.read();
68
+ if(next.done){ended=true;if(useDelta&&!tail)pending=[new Uint8Array([2])];}
69
+ else if(useDelta&&!tail){const encoded=encodeChunk(next.value,base,index);commands+=encoded?.commands||0;
70
+ if(commands>MAX_COMMANDS||!profitable(next.value,encoded,false)){tail=true;index=undefined;base=undefined;pending=blocks([new Uint8Array([3]),next.value]);}
71
+ else pending=blocks(encoded);
72
+ }else pending=[next.value];
73
+ }
74
+ if(pendingAt<pending.length)controller.enqueue(pending[pendingAt++]);else if(ended)controller.close();
75
+ }catch(error){pending=[];base=undefined;index=undefined;controller.error(error);await reader.cancel(error).catch(()=>{});}},
76
+ cancel(reason){pending=[];base=undefined;index=undefined;ended=true;return reader.cancel(reason);},
77
+ },{highWaterMark:0})};
78
+ }
79
+ function decodeStream(source,base) {
80
+ const reader=source.getReader();let chunks=[],head=0,available=0,offset=0,total=0,wireTotal=0,started=false,ended=false,tail=false,commands=0;
81
+ function consume(size){
82
+ const first=chunks[head];
83
+ if(first&&first.length-offset>=size){const result=first.subarray(offset,offset+size);offset+=size;available-=size;if(offset===first.length){head++;offset=0;}return result;}
84
+ const result=new Uint8Array(size);let written=0;
85
+ while(written<size){const value=chunks[head],count=Math.min(size-written,value.length-offset);result.set(value.subarray(offset,offset+count),written);written+=count;offset+=count;available-=count;if(offset===value.length){head++;offset=0;}}
86
+ return result;
87
+ }
88
+ async function take(size) {
89
+ if(size>MAX_RESPONSE)throw new Error('Invalid Flight delta frame size');
90
+ while(available<size){const next=await reader.read();if(next.done)throw new Error('Truncated Flight delta');if(!(next.value instanceof Uint8Array))throw new Error('Invalid Flight delta bytes');wireTotal+=next.value.length;if(wireTotal>MAX_RESPONSE+1024*1024)throw new Error('Flight delta wire budget exceeded');if(next.value.length){if(head){chunks=chunks.slice(head);head=0;}chunks.push(next.value);available+=next.value.length;}}
91
+ return consume(size);
92
+ }
93
+ function peek(at){let index=head,position=offset+at;while(index<chunks.length){if(position<chunks[index].length)return chunks[index][position];position-=chunks[index++].length;}}
94
+ function bufferedCommand(){
95
+ if(!available)return false;
96
+ const tag=peek(0);if(tag!==0&&tag!==1)return true;
97
+ if(available<(tag===1?9:5))return false;
98
+ const length=tag===1?0:((peek(1)*0x1000000)+(peek(2)<<16)+(peek(3)<<8)+peek(4));
99
+ return length>MAX_RESPONSE||available>=5+length;
100
+ }
101
+ async function pullTail(controller){
102
+ const next=available?{value:await take(available),done:false}:await reader.read();
103
+ if(next.done){ended=true;controller.close();return;}
104
+ if(!(next.value instanceof Uint8Array))throw new Error('Invalid Flight delta bytes');
105
+ total+=next.value.length;if(total>MAX_RESPONSE)throw new Error('Decoded Flight exceeds transport budget');controller.enqueue(next.value);
106
+ }
107
+ return new ReadableStream({
108
+ async pull(controller){try{
109
+ if(tail)return await pullTail(controller);
110
+ if(!started){const magic=await take(4);if(!magic.every((value,n)=>value===MAGIC[n]))throw new Error('Unsupported Flight delta version');started=true;}
111
+ if(ended)return controller.close();
112
+ const output=[];let size=0;
113
+ do {
114
+ // Publish decoded data before waiting for another source chunk or the
115
+ // final EOF. This preserves Suspense flushes and downstream backpressure.
116
+ if(output.length&&(!bufferedCommand()||peek(0)===2||peek(0)===3))break;
117
+ if(++commands>65536)throw new Error('Flight delta command budget exceeded');
118
+ const [tag]=await take(1);let value;
119
+ if(tag===3){tail=true;return await pullTail(controller);}
120
+ if(tag===2){ended=true;if(available)throw new Error('Trailing Flight delta data');const rest=await reader.read();if(!rest.done)throw new Error('Trailing Flight delta data');controller.close();return;}
121
+ if(tag===0){const header=await take(4),length=new DataView(header.buffer,header.byteOffset,header.byteLength).getUint32(0);if(!length)throw new Error('Empty Flight delta literal');value=await take(length);}
122
+ else if(tag===1){const header=await take(8),view=new DataView(header.buffer,header.byteOffset,header.byteLength),start=view.getUint32(0),length=view.getUint32(4);if(!length||start+length>base.length)throw new Error('Invalid Flight delta reference');value=base.subarray(start,start+length);}
123
+ else throw new Error('Invalid Flight delta command');
124
+ total+=value.length;if(total>MAX_RESPONSE)throw new Error('Decoded Flight exceeds transport budget');output.push(value);size+=value.length;
125
+ }while(size<BLOCK_BYTES);
126
+ controller.enqueue(output.length===1?output[0]:join(output,size));
127
+ }catch(error){chunks=[];controller.error(error);await reader.cancel(error).catch(()=>{});}},
128
+ cancel:reason=>{chunks=[];return reader.cancel(reason);},
129
+ },{highWaterMark:0});
130
+ }
131
+ function createFlightTransport() {
132
+ let current,serial=0;
133
+ return {
134
+ snapshot(){return current;},
135
+ async response(response,base) {
136
+ const version=++serial;
137
+ const token=response.headers.get('x-prnext-flight-token'), headers=new Headers(response.headers);
138
+ let source=response.body;
139
+ if(!source)return response;
140
+ if(headers.get('content-type')?.startsWith(TYPE)) {
141
+ if(!base||headers.get('x-prnext-flight-base')!==base.token){await source.cancel();throw new Error('Missing Flight delta base');}
142
+ source=decodeStream(source,base.bytes);
143
+ }
144
+ headers.set('content-type','text/x-component; charset=utf-8');headers.delete('content-length');headers.delete('content-encoding');
145
+ 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});
146
+ },
147
+ clear(){serial++;current=undefined;},
148
+ };
149
+ }
150
+ module.exports={TYPE,MAX_VALUE,createIndex,encodeChunk,deltaStream,decodeStream,collectStream,createFlightTransport};
@@ -63,12 +63,14 @@ function runRequestContext(input, callback) {
63
63
  return requestStorage.run(context, callback);
64
64
  }
65
65
 
66
+ function assertAfterRequestData(name) { const context = currentRequest(); if (context.phase === 'after' && context.afterRestricted) throw new Error(`${name}() cannot be called inside after() registered by a Server Component; read it before registering the callback`); }
66
67
  function assertUncached(name) {
67
68
  if (inCacheScope()) throw new Error(`${name}() cannot be accessed inside unstable_cache; read it outside and pass the required values as arguments`);
68
69
  }
69
- async function headers() { if (currentRequest().instantValidation && currentRequest().instantSample) { assertUncached('headers'); return sampleMethods(currentRequest(), 'headers', currentRequest().headers); } if (currentCacheScope()?.kind !== 'private') assertUncached('headers'); dynamicUsage('headers()'); return currentRequest().headers; }
70
- async function cookies() { if (currentRequest().instantValidation && currentRequest().instantSample) { assertUncached('cookies'); return sampleMethods(currentRequest(), 'cookies', currentRequest().cookies); } if (currentCacheScope()?.kind !== 'private') assertUncached('cookies'); dynamicUsage('cookies()'); return currentRequest().cookies; }
70
+ async function headers() { assertAfterRequestData('headers'); if (currentRequest().instantValidation && currentRequest().instantSample) { assertUncached('headers'); return sampleMethods(currentRequest(), 'headers', currentRequest().headers); } if (currentCacheScope()?.kind !== 'private') assertUncached('headers'); dynamicUsage('headers()'); return currentRequest().headers; }
71
+ async function cookies() { assertAfterRequestData('cookies'); if (currentRequest().instantValidation && currentRequest().instantSample) { assertUncached('cookies'); return sampleMethods(currentRequest(), 'cookies', currentRequest().cookies); } if (currentCacheScope()?.kind !== 'private') assertUncached('cookies'); dynamicUsage('cookies()'); return currentRequest().cookies; }
71
72
  async function draftMode() {
73
+ assertAfterRequestData('draftMode');
72
74
  const context = currentRequest();
73
75
  if (!context.draftProvider) context.draftProvider = {
74
76
  get isEnabled() { return context.draftMode; },
@@ -78,6 +78,7 @@ async function invalidate(context, operation) {
78
78
  await invoke(handler, 'revalidateTag', [[...new Set(tags)], durations], context);
79
79
  }
80
80
  function fresh(entry, revalidate) {
81
+ if (entry?.isStale === true) return false;
81
82
  const timestamp = entry?.lastModified;
82
83
  if (!Number.isFinite(timestamp) || timestamp < 0 || timestamp > Date.now() + 60_000) return false;
83
84
  const stored = entry.value?.revalidate;
@@ -124,14 +125,18 @@ async function cachedIncrementalValue(key, producer, options) {
124
125
  let complete;
125
126
  const owner = { revision, done: new Promise(resolve => { complete = resolve; }) };
126
127
  if (ownersCount < 64) { record.owners.set(key, owner); ownersCount++; }
127
- let lease;
128
+ let lease, sharedLease;
128
129
  const cleanup = async () => {
129
130
  if (owner.finished) return;
130
131
  owner.finished = true;
131
- try { await lease?.release(); } finally {
132
- if (record.owners.get(key) === owner) record.owners.delete(key);
133
- if (owner.tracked) ownersCount--;
134
- complete();
132
+ try {
133
+ if (sharedLease && handler.prnextLeaseVersion === 1 && typeof handler.release === 'function') await invoke(handler, 'release', [key, sharedLease], { ...context, signal: undefined });
134
+ } finally {
135
+ try { await lease?.release(); } finally {
136
+ if (record.owners.get(key) === owner) record.owners.delete(key);
137
+ if (owner.tracked) ownersCount--;
138
+ complete();
139
+ }
135
140
  }
136
141
  };
137
142
  owner.tracked = record.owners.get(key) === owner;
@@ -140,7 +145,7 @@ async function cachedIncrementalValue(key, producer, options) {
140
145
  if (!(value instanceof Uint8Array) || value.byteLength > MAX_BYTES) return;
141
146
  if (lease && !await lease.valid()) return;
142
147
  const data = incremental.encode(Buffer.from(value));
143
- const setContext = { ...getContext, tags: [...new Set([...tags, ...implicit])], cacheControl: { revalidate } };
148
+ const setContext = { ...getContext, tags: [...new Set([...tags, ...implicit])], cacheControl: { revalidate }, prnextLease: sharedLease };
144
149
  try {
145
150
  await invoke(handler, 'set', [key, { kind: 'FETCH', data, revalidate: revalidate === false ? 31536000 : revalidate }, setContext], context);
146
151
  // A mutation in any local worker can revoke the native lease while the
@@ -152,7 +157,15 @@ async function cachedIncrementalValue(key, producer, options) {
152
157
  const work = (async () => {
153
158
  try {
154
159
  lease = await cache.acquireExternalCacheLease(key, signal);
155
- if (lease) {
160
+ if (handler.prnextLeaseVersion === 1 && typeof handler.acquire === 'function') {
161
+ const until = Date.now() + 25000;
162
+ for (;;) {
163
+ signal?.throwIfAborted();
164
+ try { sharedLease = await invoke(handler, 'acquire', [key, { signal }], context); break; }
165
+ catch (error) { if (error.code !== 'PRNEXT_REDIS_BUSY' || Date.now() >= until) throw error; }
166
+ }
167
+ }
168
+ if (lease || sharedLease) {
156
169
  // Even an immediately granted lease may follow a producer that finished
157
170
  // between our first backend read and this native admission.
158
171
  let shared;
package/compat/index.d.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  import type { ComponentType } from 'react';
3
3
  import type { IncomingMessage, ServerResponse } from 'node:http';
4
4
  export type { Metadata, Viewport, ResolvingMetadata, ResolvingViewport, MetadataRoute } from './metadata.cjs';
5
+ export {route} from './typed-route.cjs';
6
+ export type {Route, RoutePattern, RouteParameters} from './typed-route.cjs';
5
7
 
6
8
  export interface InstantSample {
7
9
  cookies?: Array<{name: string; value: string | null}>;
@@ -11,6 +13,13 @@ export interface InstantSample {
11
13
  }
12
14
  export type InstantConfig = boolean | { level?: 'warning' | 'experimental-error'; unstable_samples?: InstantSample[]; unstable_disableValidation?: true; unstable_disableDevValidation?: true; unstable_disableBuildValidation?: true };
13
15
 
16
+ export interface ReactCompilerOptions {
17
+ compilationMode?: 'infer' | 'annotation';
18
+ panicThreshold?: 'none' | 'critical_errors' | 'all_errors';
19
+ /** PRNext requires matching React 19 peers. */
20
+ target?: '19';
21
+ }
22
+
14
23
  export type TurbopackCondition = 'browser' | 'foreign' | 'development' | 'production' | 'node' | 'edge-light'
15
24
  | {all: TurbopackCondition[]} | {any: TurbopackCondition[]} | {not: TurbopackCondition}
16
25
  | {path: string | RegExp; content?: RegExp} | {path?: string | RegExp; content: RegExp};
@@ -115,24 +124,39 @@ export interface NextConfig {
115
124
  /** PRNext only. CLI > environment > build configuration > profile.
116
125
  * 0 disables the corresponding retirement; keepWarm retains one lane per used host.
117
126
  */
127
+ /** Soft RSS retirement after three one-second samples; active work drains. */
128
+ workerMemory?: false | { maxRssMb: number };
118
129
  workerRetirement?: { idleSeconds?: number; laneIdleSeconds?: number; keepWarm?: boolean };
119
- /** Next dev page-retention hints; PRNext builds all routes without that eviction queue. */
130
+ /** Import selected Node routes on startup; top-level module code executes. RSS is a soft import budget. */
131
+ workerPreload?: false | { routes: string[]; maxRssMb?: number; timeoutMs?: number };
132
+ /** Keep bounded previous public hashed assets. false disables carry-over. Defaults: one hour, two generations, 64 MiB. */
133
+ deploymentRetention?: false | {maxAge?: number; maxGenerations?: number; maxBytes?: number};
134
+ /** PRNext production prerender processes; maxMemoryMb bounds their combined observed memory. */
135
+ prerender?: {workers?: number; maxMemoryMb?: number};
136
+ /** Explicit App route patterns that must produce complete static HTML without browser JavaScript. */
137
+ htmlOnly?: string[];
138
+ /** Bounded active development routes; default retains five requested routes. */
120
139
  onDemandEntries?: { maxInactiveAge?: number; pagesBufferLength?: number };
121
- /** Disable the Next development indicator. Error overlays and Fast Refresh remain enabled. */
122
- devIndicators?: false;
140
+ /** PRNext development menu; bottom-left by default. false hides it without disabling errors or Fast Refresh. */
141
+ devIndicators?: false | { position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' };
123
142
  i18n?: { locales: string[]; defaultLocale: string; localeDetection?: boolean; domains?: Array<{domain: string; defaultLocale: string; locales?: string[]; http?: boolean}> };
124
143
  webpack?: (config: import('webpack').Configuration, context: { buildId: string; dev: boolean; isServer: boolean; nextRuntime?: 'nodejs' | 'edge'; webpack: typeof import('webpack'); defaultLoaders: {babel: {loader: string}} }) => import('webpack').Configuration | Promise<import('webpack').Configuration>;
125
144
  turbopack?: { root?: string; rules?: Record<string, TurbopackRule | TurbopackRule[]>; resolveAlias?: Record<string, string | { browser: string }>; resolveExtensions?: string[] };
126
145
  images?: import('./image.cjs').ImageConfig;
127
146
  pageExtensions?: string[];
128
147
  reactStrictMode?: boolean;
129
- /** Enabled React Compiler transforms are not implemented. */
130
- reactCompiler?: false;
131
- /** Typed route generation and validation are not implemented. */
132
- typedRoutes?: false;
148
+ /** Opt in to the bundled official React Compiler for application components. Disabled by default. */
149
+ reactCompiler?: boolean | ReactCompilerOptions;
150
+ /** Generate route completions and validate Link/router destinations. */
151
+ typedRoutes?: boolean;
152
+ /** Native CSS for default styles; custom PostCSS pipelines remain authoritative. */
153
+ cssEngine?: 'auto'|'postcss'|'lightningcss';
133
154
  /** Production checks use the project's installed TypeScript without emitting files. */
134
- typescript?: { ignoreBuildErrors?: boolean; tsconfigPath?: string };
155
+ typescript?: { ignoreBuildErrors?: boolean; tsconfigPath?: string; devChecks?: boolean };
135
156
  cacheComponents?: boolean;
157
+ partialPrefetching?: boolean;
158
+ /** App Router inactive layout branches. Defaults to 4 entries for 120 seconds. */
159
+ navigationCache?: false | {maxEntries?: number; ttlMs?: number; authCookies?: readonly string[]};
136
160
  cacheLife?: Record<string, import('./cache.cjs').CacheLife>;
137
161
  transpilePackages?: string[];
138
162
  serverExternalPackages?: string[];
@@ -190,11 +214,66 @@ export interface StaticBuildOptions {
190
214
  distDir?: string;
191
215
  dev?: false;
192
216
  }
193
- export interface BuildResult { buildId: string; outputDirectory: string; }
217
+ export interface ReactCompilerReport {
218
+ version: 1;
219
+ compilerVersion: string | null;
220
+ enabled: boolean;
221
+ buildId: string | null;
222
+ mode: 'development' | 'production';
223
+ /** Incremental graphs can reuse inputs without observing their transforms. */
224
+ scope: 'observed-transforms';
225
+ options: boolean | ReactCompilerOptions | null;
226
+ truncated: boolean;
227
+ summary: {files: number; functions: number; optimized: number; skipped: number; errors: number; diagnostics: number; mixed: number; transforms: number; cacheReuses: number};
228
+ files: Array<{
229
+ file: string; transforms: number; cacheReuses: number; targets: string[]; notes: string[];
230
+ functions: Array<{
231
+ name: string | null;
232
+ start: {line: number; column: number} | null;
233
+ end: {line: number; column: number} | null;
234
+ status: 'optimized' | 'skipped' | 'error' | 'diagnostic' | 'mixed';
235
+ targets: string[];
236
+ events: Array<{
237
+ kind: 'CompileSuccess' | 'CompileSkip' | 'CompileError' | 'CompileDiagnostic' | 'PipelineError';
238
+ status: 'optimized' | 'skipped' | 'error' | 'diagnostic' | 'mixed';
239
+ reason: string | null; category: string | null; targets: string[]; policy?: boolean;
240
+ memoSlots?: number; memoBlocks?: number; memoValues?: number;
241
+ }>;
242
+ }>;
243
+ }>;
244
+ graphs: Array<{target: string; metadataAvailable: boolean; projectJavaScriptInputs: number; observedFiles: number; unobservedInputs: number}>;
245
+ }
246
+ export interface CompilationCacheReport {
247
+ version: 1;
248
+ scope: 'eligible-production-compilation-graphs';
249
+ hits: number;
250
+ misses: number;
251
+ bypasses: number;
252
+ reusedBytes: number;
253
+ kinds: Array<{kind: string; hits: number; misses: number; bypasses: number; writes: number; reusedBytes: number; reasons: Record<string, number>}>;
254
+ }
255
+ export interface BuildResult { buildId: string; outputDirectory: string; reactCompilerReport?: ReactCompilerReport; compilationCache?: CompilationCacheReport; }
256
+ export interface ProductionBuilder {
257
+ build(options?: {envMode?: 'production' | 'test'}): Promise<BuildResult>;
258
+ stats(): {contexts: number; created: number; reused: number};
259
+ dispose(): Promise<void>;
260
+ }
261
+ export function createBuilder(projectRoot: string, options?: {envMode?: 'production' | 'test'}): Promise<ProductionBuilder>;
194
262
  export interface StaticBuildResult extends BuildResult { files: number; bytes: number; }
195
263
  export function build(projectRoot: string, options: StaticBuildOptions & { adapter: 'static' }): Promise<StaticBuildResult>;
196
- export function build(projectRoot: string, options?: { adapter?: 'react'; dev?: boolean; envMode?: 'production' | 'development' | 'test' }): Promise<BuildResult>;
264
+ /** Detect a completed browser output or a PRNext React project; does not run another builder. */
265
+ export function build(projectRoot: string, options: { adapter: 'auto'; dev?: false; spa?: boolean }): Promise<BuildResult | StaticBuildResult>;
266
+ export function build(projectRoot: string, options?: { adapter?: 'react'; dev?: boolean; incremental?: boolean; envMode?: 'production' | 'development' | 'test' }): Promise<BuildResult>;
197
267
  export function buildStatic(projectRoot: string, options?: StaticBuildOptions): Promise<StaticBuildResult>;
198
268
  export function readBuildOutput(buildDirectory: string): Promise<BuildOutput>;
199
269
  export function validateBuildOutput(value: unknown): BuildOutput;
200
270
  export type { DocumentContext, DocumentInitialProps, DocumentProps } from './document.cjs';
271
+
272
+ /** Server instrumentation hooks, compatible with Next.js instrumentation files. */
273
+ export namespace Instrumentation {
274
+ export type onRequestError = (error: unknown, request: Readonly<{path: string; method: string; headers: Record<string,string|string[]>}>, context: {
275
+ routerKind: 'App Router' | 'Pages Router'; routePath: string; routeType: 'render' | 'route' | 'action' | 'proxy';
276
+ renderSource: 'react-server-components' | 'react-server-components-payload' | 'server-rendering';
277
+ revalidateReason: 'on-demand' | 'stale' | undefined; renderType: 'dynamic' | 'dynamic-resume';
278
+ }) => void | Promise<void>;
279
+ }
package/compat/link.cjs CHANGED
@@ -7,7 +7,7 @@ const { addBasePath, hasBasePath, normalizeTrailingSlash } = require('./paths.cj
7
7
  const { localizedHref } = require('./locale.cjs');
8
8
 
9
9
  const Link = React.forwardRef(function Link({ href, as, children, replace, scroll, shallow, prefetch,
10
- locale, legacyBehavior, passHref, onClick, onNavigate, onMouseEnter, onTouchStart, ...props }, ref) {
10
+ locale, legacyBehavior, passHref, onClick, onNavigate, onMouseEnter, onTouchStart, onFocus, ...props }, ref) {
11
11
  const app = React.useContext(AppRouterContext);
12
12
  const pages = React.useContext(RouterContext);
13
13
  const anchorRef = React.useRef(null);
@@ -19,14 +19,15 @@ const Link = React.forwardRef(function Link({ href, as, children, replace, scrol
19
19
  : target.startsWith('//') ? target : addBasePath(target, basePath);
20
20
  const publicTarget = normalizeTrailingSlash(mounted, policy);
21
21
  const source = formatUrl(href);
22
- const requestPrefetch = React.useCallback(() => {
23
- if (app?.router.partialPrefetch && prefetch !== false && process.env.NODE_ENV === 'production') app.router.prefetch(target);
24
- if (!app && pages && process.env.NODE_ENV === 'production') void pages.prefetch(source, as ? target : undefined, {locale}).catch(() => {});
25
- }, [app, pages, source, target, as, prefetch]);
22
+ const requestPrefetch = React.useCallback((priority = 'intent') => {
23
+ if (prefetch === false || process.env.NODE_ENV !== 'production') return;
24
+ if (app?.router.partialPrefetch) app.router.prefetch(target, { priority, full: prefetch === true });
25
+ if (!app && pages) void pages.prefetch(source, as ? target : undefined, { locale, priority }).catch(() => {});
26
+ }, [app, pages, source, target, as, locale, prefetch]);
26
27
  React.useEffect(() => {
27
28
  if ((app ? !app.router.partialPrefetch : !pages) || prefetch === false || process.env.NODE_ENV !== 'production' || typeof IntersectionObserver === 'undefined' || !anchorRef.current) return;
28
29
  const observer = new IntersectionObserver(entries => {
29
- if (entries.some(entry => entry.isIntersecting)) { observer.disconnect(); requestPrefetch(); }
30
+ if (entries.some(entry => entry.isIntersecting)) { observer.disconnect(); requestPrefetch('viewport'); }
30
31
  }, { rootMargin: '200px' });
31
32
  observer.observe(anchorRef.current);
32
33
  return () => observer.disconnect();
@@ -53,16 +54,18 @@ const Link = React.forwardRef(function Link({ href, as, children, replace, scrol
53
54
  void pages[replace ? 'replace' : 'push'](href, as, { scroll, shallow, locale }).catch(error => console.error(error));
54
55
  } else if (replace) { event.preventDefault(); window.location.replace(publicTarget); }
55
56
  }
56
- function hover(event) { onMouseEnter?.(event); requestPrefetch(); }
57
- function touch(event) { onTouchStart?.(event); requestPrefetch(); }
57
+ function hover(event) { onMouseEnter?.(event); if (!event.defaultPrevented) requestPrefetch(); }
58
+ function touch(event) { onTouchStart?.(event); if (!event.defaultPrevented) requestPrefetch(); }
59
+ function focus(event) { onFocus?.(event); if (!event.defaultPrevented) requestPrefetch(); }
58
60
  if (legacyBehavior) {
59
61
  const child = React.Children.only(children);
60
62
  return React.cloneElement(child, { ...props, href: publicTarget, ref: setRef,
61
63
  onMouseEnter(event) { child.props.onMouseEnter?.(event); hover(event); },
62
64
  onTouchStart(event) { child.props.onTouchStart?.(event); touch(event); },
65
+ onFocus(event) { child.props.onFocus?.(event); focus(event); },
63
66
  onClick(event) { child.props.onClick?.(event); if (!event.defaultPrevented) click(event); } });
64
67
  }
65
- return React.createElement('a', { ...props, href: publicTarget, ref: setRef, onClick: click, onMouseEnter: hover, onTouchStart: touch }, children);
68
+ return React.createElement('a', { ...props, href: publicTarget, ref: setRef, onClick: click, onMouseEnter: hover, onTouchStart: touch, onFocus: focus }, children);
66
69
  });
67
70
  module.exports = Link;
68
71
  module.exports.default = Link;
package/compat/link.d.cts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { AnchorHTMLAttributes, ForwardRefExoticComponent, ReactNode, RefAttributes } from 'react';
2
2
  import type { UrlObject } from './router.cjs';
3
- export interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
4
- href: string | UrlObject;
3
+ import type {Route,TypedUrlObject} from './typed-route.cjs';
4
+ export interface LinkProps<T extends string=string> extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
5
+ href: Route<T> | TypedUrlObject;
5
6
  as?: string | UrlObject;
6
7
  replace?: boolean;
7
8
  scroll?: boolean;
@@ -13,5 +14,5 @@ export interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>,
13
14
  children?: ReactNode;
14
15
  onNavigate?: (event: { preventDefault(): void }) => void;
15
16
  }
16
- declare const Link: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAnchorElement>>;
17
+ declare function Link<T extends string>(props:LinkProps<T> & RefAttributes<HTMLAnchorElement>): import('react').ReactElement|null;
17
18
  export default Link;
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+ const { createHmac } = require('node:crypto');
3
+ function navigationAuthorization(context, secret, cookieNames) {
4
+ if (!secret || context?.staticState) return undefined;
5
+ const names = cookieNames === undefined ? null : new Set(cookieNames);
6
+ 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
+ return createHmac('sha256', secret).update(JSON.stringify([cookies, context.headers.get('authorization') || ''])).digest('hex');
8
+ }
9
+ module.exports = { navigationAuthorization };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+ const { optionalRequestContext, inCacheScope } = require('./data-cache.cjs');
3
+
4
+ function postponeStage(name) {
5
+ throw Object.assign(new Error(`Deferred until ${name}.`), { code: 'PRNEXT_PPR_DYNAMIC', digest: 'PRNEXT_PPR_DYNAMIC' });
6
+ }
7
+ function stage(name) {
8
+ const context = optionalRequestContext();
9
+ if (!context?.cacheComponents) throw new Error(`${name}() requires cacheComponents: true.`);
10
+ if (inCacheScope()) throw new Error(`${name}() cannot run inside a cached function. Call it before invoking the cached function.`);
11
+ // Static prerenders deliberately continue through both gates. Cacheable work
12
+ // remains prerenderable and is not made request-dependent by either API.
13
+ if (context.staticState) return;
14
+ if (context.navigationStage === 'shell' && (name === 'navigation' || context.partialPrefetching)) postponeStage(name);
15
+ if (context.navigationStage === 'prefetch' && name === 'navigation') postponeStage(name);
16
+ }
17
+ async function navigation() { stage('navigation'); }
18
+ async function prefetch() { stage('prefetch'); }
19
+ module.exports = { navigation, prefetch, postponeStage };