432 lines
20 KiB
JavaScript
432 lines
20 KiB
JavaScript
"use strict";
|
|
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
|