| import { InvariantError } from '../../shared/lib/invariant-error' |
| import { createAtomicTimerGroup } from './app-render-scheduling' |
| import { |
| DANGEROUSLY_runPendingImmediatesAfterCurrentTask, |
| expectNoPendingImmediates, |
| } from '../node-environment-extensions/fast-set-immediate.external' |
|
|
| |
| |
| |
| |
| export function prerenderAndAbortInSequentialTasks<R>( |
| prerender: () => Promise<R>, |
| abort: () => void |
| ): Promise<R> { |
| if (process.env.NEXT_RUNTIME === 'edge') { |
| throw new InvariantError( |
| '`prerenderAndAbortInSequentialTasks` should not be called in edge runtime.' |
| ) |
| } else { |
| return new Promise((resolve, reject) => { |
| const scheduleTimeout = createAtomicTimerGroup() |
|
|
| let pendingResult: Promise<R> |
| scheduleTimeout(() => { |
| try { |
| DANGEROUSLY_runPendingImmediatesAfterCurrentTask() |
| pendingResult = prerender() |
| pendingResult.catch(() => {}) |
| } catch (err) { |
| reject(err) |
| } |
| }) |
| scheduleTimeout(() => { |
| try { |
| expectNoPendingImmediates() |
| abort() |
| resolve(pendingResult) |
| } catch (err) { |
| reject(err) |
| } |
| }) |
| }) |
| } |
| } |
|
|
| |
| |
| |
| |
| export function prerenderAndAbortInSequentialTasksWithStages<R>( |
| prerender: () => Promise<R>, |
| advanceStage: () => void, |
| abort: () => void |
| ): Promise<R> { |
| if (process.env.NEXT_RUNTIME === 'edge') { |
| throw new InvariantError( |
| '`prerenderAndAbortInSequentialTasksWithStages` should not be called in edge runtime.' |
| ) |
| } else { |
| return new Promise((resolve, reject) => { |
| const scheduleTimeout = createAtomicTimerGroup() |
|
|
| let pendingResult: Promise<R> |
| scheduleTimeout(() => { |
| try { |
| DANGEROUSLY_runPendingImmediatesAfterCurrentTask() |
| pendingResult = prerender() |
| pendingResult.catch(() => {}) |
| } catch (err) { |
| reject(err) |
| } |
| }) |
| scheduleTimeout(() => { |
| try { |
| DANGEROUSLY_runPendingImmediatesAfterCurrentTask() |
| advanceStage() |
| } catch (err) { |
| reject(err) |
| } |
| }) |
| scheduleTimeout(() => { |
| try { |
| expectNoPendingImmediates() |
| abort() |
| resolve(pendingResult) |
| } catch (err) { |
| reject(err) |
| } |
| }) |
| }) |
| } |
| } |
|
|
| |
| |
| |
| |
| export class ReactServerResult { |
| private _stream: null | ReadableStream<Uint8Array> |
|
|
| constructor(stream: ReadableStream<Uint8Array>) { |
| this._stream = stream |
| } |
|
|
| tee() { |
| if (this._stream === null) { |
| throw new Error( |
| 'Cannot tee a ReactServerResult that has already been consumed' |
| ) |
| } |
| const tee = this._stream.tee() |
| this._stream = tee[0] |
| return tee[1] |
| } |
|
|
| consume() { |
| if (this._stream === null) { |
| throw new Error( |
| 'Cannot consume a ReactServerResult that has already been consumed' |
| ) |
| } |
| const stream = this._stream |
| this._stream = null |
| return stream |
| } |
| } |
|
|
| export type ReactServerPrerenderResolveToType = { |
| prelude: ReadableStream<Uint8Array> |
| } |
|
|
| export async function createReactServerPrerenderResult( |
| underlying: Promise<ReactServerPrerenderResolveToType> |
| ): Promise<ReactServerPrerenderResult> { |
| const chunks: Array<Uint8Array> = [] |
| const { prelude } = await underlying |
| const reader = prelude.getReader() |
| while (true) { |
| const { done, value } = await reader.read() |
| if (done) { |
| return new ReactServerPrerenderResult(chunks) |
| } else { |
| chunks.push(value) |
| } |
| } |
| } |
|
|
| export async function createReactServerPrerenderResultFromRender( |
| underlying: ReadableStream<Uint8Array> |
| ): Promise<ReactServerPrerenderResult> { |
| const chunks: Array<Uint8Array> = [] |
| const reader = underlying.getReader() |
| while (true) { |
| const { done, value } = await reader.read() |
| if (done) { |
| break |
| } else { |
| chunks.push(value) |
| } |
| } |
| return new ReactServerPrerenderResult(chunks) |
| } |
| export class ReactServerPrerenderResult { |
| private _chunks: null | Array<Uint8Array> |
|
|
| private assertChunks(expression: string): Array<Uint8Array> { |
| if (this._chunks === null) { |
| throw new InvariantError( |
| `Cannot \`${expression}\` on a ReactServerPrerenderResult that has already been consumed.` |
| ) |
| } |
| return this._chunks |
| } |
|
|
| private consumeChunks(expression: string): Array<Uint8Array> { |
| const chunks = this.assertChunks(expression) |
| this.consume() |
| return chunks |
| } |
|
|
| consume(): void { |
| this._chunks = null |
| } |
|
|
| constructor(chunks: Array<Uint8Array>) { |
| this._chunks = chunks |
| } |
|
|
| asUnclosingStream(): ReadableStream<Uint8Array> { |
| const chunks = this.assertChunks('asUnclosingStream()') |
| return createUnclosingStream(chunks) |
| } |
|
|
| consumeAsUnclosingStream(): ReadableStream<Uint8Array> { |
| const chunks = this.consumeChunks('consumeAsUnclosingStream()') |
| return createUnclosingStream(chunks) |
| } |
|
|
| asStream(): ReadableStream<Uint8Array> { |
| const chunks = this.assertChunks('asStream()') |
| return createClosingStream(chunks) |
| } |
|
|
| consumeAsStream(): ReadableStream<Uint8Array> { |
| const chunks = this.consumeChunks('consumeAsStream()') |
| return createClosingStream(chunks) |
| } |
| } |
|
|
| function createUnclosingStream( |
| chunks: Array<Uint8Array> |
| ): ReadableStream<Uint8Array> { |
| let i = 0 |
| return new ReadableStream({ |
| async pull(controller) { |
| if (i < chunks.length) { |
| controller.enqueue(chunks[i++]) |
| } |
| |
| |
| |
| }, |
| }) |
| } |
|
|
| function createClosingStream( |
| chunks: Array<Uint8Array> |
| ): ReadableStream<Uint8Array> { |
| let i = 0 |
| return new ReadableStream({ |
| async pull(controller) { |
| if (i < chunks.length) { |
| controller.enqueue(chunks[i++]) |
| } else { |
| controller.close() |
| } |
| }, |
| }) |
| } |
|
|
| export async function processPrelude( |
| unprocessedPrelude: ReadableStream<Uint8Array> |
| ) { |
| const [prelude, peek] = unprocessedPrelude.tee() |
|
|
| const reader = peek.getReader() |
| const firstResult = await reader.read() |
| reader.cancel() |
|
|
| const preludeIsEmpty = firstResult.done === true |
|
|
| return { prelude, preludeIsEmpty } |
| } |
|
|