UNPKG

next

Version:

The React Framework

432 lines (431 loc) • 20.3 kB
"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