@thomas.f/prnext 0.1.19-alpha → 0.1.21-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/app.mjs +46 -78
- package/build/build-lock.mjs +2 -71
- package/build/build-retention.mjs +119 -0
- package/build/build-timings.mjs +38 -0
- package/build/bundle-runtime.mjs +17 -16
- package/build/cache-command.mjs +31 -0
- package/build/check.mjs +41 -3
- package/build/compilation-cache.mjs +32 -12
- package/build/compilation-storage.mjs +190 -0
- package/build/compiler.mjs +5 -4
- package/build/config.mjs +23 -2
- package/build/create.mjs +1 -1
- package/build/css-modules.mjs +4 -0
- package/build/dev-action-graph.mjs +21 -0
- package/build/flight-summary.mjs +64 -0
- package/build/font-cache.mjs +9 -6
- package/build/font-files.mjs +1 -1
- package/build/index.mjs +57 -36
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +7 -2
- package/build/native-discovery.mjs +18 -6
- package/build/native-ppr.mjs +32 -0
- package/build/npm-packages.mjs +2 -2
- package/build/og.mjs +121 -1
- package/build/parallel-plan.mjs +55 -0
- package/build/persistent-cache.mjs +29 -24
- package/build/production-check.mjs +34 -0
- package/build/react-compiler-report.mjs +31 -8
- package/build/react-compiler.mjs +101 -10
- package/build/server-config.mjs +35 -0
- package/build/source-analysis.mjs +108 -5
- package/build/standalone-copy.mjs +48 -0
- package/build/standalone.mjs +2 -22
- package/build/static-adapter.mjs +7 -3
- package/build/transform-cache.mjs +17 -3
- package/build/transform.mjs +38 -6
- package/build/typescript-dev.mjs +3 -1
- package/cli.mjs +27 -5
- package/compat/app-layout-context.cjs +7 -3
- package/compat/diagnostics.cjs +5 -4
- package/compat/flight-delta.cjs +47 -12
- package/compat/index.d.ts +24 -1
- package/compat/navigation-authorization.cjs +8 -1
- package/compat/runtime-sample.cjs +56 -0
- package/compat/use-cache.cjs +5 -2
- package/docs/ai/compatibility.md +113 -10
- package/docs/ai/deployment.md +144 -0
- package/docs/ai/quickstart.md +5 -0
- package/docs/ai/troubleshooting.md +22 -0
- package/package.json +25 -22
- package/release/fontkit.mjs +31 -0
- package/release/verify.mjs +3 -0
- package/runtime/api.mjs +5 -2
- package/runtime/app-client.mjs +246 -54
- package/runtime/app-partial.mjs +2 -10
- package/runtime/app-prefetch.mjs +2 -2
- package/runtime/app-render.mjs +42 -13
- package/runtime/app-stream-channel.mjs +20 -6
- package/runtime/app-stream-html.mjs +30 -11
- package/runtime/app-styles.mjs +22 -4
- package/runtime/cache-components-rsc.mjs +26 -6
- package/runtime/cache-result.mjs +58 -0
- package/runtime/compile-cache-retention.mjs +53 -0
- package/runtime/compile-cache.mjs +28 -1
- package/runtime/dev-client.mjs +32 -8
- package/runtime/dev-compiler-broker.mjs +20 -23
- package/runtime/dev-gateway.mjs +23 -6
- package/runtime/dev-route-selection.mjs +64 -0
- package/runtime/dev-runtime-pool.mjs +3 -2
- package/runtime/dev-runtime-spare.mjs +1 -1
- package/runtime/dev-session-retention.mjs +154 -0
- package/runtime/development-flight.mjs +59 -0
- package/runtime/development-queue.mjs +33 -0
- package/runtime/development.mjs +37 -16
- package/runtime/env.mjs +9 -0
- package/runtime/flight-delta.mjs +31 -5
- package/runtime/flight-diagnostics.mjs +48 -0
- package/runtime/flight-tap.mjs +10 -5
- package/runtime/html-envelope.mjs +34 -0
- package/runtime/loopback-rpc.mjs +92 -0
- package/runtime/native-ppr.mjs +88 -0
- package/runtime/og/NOTICE.txt +1 -1
- package/runtime/og/index.node.mjs +76 -31216
- package/runtime/og/upstream.json +115 -0
- package/runtime/persistent/client.mjs +5 -3
- package/runtime/persistent/common.mjs +7 -18
- package/runtime/persistent/daemon.mjs +4 -12
- package/runtime/persistent/health.mjs +58 -0
- package/runtime/persistent/native-proxy.mjs +1 -1
- package/runtime/persistent/watchdog.mjs +12 -27
- package/runtime/prewarm.mjs +35 -0
- package/runtime/project-lock.mjs +71 -0
- package/runtime/request-transport.mjs +112 -32
- package/runtime/rsc-worker.mjs +58 -8
- package/runtime/storage-path.mjs +36 -0
- package/runtime/trace.mjs +44 -4
- package/runtime/worker.mjs +37 -17
- package/vendor/fontkit/NOTICE.txt +5 -0
- package/vendor/fontkit/SOURCE-NOTICES.txt +209 -0
- package/vendor/fontkit/index.mjs +3 -0
- package/vendor/fontkit/licenses/@swc__helpers/LICENSE +201 -0
- package/vendor/fontkit/licenses/base64-js/LICENSE +21 -0
- package/vendor/fontkit/licenses/brotli/readme.md +61 -0
- package/vendor/fontkit/licenses/clone/LICENSE +18 -0
- package/vendor/fontkit/licenses/dfa/README.md +83 -0
- package/vendor/fontkit/licenses/fast-deep-equal/LICENSE +21 -0
- package/vendor/fontkit/licenses/fontkit/README.md +266 -0
- package/vendor/fontkit/licenses/restructure/LICENSE +21 -0
- package/vendor/fontkit/licenses/tiny-inflate/LICENSE +21 -0
- package/vendor/fontkit/licenses/tslib/LICENSE.txt +12 -0
- package/vendor/fontkit/licenses/unicode-properties/LICENSE +8 -0
- package/vendor/fontkit/licenses/unicode-trie/LICENSE +8 -0
- package/vendor/fontkit/upstream.json +215 -0
package/runtime/rsc-worker.mjs
CHANGED
|
@@ -32,6 +32,7 @@ import { PartialTemplates, canResumePartialDirectly, partialTemplateReuse } from
|
|
|
32
32
|
import { createAfterScope } from '../compat/after.cjs';
|
|
33
33
|
import {renderKeyFor} from './app-render-key.mjs';
|
|
34
34
|
import { selectAppStyles } from './app-style-selection.mjs';
|
|
35
|
+
import {createFlightMeasurement} from './flight-diagnostics.mjs';
|
|
35
36
|
|
|
36
37
|
installFetchCache();
|
|
37
38
|
if (process.env.PRNEXT_INSPECT_DIR) parentPort.postMessage({type:'rsc-ready'});
|
|
@@ -173,7 +174,24 @@ function errorBoundary(entry, error, production, before = Infinity) {
|
|
|
173
174
|
return index < 0 ? null : { kind: 'error', index, error: publicError(error, production) };
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
|
|
177
|
+
function renderFlight(request, fallback = null, channel = null) {
|
|
178
|
+
const measurement=createFlightMeasurement(request,'rsc');
|
|
179
|
+
if(!measurement)return renderFlightModel(request,fallback,channel);
|
|
180
|
+
return observeFlightModel(request,fallback,channel,measurement);
|
|
181
|
+
}
|
|
182
|
+
async function observeFlightModel(request,fallback,channel,measurement){
|
|
183
|
+
let status;
|
|
184
|
+
try{
|
|
185
|
+
const result=await renderFlightModel(request,fallback,channel,measurement,measurement?value=>{status=value;}:undefined);
|
|
186
|
+
measurement?.finish('completed',result?.status??status);
|
|
187
|
+
return result;
|
|
188
|
+
}catch(error){
|
|
189
|
+
const reason=channel?.signal?.reason;
|
|
190
|
+
measurement?.finish(error?.statusCode===504||reason?.statusCode===504?'timedOut':channel?.signal?.aborted?'cancelled':'failed',status??error?.statusCode);
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function renderFlightModel(request, fallback, channel,measurement,onStatus) {
|
|
177
195
|
return runRequestContext({ ...request, phase: 'render' }, async () => {
|
|
178
196
|
const cacheContext = currentRequest();
|
|
179
197
|
try {
|
|
@@ -220,7 +238,9 @@ async function renderFlight(request, fallback = null, channel = null) {
|
|
|
220
238
|
const stored = request.production
|
|
221
239
|
? await partialTemplates.get(request.partialFlight, consumer.serverConsumerManifest, decode, renderSignal)
|
|
222
240
|
: await decode();
|
|
223
|
-
const
|
|
241
|
+
const direct=!model.routing&&canResumePartialDirectly(stored);
|
|
242
|
+
measurement?.partial(direct?'direct':'normalized');
|
|
243
|
+
const live = direct ? model : await createFromReadableStream(boundedPartialFlight(renderToReadableStream(model, bundlerConfig, {
|
|
224
244
|
signal: renderSignal,
|
|
225
245
|
onError(error) { return publicError(error, request.production).digest; },
|
|
226
246
|
})), consumer);
|
|
@@ -295,9 +315,14 @@ async function renderFlight(request, fallback = null, channel = null) {
|
|
|
295
315
|
let offset = 0;
|
|
296
316
|
for (const item of batch) { value.set(item, offset); offset += item.byteLength; }
|
|
297
317
|
}
|
|
298
|
-
if (!channel.started)
|
|
299
|
-
|
|
318
|
+
if (!channel.started) {
|
|
319
|
+
const status=request.partialFlight?200:fallback?.kind==='not-found'||(entry.routing&&renderError?.digest==='NEXT_HTTP_ERROR_FALLBACK;404')?404:renderError||fallback?.kind==='error'?500:request.responseStatus||200;
|
|
320
|
+
onStatus?.(status);
|
|
321
|
+
channel.start({status,rscError:Boolean(ordinaryRenderError(renderError)),boundaryIndex:fallback?.index,headers:request.responseHeaders});
|
|
322
|
+
}
|
|
323
|
+
const emittedBytes=measurement?value.byteLength:0;
|
|
300
324
|
await channel.write(value, abort.signal, Boolean(next.result?.done));
|
|
325
|
+
measurement?.chunk(emittedBytes);
|
|
301
326
|
continue;
|
|
302
327
|
}
|
|
303
328
|
// The byte stream hands ownership of each Uint8Array to its reader.
|
|
@@ -315,7 +340,7 @@ async function renderFlight(request, fallback = null, channel = null) {
|
|
|
315
340
|
if (channel?.started && renderError.statusCode !== 504) { channel.end(); return; }
|
|
316
341
|
if (renderError.digest === 'NEXT_HTTP_ERROR_FALLBACK;404' && !entry.routing) {
|
|
317
342
|
const boundary = (entry.segments || []).findLastIndex((segment, index) => index < (fallback?.index ?? Infinity) && segment.notFound);
|
|
318
|
-
if (boundary >= 0) return
|
|
343
|
+
if (boundary >= 0) return renderFlightModel({ ...request, cacheState: cacheContext.cacheState, staticState: cacheContext.staticState }, { kind: 'not-found', index: boundary }, channel,measurement,onStatus);
|
|
319
344
|
} else if (ordinaryRenderError(renderError)) {
|
|
320
345
|
if (cacheContext.staticState) throw renderError;
|
|
321
346
|
// Preserve the original errored Flight tree. React's browser
|
|
@@ -329,7 +354,7 @@ async function renderFlight(request, fallback = null, channel = null) {
|
|
|
329
354
|
}
|
|
330
355
|
}
|
|
331
356
|
if (channel) {
|
|
332
|
-
if (!channel.started) channel.start({ status: request.responseStatus || 200, headers: request.responseHeaders });
|
|
357
|
+
if (!channel.started) {onStatus?.(request.responseStatus||200);channel.start({ status: request.responseStatus || 200, headers: request.responseHeaders });}
|
|
333
358
|
channel.end();
|
|
334
359
|
return;
|
|
335
360
|
}
|
|
@@ -340,6 +365,7 @@ async function renderFlight(request, fallback = null, channel = null) {
|
|
|
340
365
|
for (const chunk of chunks) {
|
|
341
366
|
body.set(chunk, offset);
|
|
342
367
|
offset += chunk.byteLength;
|
|
368
|
+
measurement?.chunk(chunk.byteLength);
|
|
343
369
|
}
|
|
344
370
|
return { body: body.buffer, status: staticNavigation?.status || (fallback?.kind === 'not-found' || (entry.routing && renderError?.digest === 'NEXT_HTTP_ERROR_FALLBACK;404') ? 404 : renderError || fallback?.kind === 'error' ? 500 : request.responseStatus || 200),
|
|
345
371
|
rscError: Boolean(ordinaryRenderError(renderError)), boundaryIndex: fallback?.index, headers: { ...request.responseHeaders, ...staticNavigation?.headers },
|
|
@@ -409,11 +435,35 @@ const activeRequests = new Set();
|
|
|
409
435
|
const responseCompletions = new Map();
|
|
410
436
|
const manifests = new Map();
|
|
411
437
|
const artifacts = new Map();
|
|
438
|
+
const preloading = new Map();
|
|
412
439
|
parentPort.on('message', async ({ id, request, type, value, token }) => {
|
|
413
440
|
if(type==='telemetry-flush'){await drainTraces();parentPort.postMessage({type:'telemetry-flushed',token});return;}
|
|
441
|
+
if(type==='preload-cancel'){preloading.get(id)?.abort(new Error('Framework prewarm was cancelled'));return;}
|
|
414
442
|
if(type==='preload'){
|
|
415
|
-
|
|
416
|
-
|
|
443
|
+
const abort=new AbortController();preloading.set(id,abort);let timer,reader,reply,transfer;
|
|
444
|
+
try{
|
|
445
|
+
if(value.options.frameworkWarmup){
|
|
446
|
+
if(value.files.length||!Number.isFinite(value.options.timeoutMs)||value.options.timeoutMs<=0||value.options.timeoutMs>30000||!Number.isFinite(value.options.maxRssMb)||value.options.maxRssMb<=0||value.options.maxRssMb>2048)throw new TypeError('Invalid synthetic renderer prewarm');
|
|
447
|
+
const deadline=performance.now()+value.options.timeoutMs;
|
|
448
|
+
const available=()=>{abort.signal.throwIfAborted();if(performance.now()>=deadline||process.memoryUsage().rss>=value.options.maxRssMb*1024*1024)throw new Error('Synthetic renderer prewarm budget exhausted');};
|
|
449
|
+
timer=setTimeout(()=>abort.abort(new Error('Synthetic renderer prewarm timed out')),value.options.timeoutMs);
|
|
450
|
+
available();
|
|
451
|
+
// Host elements and literals only: no routeEntry, application imports,
|
|
452
|
+
// fetches, caches, contexts or handlers participate in this warm-up.
|
|
453
|
+
const model={tree:React.createElement('html',null,React.createElement('head'),React.createElement('body',null,React.createElement('main',null,React.createElement('ul',null,Array.from({length:8},(_,index)=>React.createElement('li',{key:index},'PRNext framework '+index)))))),router:{pathname:'/_prnext-prewarm',query:{}}};
|
|
454
|
+
reader=renderToReadableStream(model,{}, {signal:abort.signal,onError:()=> 'PRNEXT_FRAMEWORK_PREWARM'}).getReader();
|
|
455
|
+
const chunks=[];let length=0;
|
|
456
|
+
for(;;){available();const next=await reader.read();if(next.done)break;length+=next.value.byteLength;if(length>65536)throw new Error('Synthetic renderer prewarm Flight exceeds its byte budget');chunks.push(new Uint8Array(next.value));}
|
|
457
|
+
available();const bytes=new Uint8Array(length);let offset=0;for(const chunk of chunks){bytes.set(chunk,offset);offset+=chunk.length;}
|
|
458
|
+
await reader.cancel();reader=undefined;
|
|
459
|
+
reply={type:'preloaded',id,frameworkFlight:bytes.buffer};transfer=[bytes.buffer];
|
|
460
|
+
}else{
|
|
461
|
+
abort.signal.throwIfAborted();await preloadModules(value.files,value.options,file=>routeEntry({modulePath:file,production:true}));abort.signal.throwIfAborted();reply={type:'preloaded',id};
|
|
462
|
+
}
|
|
463
|
+
}catch(error){reply={type:'preloaded',id,error:error.message};}
|
|
464
|
+
finally{clearTimeout(timer);await reader?.cancel().catch(()=>{});preloading.delete(id);}
|
|
465
|
+
parentPort.postMessage(reply,transfer);
|
|
466
|
+
return;
|
|
417
467
|
}
|
|
418
468
|
if (type === 'after-response') { responseCompletions.get(id)?.(); return; }
|
|
419
469
|
// Only the host arms these probes, while completed responses still have
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {lstat, realpath} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const normalized = filename => process.platform === 'win32' ? path.resolve(filename).toLowerCase() : path.resolve(filename);
|
|
5
|
+
export const sameStoragePath = (left, right) => normalized(left) === normalized(right);
|
|
6
|
+
const ordinary = info => info?.isDirectory() && !info.isSymbolicLink();
|
|
7
|
+
const identity = (left, right) => left.dev === right.dev && left.ino === right.ino && left.birthtimeMs === right.birthtimeMs;
|
|
8
|
+
|
|
9
|
+
/** Callers supply canonical roots and explicit descendants, never glob results. */
|
|
10
|
+
export async function captureStorageDirectory(directory) {
|
|
11
|
+
try {
|
|
12
|
+
directory = path.resolve(directory);
|
|
13
|
+
const info = await lstat(directory);
|
|
14
|
+
if (!ordinary(info) || !sameStoragePath(await realpath(directory), directory)) return null;
|
|
15
|
+
const current = await lstat(directory);
|
|
16
|
+
if (!ordinary(current) || !identity(info, current)) return null;
|
|
17
|
+
return {directory, dev: info.dev, ino: info.ino, birthtimeMs: info.birthtimeMs};
|
|
18
|
+
} catch { return null; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Recheck ancestors around each filesystem operation. Node does not expose an
|
|
22
|
+
* atomic openat/unlinkat boundary: these guards reject observed replacements,
|
|
23
|
+
* but are not isolation from a malicious process with the same filesystem rights. */
|
|
24
|
+
export async function verifyStorageDirectories(snapshots) {
|
|
25
|
+
if (!Array.isArray(snapshots) || !snapshots.length) return false;
|
|
26
|
+
for (const snapshot of snapshots) {
|
|
27
|
+
if (!snapshot) return false;
|
|
28
|
+
try {
|
|
29
|
+
const info = await lstat(snapshot.directory);
|
|
30
|
+
if (!ordinary(info) || !identity(info, snapshot) || !sameStoragePath(await realpath(snapshot.directory), snapshot.directory)) return false;
|
|
31
|
+
const current = await lstat(snapshot.directory);
|
|
32
|
+
if (!ordinary(current) || !identity(current, snapshot)) return false;
|
|
33
|
+
} catch { return false; }
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
package/runtime/trace.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import {isControlError} from './navigation.mjs';
|
|
|
2
2
|
import {AsyncLocalStorage} from 'node:async_hooks';
|
|
3
3
|
import {randomBytes} from 'node:crypto';
|
|
4
4
|
import {createRequire} from 'node:module';
|
|
5
|
+
import {channel} from 'node:diagnostics_channel';
|
|
5
6
|
const scope=new AsyncLocalStorage(),rawFetch=globalThis.fetch;
|
|
6
7
|
let api;
|
|
7
8
|
try{api=createRequire(import.meta.url)('@opentelemetry/api');}catch{}
|
|
@@ -9,6 +10,17 @@ const endpoint=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT||(process.env.OTEL
|
|
|
9
10
|
const protocol=process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL||process.env.OTEL_EXPORTER_OTLP_PROTOCOL;
|
|
10
11
|
const enabled=process.env.OTEL_SDK_DISABLED!=='true'&&endpoint&&(!protocol||protocol==='http/json');
|
|
11
12
|
const queue=[];let timer,flushing;
|
|
13
|
+
const diagnostics=channel('prnext.telemetry');
|
|
14
|
+
const counters={droppedSpans:0,rejectedSpans:0,exportAttempts:0,exportFailures:0,failedBatches:0,exportedSpans:0};
|
|
15
|
+
let lastWarning=-Infinity;
|
|
16
|
+
export function telemetryDiagnostics(){return {enabled:!!enabled,queuedSpans:queue.length,...counters};}
|
|
17
|
+
function notify(reason){
|
|
18
|
+
const snapshot=telemetryDiagnostics();
|
|
19
|
+
// This local channel and fixed-field warning never pass through the exporter.
|
|
20
|
+
// No collector URL, headers, exception or request attributes are included.
|
|
21
|
+
if(diagnostics.hasSubscribers)diagnostics.publish({reason,...snapshot});
|
|
22
|
+
if(reason!=='exported'&&Date.now()-lastWarning>=10000){lastWarning=Date.now();try{console.error('[prnext] telemetry '+JSON.stringify({reason,...snapshot}));}catch{}}
|
|
23
|
+
}
|
|
12
24
|
const epoch=BigInt(Date.now())*1000000n-process.hrtime.bigint();
|
|
13
25
|
const nanos=()=>String(epoch+process.hrtime.bigint());
|
|
14
26
|
export function parseTraceparent(value){
|
|
@@ -23,9 +35,25 @@ export function propagatedFetch(input,init){
|
|
|
23
35
|
return [input,{...init,headers}];
|
|
24
36
|
}
|
|
25
37
|
function enqueue(span){
|
|
26
|
-
if(!enabled||!span.flags
|
|
38
|
+
if(!enabled||!span.flags)return;
|
|
39
|
+
if(queue.length>=1024){counters.droppedSpans++;notify('queue_full');return;}
|
|
27
40
|
queue.push(span);if(!timer)timer=setTimeout(()=>{timer=undefined;void flushTraces();},200).unref();
|
|
28
41
|
}
|
|
42
|
+
async function collectorResult(response,count){
|
|
43
|
+
const reader=response.body?.getReader();if(!reader)return {rejected:0,warning:false};
|
|
44
|
+
const chunks=[];let size=0;
|
|
45
|
+
try{while(true){const {done,value}=await reader.read();if(done)break;size+=value.byteLength;if(size>16384)throw Error('collector response too large');chunks.push(value);}}
|
|
46
|
+
catch(error){await reader.cancel().catch(()=>{});throw error;}finally{reader.releaseLock();}
|
|
47
|
+
const body=size?JSON.parse(Buffer.concat(chunks,size).toString('utf8')):{};
|
|
48
|
+
if(!body||typeof body!=='object'||Array.isArray(body))throw Error('invalid collector response');
|
|
49
|
+
const partial=body.partialSuccess??body.partial_success;if(partial==null)return {rejected:0,warning:false};
|
|
50
|
+
if(typeof partial!=='object'||Array.isArray(partial))throw Error('invalid collector partial success');
|
|
51
|
+
const raw=partial.rejectedSpans??partial.rejected_spans??0;
|
|
52
|
+
if(typeof raw!=='number'&&!(typeof raw==='string'&&/^\d{1,20}$/.test(raw)))throw Error('invalid collector rejection count');
|
|
53
|
+
const rejected=Number(raw);
|
|
54
|
+
if(!Number.isSafeInteger(rejected)||rejected<0||rejected>count)throw Error('invalid collector rejection count');
|
|
55
|
+
return {rejected,warning:typeof(partial.errorMessage??partial.error_message)==='string'&&!!(partial.errorMessage??partial.error_message)};
|
|
56
|
+
}
|
|
29
57
|
export async function flushTraces(){
|
|
30
58
|
if(flushing)return flushing;
|
|
31
59
|
if(!queue.length)return;
|
|
@@ -36,15 +64,27 @@ export async function flushTraces(){
|
|
|
36
64
|
const spans=queue.splice(0,128);
|
|
37
65
|
flushing=(async()=>{
|
|
38
66
|
const body=JSON.stringify({resourceSpans:[{resource:{attributes:[{key:'service.name',value:{stringValue:process.env.OTEL_SERVICE_NAME||'prnext'}}]},scopeSpans:[{scope:{name:'prnext.runtime'},spans}]}]});
|
|
67
|
+
let exported=false;
|
|
39
68
|
for(let attempt=0;attempt<3;attempt++){
|
|
40
|
-
let retry=false;
|
|
69
|
+
let retry=false,receivedSuccess=false,accepted;
|
|
70
|
+
counters.exportAttempts++;
|
|
41
71
|
try{
|
|
42
72
|
const response=await rawFetch(endpoint,{method:'POST',redirect:'error',headers,body,signal:AbortSignal.timeout(1000)});
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
receivedSuccess=response.ok;
|
|
74
|
+
if(receivedSuccess)accepted=await collectorResult(response,spans.length);
|
|
75
|
+
else{retry=[429,502,503,504].includes(response.status);await response.body?.cancel();}
|
|
76
|
+
}catch{retry=!receivedSuccess;} // Never replay a successful/partial acknowledgement.
|
|
77
|
+
if(accepted){
|
|
78
|
+
exported=true;counters.exportedSpans+=spans.length-accepted.rejected;
|
|
79
|
+
if(accepted.rejected){counters.rejectedSpans+=accepted.rejected;counters.droppedSpans+=accepted.rejected;counters.exportFailures++;counters.failedBatches++;notify('collector_rejected');}
|
|
80
|
+
else notify(accepted.warning?'collector_warning':'exported');
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
counters.exportFailures++;
|
|
45
84
|
if(!retry)break;
|
|
46
85
|
if(attempt<2)await new Promise(resolve=>setTimeout(resolve,100*2**attempt).unref());
|
|
47
86
|
}
|
|
87
|
+
if(!exported){counters.failedBatches++;counters.droppedSpans+=spans.length;notify('export_failed');}
|
|
48
88
|
})().finally(()=>{flushing=undefined;if(queue.length&&!timer)timer=setTimeout(()=>{timer=undefined;void flushTraces();},200).unref();});
|
|
49
89
|
return flushing;
|
|
50
90
|
}
|
package/runtime/worker.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import {deferredCompiler} from './dev-compiler-broker.mjs';
|
|
|
7
7
|
import { requestFrames } from './request-transport.mjs';
|
|
8
8
|
import {scheduleCompileCacheFlush} from './compile-cache.mjs';
|
|
9
9
|
import {preloadModules} from './preload.mjs';
|
|
10
|
+
import {prewarmFramework} from './prewarm.mjs';
|
|
10
11
|
import { requestWork } from './request-work.mjs';
|
|
11
12
|
import { freezeClientModules } from './worker-metadata.mjs';
|
|
12
13
|
import { AsyncResource } from 'node:async_hooks';
|
|
@@ -70,6 +71,29 @@ const routes = new Map(manifest.routes.map(route => [route.id, route]));
|
|
|
70
71
|
const prerendered = new Set((manifest.prerendered || []).map(item => item.path));
|
|
71
72
|
const deployment={distDir,manifest,routes,prerendered,modules:new Map()};
|
|
72
73
|
const deployments=new Map([[distDir,deployment]]);
|
|
74
|
+
const prewarmRequested = process.env.PRNEXT_WORKER_PREWARM === '1';
|
|
75
|
+
delete process.env.PRNEXT_WORKER_PREWARM;
|
|
76
|
+
async function rendererFor(selected,name){
|
|
77
|
+
if(selected.modules.has(name))return selected.modules.get(name);
|
|
78
|
+
const pending=workScope.runInAsyncScope(()=>runtimeModule(name,selected.distDir));
|
|
79
|
+
selected.modules.set(name,pending);
|
|
80
|
+
try{const module=await pending;selected.modules.set(name,module);return module;}
|
|
81
|
+
catch(error){if(selected.modules.get(name)===pending)selected.modules.delete(name);throw error;}
|
|
82
|
+
}
|
|
83
|
+
async function prepareFramework(signal) {
|
|
84
|
+
const configured = manifest.config?.workerPreload;
|
|
85
|
+
const options = { maxRssMb: configured?.maxRssMb ?? 256, timeoutMs: Math.min(15000, configured?.timeoutMs ?? 15000) };
|
|
86
|
+
const result = await prewarmFramework({ routes: manifest.routes, options, signal,
|
|
87
|
+
load: name => rendererFor(deployment, name) });
|
|
88
|
+
if (diagnostics.enabled) diagnostics.record('wake', { phase: 'framework-prewarm', ...result });
|
|
89
|
+
if (!result.ready) console.warn(`[prnext] Framework prewarm incomplete (${result.reason}); normal requests remain available.`);
|
|
90
|
+
return result.ready;
|
|
91
|
+
}
|
|
92
|
+
let frameworkPrewarmed = false;
|
|
93
|
+
if (production && prewarmRequested) {
|
|
94
|
+
try { frameworkPrewarmed = await prepareFramework(); }
|
|
95
|
+
catch { console.warn('[prnext] Framework prewarm failed; normal requests remain available.'); }
|
|
96
|
+
}
|
|
73
97
|
const compileDeferred=!production&&compilerDescriptor?deferredCompiler(compilerDescriptor,{root:projectRoot,home:path.dirname(distDir)}):undefined;
|
|
74
98
|
async function deferredDeployment(route,signal,sources=[]){
|
|
75
99
|
if(!compileDeferred)throw Error('Deferred development compiler is unavailable');
|
|
@@ -91,15 +115,8 @@ async function drainRuntime(){
|
|
|
91
115
|
|
|
92
116
|
async function handleRequest(input, output, signalController, responseComplete, selected=deployment) {
|
|
93
117
|
const {manifest,distDir,routes,prerendered}=selected;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Imports share one pending job, then retain only the namespace. Starting
|
|
97
|
-
// outside the visitor's ALS scope prevents the module cache retaining it.
|
|
98
|
-
const pending=workScope.runInAsyncScope(()=>runtimeModule(name,distDir));
|
|
99
|
-
selected.modules.set(name,pending);
|
|
100
|
-
try{const module=await pending;selected.modules.set(name,module);return module;}
|
|
101
|
-
catch(error){if(selected.modules.get(name)===pending)selected.modules.delete(name);throw error;}
|
|
102
|
-
};
|
|
118
|
+
// Bootstrap and requests share pending imports outside the visitor's ALS.
|
|
119
|
+
const renderer=name=>rendererFor(selected,name);
|
|
103
120
|
let responseStarted = false;
|
|
104
121
|
const finalizers = new Set();
|
|
105
122
|
try {
|
|
@@ -108,10 +125,8 @@ async function handleRequest(input, output, signalController, responseComplete,
|
|
|
108
125
|
const options=manifest.config?.workerPreload||(frameworkOnly?{routes:[],maxRssMb:256,timeoutMs:15000}:undefined);
|
|
109
126
|
if(!production||!options){await output(input.id,{status:204,body:''},input);return;}
|
|
110
127
|
if(frameworkOnly){
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if(manifest.routes.some(route=>route.kind==='api'))await renderer('api');
|
|
114
|
-
scheduleCompileCacheFlush();await output(input.id,{status:204,body:''},input);return;
|
|
128
|
+
frameworkPrewarmed = await prepareFramework(signalController.signal);
|
|
129
|
+
scheduleCompileCacheFlush();await output(input.id,{status:frameworkPrewarmed?204:503,body:''},input);return;
|
|
115
130
|
}
|
|
116
131
|
const selected=options.routes.map(pattern=>manifest.routes.find(route=>route.pattern===pattern&&!route.internal)).filter(Boolean);
|
|
117
132
|
const deadline=performance.now()+options.timeoutMs;
|
|
@@ -142,6 +157,9 @@ async function handleRequest(input, output, signalController, responseComplete,
|
|
|
142
157
|
? { ...manifest.middleware, id: '__prnext_middleware', kind: 'middleware' }
|
|
143
158
|
: routes.get(input.routeId);
|
|
144
159
|
if (!route) { await output(input.id, { status: 404, body: 'Not Found' }, input); return; }
|
|
160
|
+
if (input.bodyStream && (route.router !== 'app' || route.kind !== 'api' || input.renderMode || !manifest.config?.streamingRequestBodies)) {
|
|
161
|
+
throw new Error('Streaming request bodies require an enabled App Route Handler');
|
|
162
|
+
}
|
|
145
163
|
const sourceModules=!production&&route.router==='app'&&route.kind==='page'?advancedRoutingSources({...input,basePath:manifest.config?.basePath||''},manifest.routes):[];
|
|
146
164
|
const deferredSources=sourceModules.length?manifest.routes.filter(candidate=>candidate.devDeferred&&sourceModules.includes(candidate.module)).map(candidate=>candidate.pattern):[];
|
|
147
165
|
if(route.devDeferred||deferredSources.length){
|
|
@@ -197,7 +215,9 @@ async function handleRequest(input, output, signalController, responseComplete,
|
|
|
197
215
|
// The application's scope was aborted above, but the lane is still valid
|
|
198
216
|
// for its one error/redirect response. Do not mistake that cleanup signal
|
|
199
217
|
// for a client disconnect while writing the replacement headers.
|
|
200
|
-
|
|
218
|
+
const ingressFailure = /^PRNEXT_BODY_(LIMIT|TIMEOUT|READ|LENGTH)$/.test(error?.code || '')
|
|
219
|
+
? { status: error.statusCode, headers: { 'content-type': 'text/plain; charset=utf-8' }, body: 'Request body transport failed' } : undefined;
|
|
220
|
+
await output(input?.id ?? null, ingressFailure || navigationResponse(error) || errorResponse(error, production), { ...input, production });
|
|
201
221
|
responseComplete?.();
|
|
202
222
|
} finally {
|
|
203
223
|
if (finalizers.size) {
|
|
@@ -261,7 +281,7 @@ if (token && laneLimit > 1) {
|
|
|
261
281
|
const candidate = Buffer.from(line);
|
|
262
282
|
if (candidate.length !== expected.length || !timingSafeEqual(candidate, expected)) throw new Error('Worker authentication failed');
|
|
263
283
|
clearTimeout(authTimer); socket.write('ready\n');
|
|
264
|
-
} })) {
|
|
284
|
+
}, streamBodies: true, credit: frame => new Promise((resolve, reject) => socket.write(JSON.stringify(frame) + '\n', error => error ? reject(error) : resolve())) })) {
|
|
265
285
|
if (invocations.size >= laneLimit) throw new Error('Worker invocation capacity exceeded');
|
|
266
286
|
controller = new AbortController();
|
|
267
287
|
const active = controller;
|
|
@@ -339,10 +359,10 @@ if (token && laneLimit > 1) {
|
|
|
339
359
|
});
|
|
340
360
|
server.listen(0, '127.0.0.1', () => {
|
|
341
361
|
if(!production&&typeof globalThis.__PRNEXT_DEV_WORKER_READY__==='function'){
|
|
342
|
-
globalThis.__PRNEXT_DEV_WORKER_READY__({port:server.address().port,binary:true,gracefulShutdown:true});
|
|
362
|
+
globalThis.__PRNEXT_DEV_WORKER_READY__({port:server.address().port,binary:true,gracefulShutdown:true,requestBodyStream:1});
|
|
343
363
|
delete globalThis.__PRNEXT_DEV_WORKER_READY__;
|
|
344
364
|
}
|
|
345
|
-
writeProtocol(JSON.stringify({ port: server.address().port, binary: true, heartbeat: true, activity: reportActivity, gracefulShutdown: true }) + '\n');
|
|
365
|
+
writeProtocol(JSON.stringify({ port: server.address().port, binary: true, heartbeat: true, activity: reportActivity, gracefulShutdown: true, requestBodyStream: 1, frameworkPrewarmed }) + '\n');
|
|
346
366
|
heartbeat = setInterval(() => {
|
|
347
367
|
// Coalesce the idle transition with the existing heartbeat. Normal
|
|
348
368
|
// request completion adds no stdout messages, and short cache bursts
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Fontkit 2.0.4 Node create() bundle, generated by scripts/prepare-fontkit.mjs.
|
|
2
|
+
Upstream: https://github.com/foliojs/fontkit
|
|
3
|
+
The original code is bundled/minified without patches. Source copyright notices, dependency licenses, README license declarations and exact versions/integrities are retained alongside it.
|
|
4
|
+
fontkit, dfa and brotli declare MIT in their package metadata/README and publish no separate license file. brotli also contains Google Apache-2.0 source notices, retained in SOURCE-NOTICES.txt; the Apache-2.0 text is included with @swc/helpers.
|
|
5
|
+
Use upstream.json to audit/update this bundled dependency when security fixes are released.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/* Copyright 2013 Google Inc. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
|
|
15
|
+
Bit reading helpers
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/* Copyright 2013 Google Inc. All Rights Reserved.
|
|
19
|
+
|
|
20
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
21
|
+
you may not use this file except in compliance with the License.
|
|
22
|
+
You may obtain a copy of the License at
|
|
23
|
+
|
|
24
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
25
|
+
|
|
26
|
+
Unless required by applicable law or agreed to in writing, software
|
|
27
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
28
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
29
|
+
See the License for the specific language governing permissions and
|
|
30
|
+
limitations under the License.
|
|
31
|
+
|
|
32
|
+
Collection of static dictionary words.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/* Copyright 2013 Google Inc. All Rights Reserved.
|
|
36
|
+
|
|
37
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
38
|
+
you may not use this file except in compliance with the License.
|
|
39
|
+
You may obtain a copy of the License at
|
|
40
|
+
|
|
41
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
42
|
+
|
|
43
|
+
Unless required by applicable law or agreed to in writing, software
|
|
44
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
45
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
46
|
+
See the License for the specific language governing permissions and
|
|
47
|
+
limitations under the License.
|
|
48
|
+
|
|
49
|
+
Lookup table to map the previous two bytes to a context id.
|
|
50
|
+
|
|
51
|
+
There are four different context modeling modes defined here:
|
|
52
|
+
CONTEXT_LSB6: context id is the least significant 6 bits of the last byte,
|
|
53
|
+
CONTEXT_MSB6: context id is the most significant 6 bits of the last byte,
|
|
54
|
+
CONTEXT_UTF8: second-order context model tuned for UTF8-encoded text,
|
|
55
|
+
CONTEXT_SIGNED: second-order context model tuned for signed integers.
|
|
56
|
+
|
|
57
|
+
The context id for the UTF8 context model is calculated as follows. If p1
|
|
58
|
+
and p2 are the previous two bytes, we calcualte the context as
|
|
59
|
+
|
|
60
|
+
context = kContextLookup[p1] | kContextLookup[p2 + 256].
|
|
61
|
+
|
|
62
|
+
If the previous two bytes are ASCII characters (i.e. < 128), this will be
|
|
63
|
+
equivalent to
|
|
64
|
+
|
|
65
|
+
context = 4 * context1(p1) + context2(p2),
|
|
66
|
+
|
|
67
|
+
where context1 is based on the previous byte in the following way:
|
|
68
|
+
|
|
69
|
+
0 : non-ASCII control
|
|
70
|
+
1 : \t, \n, \r
|
|
71
|
+
2 : space
|
|
72
|
+
3 : other punctuation
|
|
73
|
+
4 : " '
|
|
74
|
+
5 : %
|
|
75
|
+
6 : ( < [ {
|
|
76
|
+
7 : ) > ] }
|
|
77
|
+
8 : , ; :
|
|
78
|
+
9 : .
|
|
79
|
+
10 : =
|
|
80
|
+
11 : number
|
|
81
|
+
12 : upper-case vowel
|
|
82
|
+
13 : upper-case consonant
|
|
83
|
+
14 : lower-case vowel
|
|
84
|
+
15 : lower-case consonant
|
|
85
|
+
|
|
86
|
+
and context2 is based on the second last byte:
|
|
87
|
+
|
|
88
|
+
0 : control, space
|
|
89
|
+
1 : punctuation
|
|
90
|
+
2 : upper-case letter, number
|
|
91
|
+
3 : lower-case letter
|
|
92
|
+
|
|
93
|
+
If the last byte is ASCII, and the second last byte is not (in a valid UTF8
|
|
94
|
+
stream it will be a continuation byte, value between 128 and 191), the
|
|
95
|
+
context is the same as if the second last byte was an ASCII control or space.
|
|
96
|
+
|
|
97
|
+
If the last byte is a UTF8 lead byte (value >= 192), then the next byte will
|
|
98
|
+
be a continuation byte and the context id is 2 or 3 depending on the LSB of
|
|
99
|
+
the last byte and to a lesser extent on the second last byte if it is ASCII.
|
|
100
|
+
|
|
101
|
+
If the last byte is a UTF8 continuation byte, the second last byte can be:
|
|
102
|
+
- continuation byte: the next byte is probably ASCII or lead byte (assuming
|
|
103
|
+
4-byte UTF8 characters are rare) and the context id is 0 or 1.
|
|
104
|
+
- lead byte (192 - 207): next byte is ASCII or lead byte, context is 0 or 1
|
|
105
|
+
- lead byte (208 - 255): next byte is continuation byte, context is 2 or 3
|
|
106
|
+
|
|
107
|
+
The possible value combinations of the previous two bytes, the range of
|
|
108
|
+
context ids and the type of the next byte is summarized in the table below:
|
|
109
|
+
|
|
110
|
+
|--------\-----------------------------------------------------------------|
|
|
111
|
+
| \ Last byte |
|
|
112
|
+
| Second \---------------------------------------------------------------|
|
|
113
|
+
| last byte \ ASCII | cont. byte | lead byte |
|
|
114
|
+
| \ (0-127) | (128-191) | (192-) |
|
|
115
|
+
|=============|===================|=====================|==================|
|
|
116
|
+
| ASCII | next: ASCII/lead | not valid | next: cont. |
|
|
117
|
+
| (0-127) | context: 4 - 63 | | context: 2 - 3 |
|
|
118
|
+
|-------------|-------------------|---------------------|------------------|
|
|
119
|
+
| cont. byte | next: ASCII/lead | next: ASCII/lead | next: cont. |
|
|
120
|
+
| (128-191) | context: 4 - 63 | context: 0 - 1 | context: 2 - 3 |
|
|
121
|
+
|-------------|-------------------|---------------------|------------------|
|
|
122
|
+
| lead byte | not valid | next: ASCII/lead | not valid |
|
|
123
|
+
| (192-207) | | context: 0 - 1 | |
|
|
124
|
+
|-------------|-------------------|---------------------|------------------|
|
|
125
|
+
| lead byte | not valid | next: cont. | not valid |
|
|
126
|
+
| (208-) | | context: 2 - 3 | |
|
|
127
|
+
|-------------|-------------------|---------------------|------------------|
|
|
128
|
+
|
|
129
|
+
The context id for the signed context mode is calculated as:
|
|
130
|
+
|
|
131
|
+
context = (kContextLookup[512 + p1] << 3) | kContextLookup[512 + p2].
|
|
132
|
+
|
|
133
|
+
For any context modeling modes, the context ids can be calculated by |-ing
|
|
134
|
+
together two lookups from one table using context model dependent offsets:
|
|
135
|
+
|
|
136
|
+
context = kContextLookup[offset1 + p1] | kContextLookup[offset2 + p2].
|
|
137
|
+
|
|
138
|
+
where offset1 and offset2 are dependent on the context mode.
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
/* Copyright 2013 Google Inc. All Rights Reserved.
|
|
142
|
+
|
|
143
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
144
|
+
you may not use this file except in compliance with the License.
|
|
145
|
+
You may obtain a copy of the License at
|
|
146
|
+
|
|
147
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
148
|
+
|
|
149
|
+
Unless required by applicable law or agreed to in writing, software
|
|
150
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
151
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
152
|
+
See the License for the specific language governing permissions and
|
|
153
|
+
limitations under the License.
|
|
154
|
+
|
|
155
|
+
Lookup tables to map prefix codes to value ranges. This is used during
|
|
156
|
+
decoding of the block lengths, literal insertion lengths and copy lengths.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/* Copyright 2013 Google Inc. All Rights Reserved.
|
|
160
|
+
|
|
161
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
162
|
+
you may not use this file except in compliance with the License.
|
|
163
|
+
You may obtain a copy of the License at
|
|
164
|
+
|
|
165
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
166
|
+
|
|
167
|
+
Unless required by applicable law or agreed to in writing, software
|
|
168
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
169
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
170
|
+
See the License for the specific language governing permissions and
|
|
171
|
+
limitations under the License.
|
|
172
|
+
|
|
173
|
+
Transformations on dictionary words.
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
/* Copyright 2013 Google Inc. All Rights Reserved.
|
|
177
|
+
|
|
178
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
179
|
+
you may not use this file except in compliance with the License.
|
|
180
|
+
You may obtain a copy of the License at
|
|
181
|
+
|
|
182
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
183
|
+
|
|
184
|
+
Unless required by applicable law or agreed to in writing, software
|
|
185
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
186
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
187
|
+
See the License for the specific language governing permissions and
|
|
188
|
+
limitations under the License.
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The font's copyright information
|
|
193
|
+
* @type {string}
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
/******************************************************************************
|
|
197
|
+
Copyright (c) Microsoft Corporation.
|
|
198
|
+
|
|
199
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
200
|
+
purpose with or without fee is hereby granted.
|
|
201
|
+
|
|
202
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
203
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
204
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
205
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
206
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
207
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
208
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
209
|
+
***************************************************************************** */
|