next
Version:
The React Framework
432 lines (431 loc) • 20.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
assertRootParamInSamples: null,
createCookiesFromSample: null,
createDraftModeForValidation: null,
createExhaustiveParamsProxy: null,
createExhaustiveSearchParamsProxy: null,
createExhaustiveURLSearchParamsProxy: null,
createHeadersFromSample: null,
createRelativeURLFromSamples: null,
createValidationSampleTracking: null,
trackMissingSampleError: null,
trackMissingSampleErrorAndThrow: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
assertRootParamInSamples: function() {
return assertRootParamInSamples;
},
createCookiesFromSample: function() {
return createCookiesFromSample;
},
createDraftModeForValidation: function() {
return createDraftModeForValidation;
},
createExhaustiveParamsProxy: function() {
return createExhaustiveParamsProxy;
},
createExhaustiveSearchParamsProxy: function() {
return createExhaustiveSearchParamsProxy;
},
createExhaustiveURLSearchParamsProxy: function() {
return createExhaustiveURLSearchParamsProxy;
},
createHeadersFromSample: function() {
return createHeadersFromSample;
},
createRelativeURLFromSamples: function() {
return createRelativeURLFromSamples;
},
createValidationSampleTracking: function() {
return createValidationSampleTracking;
},
trackMissingSampleError: function() {
return trackMissingSampleError;
},
trackMissingSampleErrorAndThrow: function() {
return trackMissingSampleErrorAndThrow;
}
});
const _cookies = require("../../web/spec-extension/cookies");
const _requestcookies = require("../../web/spec-extension/adapters/request-cookies");
const _headers = require("../../web/spec-extension/adapters/headers");
const _getsegmentparam = require("../../../shared/lib/router/utils/get-segment-param");
const _parserelativeurl = require("../../../shared/lib/router/utils/parse-relative-url");
const _invarianterror = require("../../../shared/lib/invariant-error");
const _instantvalidationerror = require("./instant-validation-error");
const _workunitasyncstorageexternal = require("../work-unit-async-storage.external");
const _reflectutils = require("../../../shared/lib/utils/reflect-utils");
function createValidationSampleTracking() {
return {
missingSampleErrors: []
};
}
function getExpectedSampleTracking() {
let validationSampleTracking = null;
const workUnitStore = _workunitasyncstorageexternal.workUnitAsyncStorage.getStore();
if (workUnitStore) {
switch(workUnitStore.type){
case 'request':
case 'validation-client':
// TODO(instant-validation-build): do we need any special handling for caches?
validationSampleTracking = workUnitStore.validationSampleTracking ?? null;
break;
case 'cache':
case 'private-cache':
case 'unstable-cache':
case 'prerender-legacy':
case 'prerender-ppr':
case 'prerender-client':
case 'prerender':
case 'prerender-runtime':
case 'generate-static-params':
break;
default:
workUnitStore;
}
}
if (!validationSampleTracking) {
throw Object.defineProperty(new _invarianterror.InvariantError('Expected to have a workUnitStore that provides validationSampleTracking'), "__NEXT_ERROR_CODE", {
value: "E1110",
enumerable: false,
configurable: true
});
}
return validationSampleTracking;
}
function trackMissingSampleError(error) {
const validationSampleTracking = getExpectedSampleTracking();
validationSampleTracking.missingSampleErrors.push(error);
}
function trackMissingSampleErrorAndThrow(error) {
// TODO(instant-validation-build): this should abort the render
trackMissingSampleError(error);
throw error;
}
function createCookiesFromSample(sampleCookies, route) {
const declaredNames = new Set();
const cookies = new _cookies.RequestCookies(new Headers());
if (sampleCookies) {
for (const cookie of sampleCookies){
declaredNames.add(cookie.name);
if (cookie.value !== null) {
cookies.set(cookie.name, cookie.value);
}
}
}
const sealed = _requestcookies.RequestCookiesAdapter.seal(cookies);
return new Proxy(sealed, {
get (target, prop, receiver) {
if (prop === 'has') {
const originalMethod = Reflect.get(target, prop, receiver);
const wrappedMethod = function(name) {
if (!declaredNames.has(name)) {
trackMissingSampleErrorAndThrow(createMissingCookieSampleError(route, name));
}
return originalMethod.call(target, name);
};
return wrappedMethod;
}
if (prop === 'get') {
const originalMethod = Reflect.get(target, prop, receiver);
const wrappedMethod = function(nameOrCookie) {
let name;
if (typeof nameOrCookie === 'string') {
name = nameOrCookie;
} else if (nameOrCookie && typeof nameOrCookie === 'object' && typeof nameOrCookie.name === 'string') {
name = nameOrCookie.name;
} else {
// This is an invalid input. Pass it through to the original method so it can error.
return originalMethod.call(target, nameOrCookie);
}
if (!declaredNames.has(name)) {
trackMissingSampleErrorAndThrow(createMissingCookieSampleError(route, name));
}
return originalMethod.call(target, name);
};
return wrappedMethod;
}
// TODO(instant-validation-build): what should getAll do?
// Maybe we should only allow it if there's an array (possibly empty?)
return Reflect.get(target, prop, receiver);
}
});
}
function createMissingCookieSampleError(route, name) {
return Object.defineProperty(new _instantvalidationerror.InstantValidationError(`Route "${route}" accessed cookie "${name}" which is not defined in the \`samples\` ` + `of \`unstable_instant\`. Add it to the sample's \`cookies\` array, ` + `or \`{ name: "${name}", value: null }\` if it should be absent.`), "__NEXT_ERROR_CODE", {
value: "E1115",
enumerable: false,
configurable: true
});
}
function createHeadersFromSample(rawSampleHeaders, sampleCookies, route) {
// If we have cookie samples, add a `cookie` header to match.
// Accessing it will be implicitly allowed by the proxy --
// if the user defined some cookies, accessing the "cookie" header is also fine.
const sampleHeaders = rawSampleHeaders ? [
...rawSampleHeaders
] : [];
if (sampleHeaders.find(([name])=>name.toLowerCase() === 'cookie')) {
throw Object.defineProperty(new _instantvalidationerror.InstantValidationError('Invalid sample: Defining cookies via a "cookie" header is not supported. Use `cookies: [{ name: ..., value: ... }]` instead.'), "__NEXT_ERROR_CODE", {
value: "E1111",
enumerable: false,
configurable: true
});
}
if (sampleCookies) {
const cookieHeaderValue = sampleCookies.toString();
sampleHeaders.push([
'cookie',
// if the `cookies` samples were empty, or they were all `null`, then we have no cookies,
// and the header isn't present, but should remains readable, so we set it to null.
cookieHeaderValue !== '' ? cookieHeaderValue : null
]);
}
const declaredNames = new Set();
const headersInit = {};
for (const [name, value] of sampleHeaders){
declaredNames.add(name.toLowerCase());
if (value !== null) {
headersInit[name.toLowerCase()] = value;
}
}
const sealed = _headers.HeadersAdapter.seal(_headers.HeadersAdapter.from(headersInit));
return new Proxy(sealed, {
get (target, prop, receiver) {
if (prop === 'get' || prop === 'has') {
const originalMethod = Reflect.get(target, prop, receiver);
const patchedMethod = function(rawName) {
const name = rawName.toLowerCase();
if (!declaredNames.has(name)) {
trackMissingSampleErrorAndThrow(Object.defineProperty(new _instantvalidationerror.InstantValidationError(`Route "${route}" accessed header "${name}" which is not defined in the \`samples\` ` + `of \`unstable_instant\`. Add it to the sample's \`headers\` array, ` + `or \`["${name}", null]\` if it should be absent.`), "__NEXT_ERROR_CODE", {
value: "E1116",
enumerable: false,
configurable: true
}));
}
// typescript can't reconcile a union of functions with a union of return types,
// so we have to cast the original return type away
return originalMethod.call(target, name);
};
return patchedMethod;
}
return Reflect.get(target, prop, receiver);
}
});
}
function createDraftModeForValidation() {
// Create a minimal DraftModeProvider-compatible object
// that always reports draft mode as disabled.
//
// private properties that can't be set from outside the class.
return {
get isEnabled () {
return false;
},
enable () {
throw Object.defineProperty(new Error('Draft mode cannot be enabled during build-time instant validation.'), "__NEXT_ERROR_CODE", {
value: "E1092",
enumerable: false,
configurable: true
});
},
disable () {
throw Object.defineProperty(new Error('Draft mode cannot be disabled during build-time instant validation.'), "__NEXT_ERROR_CODE", {
value: "E1094",
enumerable: false,
configurable: true
});
}
};
}
function createExhaustiveParamsProxy(underlyingParams, declaredParamNames, route) {
return new Proxy(underlyingParams, {
get (target, prop, receiver) {
if (typeof prop === 'string' && !_reflectutils.wellKnownProperties.has(prop) && // Only error when accessing a param that is part of the route but wasn't provided.
// accessing properties that aren't expected to be a valid param value is fine.
prop in underlyingParams && !declaredParamNames.has(prop)) {
trackMissingSampleErrorAndThrow(Object.defineProperty(new _instantvalidationerror.InstantValidationError(`Route "${route}" accessed param "${prop}" which is not defined in the \`samples\` ` + `of \`unstable_instant\`. Add it to the sample's \`params\` object.`), "__NEXT_ERROR_CODE", {
value: "E1095",
enumerable: false,
configurable: true
}));
}
return Reflect.get(target, prop, receiver);
}
});
}
function createExhaustiveSearchParamsProxy(searchParams, declaredSearchParamNames, route) {
return new Proxy(searchParams, {
get (target, prop, receiver) {
if (typeof prop === 'string' && !_reflectutils.wellKnownProperties.has(prop) && !declaredSearchParamNames.has(prop)) {
trackMissingSampleErrorAndThrow(createMissingSearchParamSampleError(route, prop));
}
return Reflect.get(target, prop, receiver);
},
has (target, prop) {
if (typeof prop === 'string' && !_reflectutils.wellKnownProperties.has(prop) && !declaredSearchParamNames.has(prop)) {
trackMissingSampleErrorAndThrow(createMissingSearchParamSampleError(route, prop));
}
return Reflect.has(target, prop);
}
});
}
function createExhaustiveURLSearchParamsProxy(searchParams, declaredSearchParamNames, route) {
return new Proxy(searchParams, {
get (target, prop, receiver) {
// Intercept method calls that access specific param names
if (prop === 'get' || prop === 'getAll' || prop === 'has') {
const originalMathod = Reflect.get(target, prop, receiver);
return (name)=>{
if (typeof name === 'string' && !declaredSearchParamNames.has(name)) {
trackMissingSampleErrorAndThrow(createMissingSearchParamSampleError(route, name));
}
return originalMathod.call(target, name);
};
}
const value = Reflect.get(target, prop, receiver);
// Prevent `TypeError: Value of "this" must be of type URLSearchParams` for methods
if (typeof value === 'function' && !Object.hasOwn(target, prop)) {
return value.bind(target);
}
return value;
}
});
}
function createMissingSearchParamSampleError(route, name) {
return Object.defineProperty(new _instantvalidationerror.InstantValidationError(`Route "${route}" accessed searchParam "${name}" which is not defined in the \`samples\` ` + `of \`unstable_instant\`. Add it to the sample's \`searchParams\` object, ` + `or \`{ "${name}": null }\` if it should be absent.`), "__NEXT_ERROR_CODE", {
value: "E1098",
enumerable: false,
configurable: true
});
}
function createRelativeURLFromSamples(route, sampleParams, sampleSearchParams) {
// Build searchParams query object and URL search string from sample
const pathname = createPathnameFromRouteAndSampleParams(route, sampleParams ?? {});
let search = '';
if (sampleSearchParams) {
const qs = createURLSearchParamsFromSample(sampleSearchParams).toString();
if (qs) {
search = '?' + qs;
}
}
return (0, _parserelativeurl.parseRelativeUrl)(pathname + search, undefined, true);
}
function createURLSearchParamsFromSample(sampleSearchParams) {
const result = new URLSearchParams();
if (sampleSearchParams) {
for (const [key, value] of Object.entries(sampleSearchParams)){
if (value === null || value === undefined) continue;
if (Array.isArray(value)) {
for (const v of value){
result.append(key, v);
}
} else {
result.set(key, value);
}
}
}
return result;
}
/**
* Substitute sample params into `workStore.route` to create a plausible pathname.
* TODO(instant-validation-build): this logic is somewhat hacky and likely incomplete,
* but it should be good enough for some initial testing.
*/ function createPathnameFromRouteAndSampleParams(route, params) {
let interpolatedSegments = [];
const rawSegments = route.split('/');
for (const rawSegment of rawSegments){
const param = (0, _getsegmentparam.getSegmentParam)(rawSegment);
if (param) {
switch(param.paramType){
case 'catchall':
case 'optional-catchall':
{
let paramValue = params[param.paramName];
if (paramValue === undefined) {
// The value for the param was not provided. `usePathname` will detect this and throw
// before this can surface to userspace. Use `[...NAME]` as a placeholder for the param value
// in case it pops up somewhere unexpectedly.
paramValue = [
rawSegment
];
} else if (!Array.isArray(paramValue)) {
// NOTE: this happens outside of render, so we don't need `trackMissingSampleErrorAndThrow`
throw Object.defineProperty(new _instantvalidationerror.InstantValidationError(`Expected sample param value for segment '${rawSegment}' to be an array of strings, got ${typeof paramValue}`), "__NEXT_ERROR_CODE", {
value: "E1104",
enumerable: false,
configurable: true
});
}
interpolatedSegments.push(...paramValue.map((v)=>encodeURIComponent(v)));
break;
}
case 'dynamic':
{
let paramValue = params[param.paramName];
if (paramValue === undefined) {
// The value for the param was not provided. `usePathname` will detect this and throw
// before this can surface to userspace. Use `[NAME]` as a placeholder for the param value
// in case it pops up somewhere unexpectedly.
paramValue = rawSegment;
} else if (typeof paramValue !== 'string') {
// NOTE: this happens outside of render, so we don't need `trackMissingSampleErrorAndThrow`
throw Object.defineProperty(new _instantvalidationerror.InstantValidationError(`Expected sample param value for segment '${rawSegment}' to be a string, got ${typeof paramValue}`), "__NEXT_ERROR_CODE", {
value: "E1108",
enumerable: false,
configurable: true
});
}
interpolatedSegments.push(encodeURIComponent(paramValue));
break;
}
case 'catchall-intercepted-(..)(..)':
case 'catchall-intercepted-(.)':
case 'catchall-intercepted-(..)':
case 'catchall-intercepted-(...)':
case 'dynamic-intercepted-(..)(..)':
case 'dynamic-intercepted-(.)':
case 'dynamic-intercepted-(..)':
case 'dynamic-intercepted-(...)':
{
// TODO(instant-validation-build): i don't know how these are supposed to work, or if we can even get them here
throw Object.defineProperty(new _invarianterror.InvariantError('Not implemented: Validation of interception routes'), "__NEXT_ERROR_CODE", {
value: "E1106",
enumerable: false,
configurable: true
});
}
default:
{
param.paramType;
}
}
} else {
interpolatedSegments.push(rawSegment);
}
}
return interpolatedSegments.join('/');
}
function assertRootParamInSamples(workStore, sampleParams, paramName) {
if (sampleParams && paramName in sampleParams) {
// The param is defined in the samples.
} else {
const route = workStore.route;
trackMissingSampleErrorAndThrow(Object.defineProperty(new _instantvalidationerror.InstantValidationError(`Route "${route}" accessed root param "${paramName}" which is not defined in the \`samples\` ` + `of \`unstable_instant\`. Add it to the sample's \`params\` object.`), "__NEXT_ERROR_CODE", {
value: "E1114",
enumerable: false,
configurable: true
}));
}
}
//# sourceMappingURL=instant-samples.js.map