@thomas.f/prnext 0.1.17-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.
- package/README.md +26 -869
- package/build/actions.mjs +1 -1
- package/build/analyze.mjs +180 -24
- package/build/app-config.mjs +13 -0
- package/build/app-static.mjs +34 -12
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +81 -26
- package/build/bundle-runtime.mjs +22 -9
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/compact-css.mjs +53 -0
- package/build/compilation-cache.mjs +269 -0
- package/build/compilation-store.mjs +142 -0
- package/build/compiler.mjs +31 -8
- package/build/config.mjs +30 -1
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +56 -12
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-css-update.mjs +4 -2
- package/build/dev-dependencies.mjs +1 -1
- package/build/dev-discovery.mjs +3 -3
- package/build/dev.mjs +15 -8
- package/build/dynamic.mjs +1 -1
- package/build/edge.mjs +3 -3
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +15 -5
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/index.mjs +59 -26
- package/build/instrumentation.mjs +30 -0
- package/build/metadata-routes.mjs +1 -1
- package/build/migration-audit.mjs +1 -1
- package/build/native-type-config.mjs +37 -0
- package/build/persistent-cache.mjs +18 -3
- package/build/precompress.mjs +1 -1
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +19 -9
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +5 -5
- package/build/production-builder.mjs +58 -9
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/static-export.mjs +4 -4
- package/build/styles.mjs +6 -4
- package/build/transform.mjs +1 -1
- package/build/typed-routes.mjs +4 -0
- package/build/typescript-dev.mjs +35 -8
- package/build/typescript-native-worker.mjs +55 -0
- package/build/typescript-native.mjs +18 -0
- package/build/typescript-toolchain.mjs +19 -0
- package/build/typescript.mjs +6 -4
- package/build/webpack-graph.mjs +6 -3
- package/build/worker-memory.mjs +9 -0
- package/cli.mjs +60 -4
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +12 -2
- package/compat/cache-wire.cjs +28 -0
- package/compat/cache.cjs +2 -1
- package/compat/cache.d.cts +4 -0
- package/compat/cookies.cjs +21 -5
- package/compat/data-cache.cjs +12 -8
- package/compat/flight-delta.cjs +150 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +68 -3
- package/compat/link.cjs +12 -9
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/offline-store.cjs +11 -0
- package/compat/offline.cjs +5 -0
- package/compat/offline.d.cts +2 -0
- package/compat/redis-cache.cjs +201 -0
- package/compat/redis-cache.d.cts +37 -0
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +171 -2
- package/docs/ai/data.md +165 -5
- package/docs/ai/deployment.md +158 -0
- package/docs/ai/quickstart.md +29 -4
- package/docs/ai/routing.md +82 -0
- package/docs/ai/troubleshooting.md +37 -2
- package/package.json +26 -7
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/app-client.mjs +212 -65
- package/runtime/app-content.mjs +4 -3
- package/runtime/app-partial-client.mjs +3 -0
- package/runtime/app-partial.mjs +1 -1
- package/runtime/app-prefetch-constants.mjs +4 -0
- package/runtime/app-prefetch.mjs +2 -3
- package/runtime/app-render-key.mjs +5 -0
- package/runtime/app-render.mjs +88 -28
- package/runtime/app-routing.mjs +22 -9
- package/runtime/app-static.mjs +18 -8
- package/runtime/app-stream-html.mjs +10 -53
- package/runtime/app-style-selection.mjs +34 -0
- package/runtime/app-styles.mjs +154 -0
- package/runtime/cache-components-rsc.mjs +4 -1
- package/runtime/cache-request.mjs +2 -1
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-client.mjs +7 -2
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-indicator.mjs +8 -3
- package/runtime/dev-lazy-tool.mjs +16 -0
- package/runtime/development.mjs +29 -12
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +50 -0
- package/runtime/flight-tap.mjs +28 -0
- package/runtime/html-only.mjs +27 -0
- package/runtime/http.mjs +2 -0
- package/runtime/incremental-cache.mjs +25 -4
- package/runtime/instrumentation.mjs +49 -0
- package/runtime/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +14 -7
- package/runtime/pages-navigation.mjs +48 -63
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/profile.mjs +14 -0
- package/runtime/profiles.json +2 -2
- package/runtime/render.mjs +3 -0
- package/runtime/route-pattern.mjs +44 -0
- package/runtime/rsc-worker.mjs +34 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/watch-project.mjs +19 -15
- package/runtime/worker.mjs +70 -26
|
@@ -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};
|
package/compat/headers.cjs
CHANGED
|
@@ -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 {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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 (
|
|
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
|
@@ -13,6 +13,13 @@ export interface InstantSample {
|
|
|
13
13
|
}
|
|
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
|
+
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
|
+
|
|
16
23
|
export type TurbopackCondition = 'browser' | 'foreign' | 'development' | 'production' | 'node' | 'edge-light'
|
|
17
24
|
| {all: TurbopackCondition[]} | {any: TurbopackCondition[]} | {not: TurbopackCondition}
|
|
18
25
|
| {path: string | RegExp; content?: RegExp} | {path?: string | RegExp; content: RegExp};
|
|
@@ -117,9 +124,17 @@ export interface NextConfig {
|
|
|
117
124
|
/** PRNext only. CLI > environment > build configuration > profile.
|
|
118
125
|
* 0 disables the corresponding retirement; keepWarm retains one lane per used host.
|
|
119
126
|
*/
|
|
127
|
+
/** Soft RSS retirement after three one-second samples; active work drains. */
|
|
128
|
+
workerMemory?: false | { maxRssMb: number };
|
|
120
129
|
workerRetirement?: { idleSeconds?: number; laneIdleSeconds?: number; keepWarm?: boolean };
|
|
121
130
|
/** Import selected Node routes on startup; top-level module code executes. RSS is a soft import budget. */
|
|
122
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[];
|
|
123
138
|
/** Bounded active development routes; default retains five requested routes. */
|
|
124
139
|
onDemandEntries?: { maxInactiveAge?: number; pagesBufferLength?: number };
|
|
125
140
|
/** PRNext development menu; bottom-left by default. false hides it without disabling errors or Fast Refresh. */
|
|
@@ -130,8 +145,8 @@ export interface NextConfig {
|
|
|
130
145
|
images?: import('./image.cjs').ImageConfig;
|
|
131
146
|
pageExtensions?: string[];
|
|
132
147
|
reactStrictMode?: boolean;
|
|
133
|
-
/**
|
|
134
|
-
reactCompiler?:
|
|
148
|
+
/** Opt in to the bundled official React Compiler for application components. Disabled by default. */
|
|
149
|
+
reactCompiler?: boolean | ReactCompilerOptions;
|
|
135
150
|
/** Generate route completions and validate Link/router destinations. */
|
|
136
151
|
typedRoutes?: boolean;
|
|
137
152
|
/** Native CSS for default styles; custom PostCSS pipelines remain authoritative. */
|
|
@@ -139,6 +154,9 @@ export interface NextConfig {
|
|
|
139
154
|
/** Production checks use the project's installed TypeScript without emitting files. */
|
|
140
155
|
typescript?: { ignoreBuildErrors?: boolean; tsconfigPath?: string; devChecks?: boolean };
|
|
141
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[]};
|
|
142
160
|
cacheLife?: Record<string, import('./cache.cjs').CacheLife>;
|
|
143
161
|
transpilePackages?: string[];
|
|
144
162
|
serverExternalPackages?: string[];
|
|
@@ -196,7 +214,45 @@ export interface StaticBuildOptions {
|
|
|
196
214
|
distDir?: string;
|
|
197
215
|
dev?: false;
|
|
198
216
|
}
|
|
199
|
-
export interface
|
|
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; }
|
|
200
256
|
export interface ProductionBuilder {
|
|
201
257
|
build(options?: {envMode?: 'production' | 'test'}): Promise<BuildResult>;
|
|
202
258
|
stats(): {contexts: number; created: number; reused: number};
|
|
@@ -212,3 +268,12 @@ export function buildStatic(projectRoot: string, options?: StaticBuildOptions):
|
|
|
212
268
|
export function readBuildOutput(buildDirectory: string): Promise<BuildOutput>;
|
|
213
269
|
export function validateBuildOutput(value: unknown): BuildOutput;
|
|
214
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 (
|
|
24
|
-
if (
|
|
25
|
-
|
|
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;
|
|
@@ -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 };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const waiting=new Set(),listeners=new Set();
|
|
3
|
+
const changed=()=>{for(const listener of listeners)listener();};
|
|
4
|
+
const snapshot=()=>waiting.size>0||globalThis.navigator?.onLine===false;
|
|
5
|
+
function subscribe(listener){
|
|
6
|
+
if(!listeners.size){globalThis.window?.addEventListener('online',changed);globalThis.window?.addEventListener('offline',changed);}
|
|
7
|
+
listeners.add(listener);
|
|
8
|
+
return()=>{listeners.delete(listener);if(!listeners.size){globalThis.window?.removeEventListener('online',changed);globalThis.window?.removeEventListener('offline',changed);}};
|
|
9
|
+
}
|
|
10
|
+
function beginWait(){const id={};waiting.add(id);changed();let ended=false;return()=>{if(ended)return;ended=true;waiting.delete(id);changed();};}
|
|
11
|
+
module.exports={snapshot,subscribe,beginWait};
|