@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,12 +1,13 @@
1
+ import type {Route} from './typed-route.cjs';
1
2
  export interface NavigateOptions { scroll?: boolean }
2
3
  export interface PrefetchOptions { onInvalidate?: () => void }
3
4
  export interface AppRouterInstance {
4
- push(href: string, options?: NavigateOptions): void;
5
- replace(href: string, options?: NavigateOptions): void;
5
+ push<T extends string>(href: Route<T>, options?: NavigateOptions): void;
6
+ replace<T extends string>(href: Route<T>, options?: NavigateOptions): void;
6
7
  refresh(): void;
7
8
  back(): void;
8
9
  forward(): void;
9
- prefetch(href: string, options?: PrefetchOptions): void;
10
+ prefetch<T extends string>(href: Route<T>, options?: PrefetchOptions): void;
10
11
  }
11
12
  export class ReadonlyURLSearchParams extends URLSearchParams {
12
13
  append(): never;
@@ -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};
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+ 'use strict';
3
+ const React=require('react'),store=require('./offline-store.cjs');
4
+ function useOffline(){return React.useSyncExternalStore(store.subscribe,store.snapshot,()=>false);}
5
+ module.exports={useOffline};
@@ -0,0 +1,2 @@
1
+ /** True when the browser is offline or a navigation is waiting for connectivity. */
2
+ export declare function useOffline(): boolean;
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+ const {createHash,randomBytes}=require('node:crypto');
3
+ const MAX_VALUE=2*1024*1024,MAX_META=65536;
4
+ const hash=value=>createHash('sha256').update(value).digest('hex');
5
+ const ACQUIRE=`
6
+ if not redis.call('HGET',KEYS[2],'_incarnation') then redis.call('HSET',KEYS[2],'_incarnation',ARGV[3],'_epoch',ARGV[3]) end
7
+ redis.call('PEXPIRE',KEYS[2],ARGV[4])
8
+ local epoch=redis.call('HGET',KEYS[2],'_epoch');local incarnation=redis.call('HGET',KEYS[2],'_incarnation')
9
+ if redis.call('SET',KEYS[1],ARGV[1],'NX','PX',ARGV[2]) then return {1,epoch,incarnation} end
10
+ return {0}`;
11
+ const RELEASE=`if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) end return 0`;
12
+ const READ=`
13
+ local fields=redis.call('HMGET',KEYS[1],'meta','value')
14
+ if not fields[1] or not fields[2] or string.len(fields[1])>65536 or string.len(fields[2])>tonumber(ARGV[2]) then return nil end
15
+ local meta=cjson.decode(fields[1]); local extra=cjson.decode(ARGV[1]); local stale=0
16
+ if redis.call('HGET',KEYS[2],'_incarnation')~=meta.incarnation then return nil end
17
+ local t=redis.call('TIME'); local now=tonumber(t[1])*1000+math.floor(tonumber(t[2])/1000)
18
+ local seen={}
19
+ for _,id in ipairs(meta.tagIds) do seen[id]=true end
20
+ for _,id in ipairs(extra) do seen[id]=true end
21
+ for id,_ in pairs(seen) do
22
+ local value=redis.call('HGET',KEYS[2],id)
23
+ if value then
24
+ local tag=cjson.decode(value)
25
+ if tag.generation~=(meta.versions[id] or '0') then
26
+ if tag.deadline>=0 and tag.deadline<=now then return nil end
27
+ stale=1
28
+ end
29
+ end
30
+ end
31
+ return {fields[1],fields[2],math.max(0,now-meta.storedAt),stale}`;
32
+ const WRITE=`
33
+ if redis.call('GET',KEYS[3])~=ARGV[1] or redis.call('HGET',KEYS[2],'_epoch')~=ARGV[2] then return 0 end
34
+ local meta=cjson.decode(ARGV[3]); meta.versions={};meta.incarnation=ARGV[5];meta.writer=ARGV[1]
35
+ local t=redis.call('TIME'); local now=tonumber(t[1])*1000+math.floor(tonumber(t[2])/1000)
36
+ meta.storedAt=now-tonumber(ARGV[6])
37
+ for _,id in ipairs(meta.tagIds) do
38
+ local value=redis.call('HGET',KEYS[2],id)
39
+ meta.versions[id]=value and cjson.decode(value).generation or '0'
40
+ end
41
+ local encoded=cjson.encode(meta)
42
+ if string.len(encoded)>65536 then return 0 end
43
+ redis.call('HSET',KEYS[1],'meta',encoded,'value',ARGV[4])
44
+ redis.call('PEXPIRE',KEYS[1],ARGV[7]);redis.call('PEXPIRE',KEYS[2],ARGV[8]);return 1`;
45
+ const INVALIDATE=`
46
+ local generation=ARGV[3];local ids=cjson.decode(ARGV[4]);local added=0
47
+ for _,id in ipairs(ids) do if redis.call('HEXISTS',KEYS[1],id)==0 then added=added+1 end end
48
+ if redis.call('HLEN',KEYS[1])+added>4098 then redis.call('DEL',KEYS[1]) end
49
+ if not redis.call('HGET',KEYS[1],'_incarnation') then redis.call('HSET',KEYS[1],'_incarnation',generation) end
50
+ redis.call('HSET',KEYS[1],'_epoch',generation)
51
+ local t=redis.call('TIME');local now=tonumber(t[1])*1000+math.floor(tonumber(t[2])/1000)
52
+ local expiry=tonumber(ARGV[1]);local deadline=expiry<0 and -1 or now+expiry
53
+ for _,id in ipairs(ids) do redis.call('HSET',KEYS[1],id,cjson.encode({generation=generation,deadline=deadline})) end
54
+ redis.call('PEXPIRE',KEYS[1],ARGV[2])
55
+ return 1`;
56
+ const REMOVE=`local meta=redis.call('HGET',KEYS[1],'meta');if meta and cjson.decode(meta).writer==ARGV[1] then return redis.call('DEL',KEYS[1]) end return 0`;
57
+
58
+ function tags(values=[]){
59
+ if(!Array.isArray(values)||values.length>256||values.some(value=>typeof value!=='string'||!value||Buffer.byteLength(value)>8192))throw new TypeError('Redis cache tags must be bounded nonempty strings.');
60
+ return [...new Set(values)];
61
+ }
62
+ function positive(value,fallback,min,max,name){value??=fallback;if(!Number.isSafeInteger(value)||value<min||value>max)throw new TypeError(`Redis cache ${name} must be between ${min} and ${max}.`);return value;}
63
+ function pause(ms,signal){return new Promise((resolve,reject)=>{const done=error=>{clearTimeout(timer);signal?.removeEventListener('abort',abort);error?reject(error):resolve();};const abort=()=>done(signal.reason);const timer=setTimeout(done,ms);signal?.addEventListener('abort',abort,{once:true});if(signal?.aborted)abort();});}
64
+ function boundedRead(reader,ms){return new Promise((resolve,reject)=>{const timer=setTimeout(()=>reject(new Error('Redis cache value read timed out.')),ms);reader.read().then(value=>{clearTimeout(timer);resolve(value);},error=>{clearTimeout(timer);reject(error);});});}
65
+
66
+ /** First-party Cache Components handler; Redis is loaded only when configured. */
67
+ function createRedisCache(options={},maxValueBytes=MAX_VALUE){
68
+ if(!options||typeof options!=='object'||Array.isArray(options))throw new TypeError('Redis cache options must be an object.');
69
+ const known=new Set(['namespace','urlEnv','client','leaseMs','waitMs','commandTimeoutMs','maxAgeSeconds']);
70
+ for(const key of Object.keys(options))if(!known.has(key))throw new TypeError(`Unknown Redis cache option: ${key}`);
71
+ if(options.client!==undefined&&typeof options.client?.withTypeMapping!=='function')throw new TypeError('Redis cache client must be a node-redis client.');
72
+ if(typeof options.namespace!=='string'||!options.namespace||Buffer.byteLength(options.namespace)>256)throw new TypeError('Redis cache requires an application-specific namespace.');
73
+ const urlEnv=options.urlEnv??'REDIS_URL';if(typeof urlEnv!=='string'||!/^[_A-Za-z][_A-Za-z0-9]*$/.test(urlEnv))throw new TypeError('Redis cache urlEnv must name an environment variable.');
74
+ const leaseMs=positive(options.leaseMs,30000,1000,120000,'leaseMs'),waitMs=positive(options.waitMs,2500,100,3500,'waitMs');
75
+ const commandTimeoutMs=positive(options.commandTimeoutMs,1000,100,2000,'commandTimeoutMs'),maxAgeSeconds=positive(options.maxAgeSeconds,604800,1,2592000,'maxAgeSeconds');
76
+ if(waitMs+2*commandTimeoutMs>4500)throw new TypeError('Redis cache waitMs plus twice commandTimeoutMs must not exceed 4500 ms.');
77
+ const prefix='prnext:v1:{'+hash(options.namespace)+'}:',epochKey=prefix+'tags',retention=String(maxAgeSeconds*1000+leaseMs+60000);
78
+ let owned,connecting,closed=false;
79
+ async function client(){
80
+ if(closed)throw new Error('Redis cache is closed.');
81
+ if(options.client){
82
+ if(options.client.isReady===false)throw new Error('Redis cache client is not connected.');
83
+ return options.client;
84
+ }
85
+ if(owned?.isReady)return owned;
86
+ if(!connecting){
87
+ connecting=(async()=>{
88
+ const url=process.env[urlEnv];
89
+ if(!url||!/^rediss?:\/\//.test(url))throw new Error(`Set ${urlEnv} to a Redis connection URL.`);
90
+ // Use node-redis's base client; Redis Stack command packages are not
91
+ // needed by this provider's bounded EVAL/HASH protocol.
92
+ const {createClient}=require('@redis/client');
93
+ owned?.destroy();owned=createClient({url,disableOfflineQueue:true,commandsQueueMaxLength:128,socket:{connectTimeout:commandTimeoutMs,reconnectStrategy:false}});
94
+ owned.on('error',()=>{});await owned.connect();return owned;
95
+ })().finally(()=>{connecting=undefined;});
96
+ }
97
+ return connecting;
98
+ }
99
+ async function command(args,signal){
100
+ const connection=await client();
101
+ // '$' (36) is RESP's binary-safe bulk-string type, as exposed by node-redis.
102
+ const binary=connection.withTypeMapping({36:Buffer});
103
+ const deadline=AbortSignal.timeout(commandTimeoutMs);
104
+ return binary.sendCommand(args,{abortSignal:signal?AbortSignal.any([signal,deadline]):deadline});
105
+ }
106
+ const keys=key=>{if(typeof key!=='string'||!key||Buffer.byteLength(key)>8192)throw new TypeError('Invalid Redis cache key.');const id=hash(key);return {entry:prefix+'entry:'+id,lock:prefix+'lease:'+id};};
107
+ async function acquire(key,{signal}={}){
108
+ const {lock}=keys(key),token=randomBytes(24).toString('hex'),until=performance.now()+waitMs;
109
+ for(;;){
110
+ signal?.throwIfAborted();
111
+ const started=performance.now();
112
+ const result=await command(['EVAL',ACQUIRE,'2',lock,epochKey,token,String(leaseMs),randomBytes(24).toString('hex'),retention],signal);
113
+ if(Number(result[0])===1){let released=false;return {token,started,epoch:String(result[1]),incarnation:String(result[2]),async release(){if(released)return;released=true;await command(['EVAL',RELEASE,'1',lock,token]);}};}
114
+ if(performance.now()>=until)throw Object.assign(new Error('Redis cache fill wait exceeded its budget.'),{code:'PRNEXT_REDIS_BUSY'});
115
+ await pause(Math.min(50,Math.max(1,until-performance.now())),signal);
116
+ }
117
+ }
118
+ async function get(key,softTags=[]){
119
+ const result=await command(['EVAL',READ,'2',keys(key).entry,epochKey,JSON.stringify(tags(softTags).map(hash)),String(maxValueBytes)]);
120
+ if(!result)return undefined;
121
+ const meta=JSON.parse(result[0].toString('utf8')),bytes=result[1];
122
+ if(meta.version!==1||!Buffer.isBuffer(bytes)||bytes.length>maxValueBytes)throw new Error('Invalid Redis cache entry.');
123
+ const age=Number(result[2]);if(!Number.isFinite(age)||age<0)throw new Error('Invalid Redis cache age.');
124
+ let offset=0;
125
+ return {tags:meta.tags,stale:meta.stale,revalidate:Number(result[3])?0:meta.revalidate,expire:meta.expire,timestamp:Math.max(0,Date.now()-age),
126
+ value:new ReadableStream({pull(controller){if(offset===bytes.length){controller.close();return;}const end=Math.min(bytes.length,offset+65536);controller.enqueue(new Uint8Array(bytes.subarray(offset,end)));offset=end;}},{highWaterMark:0})};
127
+ }
128
+ async function set(key,pending,{lease,softTags=[]}={}){
129
+ if(!lease||!Number.isFinite(lease.started)){
130
+ // Observe and release a caller's value even when its lease is missing.
131
+ // An unfenced producer must never publish after an invalidation.
132
+ void Promise.resolve(pending).then(entry=>entry.value.cancel()).catch(()=>{});
133
+ throw new Error('Redis cache writes require the lease returned by acquire().');
134
+ }
135
+ const entry=await pending,entryTags=tags(entry.tags);
136
+ for(const field of ['timestamp','stale','revalidate','expire'])if(!Number.isFinite(entry[field])||entry[field]<0)throw new TypeError('Invalid Redis cache '+field);
137
+ const reader=entry.value.getReader(),chunks=[];let size=0,complete=false;
138
+ const until=performance.now()+commandTimeoutMs;
139
+ let reads=0;
140
+ try{for(;;){if(performance.now()>=until||++reads>10000)throw new Error('Redis cache value read timed out.');const part=await boundedRead(reader,Math.max(1,until-performance.now()));if(part.done){complete=true;break;}if(!(part.value instanceof Uint8Array)||(size+=part.value.byteLength)>maxValueBytes)throw new Error(`Redis cache value exceeds ${maxValueBytes/1024/1024} MiB.`);chunks.push(Buffer.from(part.value));}}
141
+ finally{if(!complete)void reader.cancel().catch(()=>{});reader.releaseLock();}
142
+ const meta={version:1,tags:entryTags,tagIds:tags([...entryTags,...softTags]).map(hash),stale:entry.stale,revalidate:entry.revalidate,expire:entry.expire};
143
+ const metadata=JSON.stringify(meta);if(Buffer.byteLength(metadata)>MAX_META)throw new Error('Redis cache metadata exceeds 64 KiB.');
144
+ const ttl=Math.ceil(Math.min(maxAgeSeconds,entry.expire)*1000);if(ttl<=0)return;
145
+ const {entry:entryKey,lock}=keys(key);
146
+ return Number(await command(['EVAL',WRITE,'3',entryKey,epochKey,lock,lease.token,lease.epoch,metadata,Buffer.concat(chunks,size),lease.incarnation,String(Math.max(0,performance.now()-lease.started)),String(ttl),retention]))===1;
147
+ }
148
+ async function updateTags(values,durations){
149
+ const list=tags(values);if(!list.length)return;
150
+ const expire=durations?.expire;if(expire!==undefined&&(!Number.isFinite(expire)||expire<0))throw new TypeError('Invalid Redis tag expiration.');
151
+ await command(['EVAL',INVALIDATE,'1',epochKey,String(expire===undefined?-1:Math.ceil(expire*1000)),retention,randomBytes(24).toString('hex'),JSON.stringify(list.map(hash))]);
152
+ }
153
+ return {get,set,acquire,updateTags,
154
+ async release(key,lease){if(lease?.token)await command(['EVAL',RELEASE,'1',keys(key).lock,lease.token]);},
155
+ async remove(key,lease){if(lease?.token)await command(['EVAL',REMOVE,'1',keys(key).entry,lease.token]);},
156
+ async refreshTags(){},async getExpiration(){return Infinity;},async close(){closed=true;await connecting?.catch(()=>{});if(owned?.isOpen)owned.destroy();}};
157
+ }
158
+
159
+ /** Legacy incremental handler for HTML/ISR, fetch and unstable_cache. */
160
+ function createRedisIncrementalCache(options){
161
+ const {serialize,deserialize}=require('node:v8');
162
+ const backend=createRedisCache({leaseMs:60000,...options},16*1024*1024);
163
+ const keyFor=key=>'incremental:v8-'+process.versions.v8.split('.')[0]+':'+key;
164
+ function leaseFor(lease){
165
+ if(!lease||!['token','epoch','incarnation'].every(field=>typeof lease[field]==='string'&&/^[a-f0-9]{48}$/.test(lease[field]))||!Number.isFinite(lease.startedAt))throw new Error('Incremental Redis writes require a valid generation lease.');
166
+ return {...lease,started:performance.now()-Math.max(0,Date.now()-lease.startedAt)};
167
+ }
168
+ return class RedisIncrementalCache {
169
+ get prnextLeaseVersion(){return 1;}
170
+ async acquire(key,{signal}={}){
171
+ const lease=await backend.acquire(keyFor(key),{signal});
172
+ return {token:lease.token,epoch:lease.epoch,incarnation:lease.incarnation,startedAt:Date.now()};
173
+ }
174
+ async release(key,lease){await backend.release(keyFor(key),lease);}
175
+ async get(key,context={}){
176
+ const entry=await backend.get(keyFor(key),tags([...new Set([...(context.tags||[]),...(context.softTags||[])])]));
177
+ if(!entry)return null;
178
+ const bytes=Buffer.from(await new Response(entry.value).arrayBuffer());
179
+ const payload=deserialize(bytes);
180
+ if(payload?.version!==1||!Number.isFinite(payload.lastModified)||!payload.value||typeof payload.value.kind!=='string')throw new Error('Invalid incremental Redis payload.');
181
+ return {value:payload.value,lastModified:payload.lastModified,isStale:entry.revalidate===0};
182
+ }
183
+ async set(key,value,context={}){
184
+ const lease=leaseFor(context.prnextLease);
185
+ if(value===null){await backend.remove(keyFor(key),lease);return true;}
186
+ const raw=context.cacheControl?.revalidate??value.revalidate??value._prnext?.revalidate??false;
187
+ if(raw!==false&&(!Number.isFinite(raw)||raw<0))throw new TypeError('Invalid incremental Redis revalidate value.');
188
+ if(raw===0)return true;
189
+ const revalidate=raw===false?31536000:raw;
190
+ const entryTags=tags([...new Set([...(context.tags||[]),...(context.softTags||[])])]);
191
+ const bytes=serialize({version:1,lastModified:Date.now(),value});
192
+ if(bytes.length>16*1024*1024)throw new Error('Incremental Redis entry exceeds 16 MiB.');
193
+ return backend.set(keyFor(key),Promise.resolve({timestamp:Date.now(),stale:0,revalidate,expire:Math.max(revalidate,options?.maxAgeSeconds??604800),tags:entryTags,
194
+ value:new ReadableStream({start(controller){controller.enqueue(bytes);controller.close();}})}),{lease});
195
+ }
196
+ async revalidateTag(values,durations){await backend.updateTags(Array.isArray(values)?values:[values],durations);}
197
+ resetRequestCache(){}
198
+ async close(){await backend.close();}
199
+ };
200
+ }
201
+ module.exports={createRedisCache,createRedisIncrementalCache};
@@ -0,0 +1,37 @@
1
+ /// <reference types="node" />
2
+ export interface RedisCacheOptions {
3
+ /** Unique application namespace, shared by its replicas. */
4
+ namespace: string;
5
+ /** Runtime environment variable; the URL is never embedded in a build. Default REDIS_URL. */
6
+ urlEnv?: string;
7
+ /** An already-connected node-redis client. Its lifecycle remains yours. */
8
+ client?: {readonly isReady?: boolean; withTypeMapping(mapping: {36: BufferConstructor}): {sendCommand(args: (string | Buffer)[], options?: {abortSignal?: AbortSignal}): Promise<unknown>}};
9
+ leaseMs?: number;
10
+ waitMs?: number;
11
+ commandTimeoutMs?: number;
12
+ maxAgeSeconds?: number;
13
+ }
14
+ interface CacheEntry {value: ReadableStream<Uint8Array>; tags: string[]; timestamp: number; stale: number; revalidate: number; expire: number}
15
+ interface Lease {token: string; started: number; epoch: string; incarnation: string; release(): Promise<void>}
16
+ export declare function createRedisCache(options: RedisCacheOptions): {
17
+ get(key: string, softTags?: string[]): Promise<CacheEntry | undefined>;
18
+ set(key: string, entry: Promise<CacheEntry>, options?: {lease?: Lease; softTags?: string[]}): Promise<void>;
19
+ acquire(key: string, options?: {signal?: AbortSignal}): Promise<Lease>;
20
+ refreshTags(): Promise<void>;
21
+ getExpiration(): Promise<number>;
22
+ updateTags(tags: string[], durations?: {expire: number}): Promise<void>;
23
+ close(): Promise<void>;
24
+ };
25
+
26
+ export interface IncrementalRedisLease {token: string; epoch: string; incarnation: string; startedAt: number}
27
+ /** Configure its returned constructor as cacheHandler; Cache Components use createRedisCache separately. */
28
+ export declare function createRedisIncrementalCache(options: RedisCacheOptions): new (context?: unknown) => {
29
+ readonly prnextLeaseVersion: 1;
30
+ acquire(key: string, options?: {signal?: AbortSignal}): Promise<IncrementalRedisLease>;
31
+ release(key: string, lease: IncrementalRedisLease): Promise<void>;
32
+ get(key: string, context?: {tags?: string[]; softTags?: string[]}): Promise<{value: unknown; lastModified: number; isStale?: boolean} | null>;
33
+ set(key: string, value: unknown, context?: {prnextLease?: IncrementalRedisLease; tags?: string[]; softTags?: string[]; cacheControl?: {revalidate: number | false}}): Promise<boolean>;
34
+ revalidateTag(tags: string | string[], durations?: {expire?: number}): Promise<void>;
35
+ resetRequestCache(): void;
36
+ close(): Promise<void>;
37
+ };
@@ -1,4 +1,5 @@
1
1
  import type { ComponentType, ReactNode, ReactElement } from 'react';
2
+ import type {Route,TypedUrlObject} from './typed-route.cjs';
2
3
  export interface UrlObject {
3
4
  auth?: string | null;
4
5
  host?: string | null;
@@ -33,9 +34,9 @@ export interface PRNextRouter extends RouterSnapshot {
33
34
  isFallback: boolean;
34
35
  isPreview: boolean;
35
36
  basePath: string;
36
- push(url: string | UrlObject, as?: string | UrlObject, options?: { scroll?: boolean; shallow?: boolean; locale?: string | false }): Promise<boolean>;
37
- replace(url: string | UrlObject, as?: string | UrlObject, options?: { scroll?: boolean; shallow?: boolean; locale?: string | false }): Promise<boolean>;
38
- prefetch(url: string | UrlObject, as?: string | UrlObject, options?: { priority?: boolean; locale?: string | false }): Promise<void>;
37
+ push<T extends string>(url: Route<T> | TypedUrlObject, as?: string | UrlObject, options?: { scroll?: boolean; shallow?: boolean; locale?: string | false }): Promise<boolean>;
38
+ replace<T extends string>(url: Route<T> | TypedUrlObject, as?: string | UrlObject, options?: { scroll?: boolean; shallow?: boolean; locale?: string | false }): Promise<boolean>;
39
+ prefetch<T extends string>(url: Route<T> | TypedUrlObject, as?: string | UrlObject, options?: { priority?: boolean; locale?: string | false }): Promise<void>;
39
40
  beforePopState(callback: (state: { url: string; as: string; options: { shallow?: boolean; scroll?: boolean } }) => boolean): void;
40
41
  events: RouterEvents;
41
42
  reload(): void;
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- const { CookieStore, ResponseCookieStore } = require('./cookies.cjs');
2
+ const { RequestCookieStore, ResponseCookieStore } = require('./cookies.cjs');
3
3
  const { hasBasePath, addBasePath, removeBasePath } = require('./paths.cjs');
4
4
  const { localePath, withLocale } = require('./locale.cjs');
5
5
 
@@ -14,21 +14,37 @@ function middlewareHeaders(init, headers) {
14
14
  headers.set('x-middleware-override-headers', names.join(','));
15
15
  }
16
16
 
17
+ function nextDataPath(pathname) {
18
+ const match = /^\/_next\/data\/([^/]+)\/(.*)\.json$/.exec(pathname);
19
+ if (!match) return { pathname };
20
+ const parts = match[2].split('/');
21
+ return { buildId: match[1], pathname: parts[0] === 'index' ? '/' : '/' + parts.join('/') };
22
+ }
23
+
17
24
  class NextURL extends URL {
18
25
  constructor(input, baseOrOptions, options) {
19
26
  const base = typeof baseOrOptions === 'string' || baseOrOptions instanceof URL ? baseOrOptions : undefined;
20
27
  const config = options || (base ? {} : baseOrOptions) || {};
21
28
  super(input, base || config.base);
22
29
  this._nextConfig = config.nextConfig || {};
30
+ this._forceLocale = config.forceLocale === true;
31
+ this._parseData = !(this._nextConfig.skipProxyUrlNormalize ?? this._nextConfig.skipMiddlewareUrlNormalize);
23
32
  this._trailingSlash = super.pathname === '/' ? !!this._nextConfig.trailingSlash : super.pathname.endsWith('/');
24
33
  this._basePath = hasBasePath(super.pathname, this._nextConfig.basePath) ? this._nextConfig.basePath || '' : '';
25
- this._locale = this._nextConfig.i18n ? localePath(removeBasePath(super.pathname, this._basePath), {...this._nextConfig.i18n,defaultLocale:this.defaultLocale}).locale : '';
34
+ const logical = this._parseData ? nextDataPath(removeBasePath(super.pathname, this._basePath)) : { pathname: removeBasePath(super.pathname, this._basePath) };
35
+ this._buildId = logical.buildId;
36
+ this._locale = this._nextConfig.i18n ? localePath(logical.pathname, {...this._nextConfig.i18n,defaultLocale:this.defaultLocale}).locale : '';
37
+ }
38
+ get pathname() {
39
+ const raw = removeBasePath(super.pathname, this._basePath);
40
+ return localePath(this._parseData ? nextDataPath(raw).pathname : raw, this._nextConfig.i18n).pathname;
26
41
  }
27
- get pathname() { return localePath(removeBasePath(super.pathname, this._basePath), this._nextConfig.i18n).pathname; }
28
42
  set pathname(value) { super.pathname = addBasePath(withLocale(String(value).startsWith('/') ? value : `/${value}`, this._locale, this.defaultLocale), this._basePath); }
29
43
  get defaultLocale() { return this.domainLocale?.defaultLocale || this._nextConfig.i18n?.defaultLocale; }
30
44
  get domainLocale() { return this._nextConfig.i18n?.domains?.find(value=>value.domain.toLowerCase()===this.host.toLowerCase()); }
31
45
  get locale() { return this._locale; }
46
+ get buildId() { return this._buildId; }
47
+ set buildId(value) { this._buildId = value; }
32
48
  set locale(value) {
33
49
  if (!this._nextConfig.i18n?.locales.includes(value)) throw new TypeError(`Unknown locale: ${value}`);
34
50
  const pathname=this.pathname;this._locale=value;this.pathname=pathname;
@@ -43,8 +59,13 @@ class NextURL extends URL {
43
59
  set trailingSlash(value) { this._trailingSlash = value; }
44
60
  get href() {
45
61
  const url = new URL(super.href);
46
- if (this._nextConfig.i18n) url.pathname=addBasePath(withLocale(this.pathname,this._locale,this.defaultLocale),this._basePath);
47
- if (this._trailingSlash) { if (!url.pathname.endsWith('/')) url.pathname += '/'; }
62
+ let pathname = withLocale(this.pathname, this._locale, this._buildId || this._forceLocale ? undefined : this.defaultLocale);
63
+ if (this._buildId) {
64
+ pathname = pathname.replace(/\/$/, '');
65
+ pathname = '/_next/data/' + this._buildId + pathname + (this.pathname === '/' ? '/index.json' : '.json');
66
+ }
67
+ url.pathname = addBasePath(pathname, this._basePath);
68
+ if (this._trailingSlash && !this._buildId) { if (!url.pathname.endsWith('/')) url.pathname += '/'; }
48
69
  else if (url.pathname !== '/') url.pathname = url.pathname.replace(/\/$/, '');
49
70
  return url.href;
50
71
  }
@@ -54,19 +75,18 @@ class NextURL extends URL {
54
75
  super.href = value;
55
76
  this._trailingSlash = super.pathname === '/' ? !!this._nextConfig.trailingSlash : super.pathname.endsWith('/');
56
77
  this._basePath = hasBasePath(super.pathname, this._nextConfig.basePath) ? this._nextConfig.basePath || '' : '';
57
- this._locale = this._nextConfig.i18n ? localePath(removeBasePath(super.pathname, this._basePath), {...this._nextConfig.i18n,defaultLocale:this.defaultLocale}).locale : '';
78
+ const logical = this._parseData ? nextDataPath(removeBasePath(super.pathname, this._basePath)) : { pathname: removeBasePath(super.pathname, this._basePath) };
79
+ this._buildId = logical.buildId;
80
+ this._locale = this._nextConfig.i18n ? localePath(logical.pathname, {...this._nextConfig.i18n,defaultLocale:this.defaultLocale}).locale : '';
58
81
  }
59
- clone() { return new NextURL(this.href, { nextConfig: this._nextConfig }); }
82
+ clone() { return new NextURL(this.href, { nextConfig: this._nextConfig, forceLocale: this._forceLocale }); }
60
83
  }
61
84
 
62
85
  class NextRequest extends Request {
63
86
  constructor(input, init) {
64
87
  super(input, init);
65
88
  this.nextUrl = new NextURL(this.url, { nextConfig: init?.nextConfig || input?.nextUrl?._nextConfig });
66
- this.cookies = new CookieStore(this.headers.get('cookie') || '', {
67
- mutable: true,
68
- onChange: () => this.headers.set('cookie', this.cookies.toString()),
69
- });
89
+ this.cookies = new RequestCookieStore(this.headers);
70
90
  }
71
91
  }
72
92
 
@@ -1,7 +1,20 @@
1
1
  import type { CookieStore } from './headers.cjs';
2
2
  export interface NextURLConfig { i18n?: import('./index.js').NextConfig['i18n']; basePath?: string; trailingSlash?: boolean; skipMiddlewareUrlNormalize?: boolean; skipProxyUrlNormalize?: boolean }
3
- export class NextURL extends URL { constructor(input: string | URL, options?: { base?: string | URL; nextConfig?: NextURLConfig }); constructor(input: string | URL, base: string | URL, options?: { nextConfig?: NextURLConfig }); clone(): NextURL; locale: string; readonly defaultLocale?: string; readonly domainLocale?: NonNullable<NonNullable<import('./index.js').NextConfig['i18n']>['domains']>[number]; basePath: string; trailingSlash: boolean }
4
- export class NextRequest extends Request { constructor(input: RequestInfo | URL, init?: RequestInit & { nextConfig?: NextURLConfig }); nextUrl: NextURL; cookies: CookieStore }
3
+ export class NextURL extends URL { constructor(input: string | URL, options?: { base?: string | URL; nextConfig?: NextURLConfig; forceLocale?: boolean }); constructor(input: string | URL, base: string | URL, options?: { nextConfig?: NextURLConfig; forceLocale?: boolean }); clone(): NextURL; buildId: string | undefined; locale: string; readonly defaultLocale?: string; readonly domainLocale?: NonNullable<NonNullable<import('./index.js').NextConfig['i18n']>['domains']>[number]; basePath: string; trailingSlash: boolean }
4
+ export interface RequestCookie { name: string; value: string }
5
+ export interface RequestCookieStore extends Iterable<[string, RequestCookie]> {
6
+ readonly size: number;
7
+ get(name: string | {name: string}): RequestCookie | undefined;
8
+ getAll(name?: string | {name: string}): RequestCookie[];
9
+ has(name: string): boolean;
10
+ set(name: string, value: string): this;
11
+ set(cookie: RequestCookie): this;
12
+ delete(name: string): boolean;
13
+ delete(names: string[]): boolean[];
14
+ clear(): this;
15
+ toString(): string;
16
+ }
17
+ export class NextRequest extends Request { constructor(input: RequestInfo | URL, init?: RequestInit & { nextConfig?: NextURLConfig }); nextUrl: NextURL; cookies: RequestCookieStore }
5
18
  export interface MiddlewareResponseInit extends ResponseInit { request?: { headers?: Headers } }
6
19
  export class NextResponse<Body = unknown> extends Response {
7
20
  cookies: CookieStore;
@@ -27,6 +27,13 @@ function dynamicUsage(reason, request = context()) {
27
27
  function staticBailout(reason, request = context()) {
28
28
  if (diagnostics.enabled) diagnostics.record('dynamic', { route: diagnostics.route(request), reason });
29
29
  const state = request?.staticState;
30
+ if (!state && request?.navigationStage && ['an uncached fetch', 'connection()', 'unstable_noStore()'].includes(reason)
31
+ && !require('./data-cache.cjs').inCacheScope()) require('./navigation-stages.cjs').postponeStage(reason);
32
+ const requirement = request?.cacheConfig?.ensureStatic;
33
+ if (!state && (request?.navigationStage === 'shell' && ['shell', 'prefetch', 'navigation'].includes(requirement)
34
+ || request?.navigationStage === 'prefetch' && ['prefetch', 'navigation'].includes(requirement))) {
35
+ require('./navigation-stages.cjs').postponeStage(reason);
36
+ }
30
37
  if (!state) return true;
31
38
  if (state.partial && state.mode === 'auto') {
32
39
  state.dynamicReasons.add(reason);
@@ -0,0 +1,17 @@
1
+ import type {UrlObject} from './router.cjs';
2
+ declare global { namespace PRNext { interface RouteMap {} interface RouteConfig {} } }
3
+ type GeneratedRoutes = keyof PRNext.RouteMap extends never ? PRNext.RouteConfig extends {enabled:true} ? true : false : true;
4
+ export type RoutePattern = GeneratedRoutes extends true ? Extract<keyof PRNext.RouteMap,string> : string;
5
+ export type RouteParameters<P extends string> = P extends keyof PRNext.RouteMap ? PRNext.RouteMap[P] : Record<string,string|string[]|undefined>;
6
+ type Suffix = '' | `?${string}` | `#${string}`;
7
+ type External = `https://${string}` | `http://${string}` | `mailto:${string}` | `tel:${string}` | `#${string}` | `?${string}`;
8
+ type Expand<P extends string> = P extends `${infer A}/[[...${string}]]` ? A | `${A}/${string}` : P extends `${infer A}[${string}]${infer B}` ? `${A}${string}${Expand<B>}` : P;
9
+ type Strip<P extends string> = P extends `${infer A}#${string}` ? Strip<A> : P extends `${infer A}?${string}` ? A : P;
10
+ type Segment<S extends string> = S extends ''|'.'|'..' ? false : S extends `${string}/${string}`|`${string}\\${string}` ? false : true;
11
+ type Segments<S extends string> = S extends `${infer H}/${infer R}` ? Segment<H> extends true ? Segments<R> : false : Segment<S>;
12
+ type Match<P extends string,S extends string> = P extends `${infer A}/[[...${string}]]` ? S extends A ? true : Match<`${A}/[...rest]`,S> : P extends `[...${string}]` ? Segments<S> : P extends `${infer H}/${infer R}` ? S extends `${infer V}/${infer T}` ? H extends `[${string}]` ? Segment<V> extends true ? Match<R,T> : false : H extends V ? Match<R,T> : false : false : P extends `[${string}]` ? Segment<S> : P extends S ? true : false;
13
+ type Matching<S extends string,P extends string=RoutePattern> = P extends unknown ? Match<P,S> extends true ? true : never : never;
14
+ type Suggestions = Expand<RoutePattern> extends infer P extends string ? `${P}${Suffix}` : never;
15
+ export type Route<T extends string=string> = GeneratedRoutes extends false ? T : string extends T ? Suggestions|External : T extends External ? T : true extends Matching<Strip<T>> ? T : never;
16
+ export type TypedUrlObject = GeneratedRoutes extends false ? UrlObject : {[P in RoutePattern]: Omit<UrlObject,'pathname'|'query'> & {pathname:P} & ({} extends RouteParameters<P> ? {query?:RouteParameters<P>&Record<string,unknown>} : {query:RouteParameters<P>&Record<string,unknown>})}[RoutePattern];
17
+ export function route<P extends RoutePattern>(pattern:P,...args:{} extends RouteParameters<P> ? [params?:RouteParameters<P>] : [params:RouteParameters<P>]): Route;
@@ -35,6 +35,9 @@ function recordCacheDependency({ tags = [], revalidate } = {}) {
35
35
  }
36
36
  function metadata(scope, context) { return { ...scope.life, revalidate: Math.min(scope.life.revalidate, scope.life.expire), tags: [...scope.tags], paths: getCachePaths(context), ...(scope.externalCache ? { externalCache: true } : {}) }; }
37
37
  function propagate(info, parent, context) {
38
+ if (context?.staticState && context.cacheConfig?.ensureStatic === 'navigation' && (info.expire < 300 || info.stale < 30 || info.revalidate === 0)) {
39
+ dynamicUsage('a cache lifetime with expire below 300 seconds, stale below 30 seconds, or revalidate=0', context);
40
+ }
38
41
  trackStaticDependency(info, context);
39
42
  if (!parent || parent === true) return;
40
43
  if (info.externalCache) parent.externalCache = true;