198 lines
10 KiB
JavaScript
198 lines
10 KiB
JavaScript
import { getDraftModeProviderForCacheScope, throwForMissingRequestStore } from '../app-render/work-unit-async-storage.external';
|
|
import { workAsyncStorage } from '../app-render/work-async-storage.external';
|
|
import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external';
|
|
import { abortAndThrowOnSynchronousRequestDataAccess, delayUntilRuntimeStage, postponeWithTracking, trackDynamicDataInDynamicRender } from '../app-render/dynamic-rendering';
|
|
import { createDedupedByCallsiteServerErrorLoggerDev } from '../create-deduped-by-callsite-server-error-logger';
|
|
import { StaticGenBailoutError } from '../../client/components/static-generation-bailout';
|
|
import { DynamicServerError } from '../../client/components/hooks-server-context';
|
|
import { InvariantError } from '../../shared/lib/invariant-error';
|
|
import { ReflectAdapter } from '../web/spec-extension/adapters/reflect';
|
|
export function draftMode() {
|
|
const callingExpression = 'draftMode';
|
|
const workStore = workAsyncStorage.getStore();
|
|
const workUnitStore = workUnitAsyncStorage.getStore();
|
|
if (!workStore || !workUnitStore) {
|
|
throwForMissingRequestStore(callingExpression);
|
|
}
|
|
switch(workUnitStore.type){
|
|
case 'prerender-runtime':
|
|
// TODO(runtime-ppr): does it make sense to delay this? normally it's always microtasky
|
|
return delayUntilRuntimeStage(workUnitStore, createOrGetCachedDraftMode(workUnitStore.draftMode, workStore));
|
|
case 'request':
|
|
return createOrGetCachedDraftMode(workUnitStore.draftMode, workStore);
|
|
case 'cache':
|
|
case 'private-cache':
|
|
case 'unstable-cache':
|
|
// Inside of `"use cache"` or `unstable_cache`, draft mode is available if
|
|
// the outmost work unit store is a request store (or a runtime prerender),
|
|
// and if draft mode is enabled.
|
|
const draftModeProvider = getDraftModeProviderForCacheScope(workStore, workUnitStore);
|
|
if (draftModeProvider) {
|
|
return createOrGetCachedDraftMode(draftModeProvider, workStore);
|
|
}
|
|
// Otherwise, we fall through to providing an empty draft mode.
|
|
// eslint-disable-next-line no-fallthrough
|
|
case 'prerender':
|
|
case 'prerender-client':
|
|
case 'prerender-ppr':
|
|
case 'prerender-legacy':
|
|
// Return empty draft mode
|
|
return createOrGetCachedDraftMode(null, workStore);
|
|
default:
|
|
return workUnitStore;
|
|
}
|
|
}
|
|
function createOrGetCachedDraftMode(draftModeProvider, workStore) {
|
|
const cacheKey = draftModeProvider ?? NullDraftMode;
|
|
const cachedDraftMode = CachedDraftModes.get(cacheKey);
|
|
if (cachedDraftMode) {
|
|
return cachedDraftMode;
|
|
}
|
|
if (process.env.NODE_ENV === 'development' && !(workStore == null ? void 0 : workStore.isPrefetchRequest)) {
|
|
const route = workStore == null ? void 0 : workStore.route;
|
|
return createDraftModeWithDevWarnings(draftModeProvider, route);
|
|
} else {
|
|
return Promise.resolve(new DraftMode(draftModeProvider));
|
|
}
|
|
}
|
|
const NullDraftMode = {};
|
|
const CachedDraftModes = new WeakMap();
|
|
function createDraftModeWithDevWarnings(underlyingProvider, route) {
|
|
const instance = new DraftMode(underlyingProvider);
|
|
const promise = Promise.resolve(instance);
|
|
const proxiedPromise = new Proxy(promise, {
|
|
get (target, prop, receiver) {
|
|
switch(prop){
|
|
case 'isEnabled':
|
|
warnForSyncAccess(route, `\`draftMode().${prop}\``);
|
|
break;
|
|
case 'enable':
|
|
case 'disable':
|
|
{
|
|
warnForSyncAccess(route, `\`draftMode().${prop}()\``);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
// We only warn for well-defined properties of the draftMode object.
|
|
}
|
|
}
|
|
return ReflectAdapter.get(target, prop, receiver);
|
|
}
|
|
});
|
|
return proxiedPromise;
|
|
}
|
|
class DraftMode {
|
|
constructor(provider){
|
|
this._provider = provider;
|
|
}
|
|
get isEnabled() {
|
|
if (this._provider !== null) {
|
|
return this._provider.isEnabled;
|
|
}
|
|
return false;
|
|
}
|
|
enable() {
|
|
// We have a store we want to track dynamic data access to ensure we
|
|
// don't statically generate routes that manipulate draft mode.
|
|
trackDynamicDraftMode('draftMode().enable()', this.enable);
|
|
if (this._provider !== null) {
|
|
this._provider.enable();
|
|
}
|
|
}
|
|
disable() {
|
|
trackDynamicDraftMode('draftMode().disable()', this.disable);
|
|
if (this._provider !== null) {
|
|
this._provider.disable();
|
|
}
|
|
}
|
|
}
|
|
const warnForSyncAccess = createDedupedByCallsiteServerErrorLoggerDev(createDraftModeAccessError);
|
|
function createDraftModeAccessError(route, expression) {
|
|
const prefix = route ? `Route "${route}" ` : 'This route ';
|
|
return Object.defineProperty(new Error(`${prefix}used ${expression}. ` + `\`draftMode()\` returns a Promise and must be unwrapped with \`await\` or \`React.use()\` before accessing its properties. ` + `Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`), "__NEXT_ERROR_CODE", {
|
|
value: "E835",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
function trackDynamicDraftMode(expression, constructorOpt) {
|
|
const workStore = workAsyncStorage.getStore();
|
|
const workUnitStore = workUnitAsyncStorage.getStore();
|
|
if (workStore) {
|
|
// We have a store we want to track dynamic data access to ensure we
|
|
// don't statically generate routes that manipulate draft mode.
|
|
if ((workUnitStore == null ? void 0 : workUnitStore.phase) === 'after') {
|
|
throw Object.defineProperty(new Error(`Route ${workStore.route} used "${expression}" inside \`after()\`. The enabled status of \`draftMode()\` can be read inside \`after()\` but you cannot enable or disable \`draftMode()\`. See more info here: https://nextjs.org/docs/app/api-reference/functions/after`), "__NEXT_ERROR_CODE", {
|
|
value: "E845",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
if (workStore.dynamicShouldError) {
|
|
throw Object.defineProperty(new StaticGenBailoutError(`Route ${workStore.route} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`), "__NEXT_ERROR_CODE", {
|
|
value: "E553",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
if (workUnitStore) {
|
|
switch(workUnitStore.type){
|
|
case 'cache':
|
|
case 'private-cache':
|
|
{
|
|
const error = Object.defineProperty(new Error(`Route ${workStore.route} used "${expression}" inside "use cache". The enabled status of \`draftMode()\` can be read in caches but you must not enable or disable \`draftMode()\` inside a cache. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache`), "__NEXT_ERROR_CODE", {
|
|
value: "E829",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
Error.captureStackTrace(error, constructorOpt);
|
|
workStore.invalidDynamicUsageError ??= error;
|
|
throw error;
|
|
}
|
|
case 'unstable-cache':
|
|
throw Object.defineProperty(new Error(`Route ${workStore.route} used "${expression}" inside a function cached with \`unstable_cache()\`. The enabled status of \`draftMode()\` can be read in caches but you must not enable or disable \`draftMode()\` inside a cache. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache`), "__NEXT_ERROR_CODE", {
|
|
value: "E844",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
case 'prerender':
|
|
case 'prerender-runtime':
|
|
{
|
|
const error = Object.defineProperty(new Error(`Route ${workStore.route} used ${expression} without first calling \`await connection()\`. See more info here: https://nextjs.org/docs/messages/next-prerender-sync-headers`), "__NEXT_ERROR_CODE", {
|
|
value: "E126",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
return abortAndThrowOnSynchronousRequestDataAccess(workStore.route, expression, error, workUnitStore);
|
|
}
|
|
case 'prerender-client':
|
|
const exportName = '`draftMode`';
|
|
throw Object.defineProperty(new InvariantError(`${exportName} must not be used within a Client Component. Next.js should be preventing ${exportName} from being included in Client Components statically, but did not in this case.`), "__NEXT_ERROR_CODE", {
|
|
value: "E832",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
case 'prerender-ppr':
|
|
return postponeWithTracking(workStore.route, expression, workUnitStore.dynamicTracking);
|
|
case 'prerender-legacy':
|
|
workUnitStore.revalidate = 0;
|
|
const err = Object.defineProperty(new DynamicServerError(`Route ${workStore.route} couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/messages/dynamic-server-error`), "__NEXT_ERROR_CODE", {
|
|
value: "E558",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
workStore.dynamicUsageDescription = expression;
|
|
workStore.dynamicUsageStack = err.stack;
|
|
throw err;
|
|
case 'request':
|
|
trackDynamicDataInDynamicRender(workUnitStore);
|
|
break;
|
|
default:
|
|
workUnitStore;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//# sourceMappingURL=draft-mode.js.map
|