main repo

This commit is contained in:
Basilosaurusrex
2025-11-24 18:09:40 +01:00
parent b636ee5e70
commit f027651f9b
34146 changed files with 4436636 additions and 0 deletions

View File

@@ -0,0 +1,222 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ import { isDynamicRoute } from "../../../shared/lib/router/utils";
import { getSortedRoutes } from "../../../shared/lib/router/utils";
import { LocaleRouteMatcher } from "../route-matchers/locale-route-matcher";
import { ensureLeadingSlash } from "../../../shared/lib/page-path/ensure-leading-slash";
export class DefaultRouteMatcherManager {
/**
* When this value changes, it indicates that a change has been introduced
* that requires recompilation.
*/ get compilationID() {
return this.providers.length;
}
async waitTillReady() {
if (this.waitTillReadyPromise) {
await this.waitTillReadyPromise;
delete this.waitTillReadyPromise;
}
}
async reload() {
let callbacks;
this.waitTillReadyPromise = new Promise((resolve, reject)=>{
callbacks = {
resolve,
reject
};
});
// Grab the compilation ID for this run, we'll verify it at the end to
// ensure that if any routes were added before reloading is finished that
// we error out.
const compilationID = this.compilationID;
try {
// Collect all the matchers from each provider.
const matchers = [];
// Get all the providers matchers.
const providersMatchers = await Promise.all(this.providers.map((provider)=>provider.matchers()));
// Use this to detect duplicate pathnames.
const all = new Map();
const duplicates = {};
for (const providerMatchers of providersMatchers){
for (const matcher of providerMatchers){
// Reset duplicated matches when reloading from pages conflicting state.
if (matcher.duplicated) delete matcher.duplicated;
// Test to see if the matcher being added is a duplicate.
const duplicate = all.get(matcher.definition.pathname);
if (duplicate) {
// This looks a little weird, but essentially if the pathname
// already exists in the duplicates map, then we got that array
// reference. Otherwise, we create a new array with the original
// duplicate first. Then we push the new matcher into the duplicate
// array, and reset it to the duplicates object (which may be a
// no-op if the pathname already existed in the duplicates object).
// Then we set the array of duplicates on both the original
// duplicate object and the new one, so we can keep them in sync.
// If a new duplicate is found, and it matches an existing pathname,
// the retrieval of the `other` will actually return the array
// reference used by all other duplicates. This is why ReadonlyArray
// is so important! Array's are always references!
const others = duplicates[matcher.definition.pathname] ?? [
duplicate
];
others.push(matcher);
duplicates[matcher.definition.pathname] = others;
// Add duplicated details to each route.
duplicate.duplicated = others;
matcher.duplicated = others;
// TODO: see if we should error for duplicates in production?
}
matchers.push(matcher);
// Add the matcher's pathname to the set.
all.set(matcher.definition.pathname, matcher);
}
}
// Update the duplicate matchers. This is used in the development manager
// to warn about duplicates.
this.matchers.duplicates = duplicates;
// If the cache is the same as what we just parsed, we can exit now. We
// can tell by using the `===` which compares object identity, which for
// the manifest matchers, will return the same matcher each time.
if (this.previousMatchers.length === matchers.length && this.previousMatchers.every((cachedMatcher, index)=>cachedMatcher === matchers[index])) {
return;
}
this.previousMatchers = matchers;
// For matchers that are for static routes, filter them now.
this.matchers.static = matchers.filter((matcher)=>!matcher.isDynamic);
// For matchers that are for dynamic routes, filter them and sort them now.
const dynamic = matchers.filter((matcher)=>matcher.isDynamic);
// As `getSortedRoutes` only takes an array of strings, we need to create
// a map of the pathnames (used for sorting) and the matchers. When we
// have locales, there may be multiple matches for the same pathname. To
// handle this, we keep a map of all the indexes (in `reference`) and
// merge them in later.
const reference = new Map();
const pathnames = new Array();
for(let index = 0; index < dynamic.length; index++){
// Grab the pathname from the definition.
const pathname = dynamic[index].definition.pathname;
// Grab the index in the dynamic array, push it into the reference.
const indexes = reference.get(pathname) ?? [];
indexes.push(index);
// If this is the first one set it. If it isn't, we don't need to
// because pushing above on the array will mutate the array already
// stored there because array's are always a reference!
if (indexes.length === 1) reference.set(pathname, indexes);
else continue;
pathnames.push(pathname);
}
// Sort the array of pathnames.
const sorted = getSortedRoutes(pathnames);
// For each of the sorted pathnames, iterate over them, grabbing the list
// of indexes and merging them back into the new `sortedDynamicMatchers`
// array. The order of the same matching pathname doesn't matter because
// they will have other matching characteristics (like the locale) that
// is considered.
const sortedDynamicMatchers = [];
for (const pathname of sorted){
const indexes = reference.get(pathname);
if (!Array.isArray(indexes)) {
throw new Error("Invariant: expected to find identity in indexes map");
}
const dynamicMatches = indexes.map((index)=>dynamic[index]);
sortedDynamicMatchers.push(...dynamicMatches);
}
this.matchers.dynamic = sortedDynamicMatchers;
// This means that there was a new matcher pushed while we were waiting
if (this.compilationID !== compilationID) {
throw new Error("Invariant: expected compilation to finish before new matchers were added, possible missing await");
}
} catch (err) {
callbacks.reject(err);
} finally{
// The compilation ID matched, so mark the complication as finished.
this.lastCompilationID = compilationID;
callbacks.resolve();
}
}
push(provider) {
this.providers.push(provider);
}
async test(pathname, options) {
// See if there's a match for the pathname...
const match = await this.match(pathname, options);
// This default implementation only needs to check to see if there _was_ a
// match. The development matcher actually changes it's behavior by not
// recompiling the routes.
return match !== null;
}
async match(pathname, options) {
// "Iterate" over the match options. Once we found a single match, exit with
// it, otherwise return null below. If no match is found, the inner block
// won't be called.
for await (const match of this.matchAll(pathname, options)){
return match;
}
return null;
}
/**
* This is a point for other managers to override to inject other checking
* behavior like duplicate route checking on a per-request basis.
*
* @param pathname the pathname to validate against
* @param matcher the matcher to validate/test with
* @returns the match if found
*/ validate(pathname, matcher, options) {
var _options_i18n;
if (matcher instanceof LocaleRouteMatcher) {
return matcher.match(pathname, options);
}
// If the locale was inferred from the default locale, then it will have
// already added a locale to the pathname. We need to remove it before
// matching because this matcher is not locale aware.
if ((_options_i18n = options.i18n) == null ? void 0 : _options_i18n.inferredFromDefault) {
return matcher.match(options.i18n.pathname);
}
return matcher.match(pathname);
}
async *matchAll(pathname, options) {
// Guard against the matcher manager from being run before it needs to be
// recompiled. This was preferred to re-running the compilation here because
// it should be re-ran only when it changes. If a match is attempted before
// this is done, it indicates that there is a case where a provider is added
// before it was recompiled (an error). We also don't want to affect request
// times.
if (this.lastCompilationID !== this.compilationID) {
throw new Error("Invariant: expected routes to have been loaded before match");
}
// Ensure that path matching is done with a leading slash.
pathname = ensureLeadingSlash(pathname);
// If this pathname doesn't look like a dynamic route, and this pathname is
// listed in the normalized list of routes, then return it. This ensures
// that when a route like `/user/[id]` is encountered, it doesn't just match
// with the list of normalized routes.
if (!isDynamicRoute(pathname)) {
for (const matcher of this.matchers.static){
const match = this.validate(pathname, matcher, options);
if (!match) continue;
yield match;
}
}
// If we should skip handling dynamic routes, exit now.
if (options == null ? void 0 : options.skipDynamic) return null;
// Loop over the dynamic matchers, yielding each match.
for (const matcher of this.matchers.dynamic){
const match = this.validate(pathname, matcher, options);
if (!match) continue;
yield match;
}
// We tried direct matching against the pathname and against all the dynamic
// paths, so there was no match.
return null;
}
constructor(){
this.providers = [];
this.matchers = {
static: [],
dynamic: [],
duplicates: {}
};
this.lastCompilationID = this.compilationID;
this.previousMatchers = [];
}
}
//# sourceMappingURL=default-route-matcher-manager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,70 @@
import { RouteKind } from "../route-kind";
import { DefaultRouteMatcherManager } from "./default-route-matcher-manager";
import path from "../../../shared/lib/isomorphic/path";
import * as Log from "../../../build/output/log";
import chalk from "next/dist/compiled/chalk";
export class DevRouteMatcherManager extends DefaultRouteMatcherManager {
constructor(production, ensurer, dir){
super();
this.production = production;
this.ensurer = ensurer;
this.dir = dir;
}
async test(pathname, options) {
// Try to find a match within the developer routes.
const match = await super.match(pathname, options);
// Return if the match wasn't null. Unlike the implementation of `match`
// which uses `matchAll` here, this does not call `ensure` on the match
// found via the development matches.
return match !== null;
}
validate(pathname, matcher, options) {
const match = super.validate(pathname, matcher, options);
// If a match was found, check to see if there were any conflicting app or
// pages files.
// TODO: maybe expand this to _any_ duplicated routes instead?
if (match && matcher.duplicated && matcher.duplicated.some((duplicate)=>duplicate.definition.kind === RouteKind.APP_PAGE || duplicate.definition.kind === RouteKind.APP_ROUTE) && matcher.duplicated.some((duplicate)=>duplicate.definition.kind === RouteKind.PAGES || duplicate.definition.kind === RouteKind.PAGES_API)) {
return null;
}
return match;
}
async *matchAll(pathname, options) {
// Compile the development routes.
// TODO: we may want to only run this during testing, users won't be fast enough to require this many dir scans
await super.reload();
// Iterate over the development matches to see if one of them match the
// request path.
for await (const development of super.matchAll(pathname, options)){
// We're here, which means that we haven't seen this match yet, so we
// should try to ensure it and recompile the production matcher.
await this.ensurer.ensure(development);
await this.production.reload();
// Iterate over the production matches again, this time we should be able
// to match it against the production matcher unless there's an error.
for await (const production of this.production.matchAll(pathname, options)){
yield production;
}
}
// We tried direct matching against the pathname and against all the dynamic
// paths, so there was no match.
return null;
}
async reload() {
// Compile the production routes again.
await this.production.reload();
// Compile the development routes.
await super.reload();
// Check for and warn of any duplicates.
for (const [pathname, matchers] of Object.entries(this.matchers.duplicates)){
// We only want to warn about matchers resolving to the same path if their
// identities are different.
const identity = matchers[0].identity;
if (matchers.slice(1).some((matcher)=>matcher.identity !== identity)) {
continue;
}
Log.warn(`Duplicate page detected. ${matchers.map((matcher)=>chalk.cyan(path.relative(this.dir, matcher.definition.filename))).join(" and ")} resolve to ${chalk.cyan(pathname)}`);
}
}
}
//# sourceMappingURL=dev-route-matcher-manager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/future/route-matcher-managers/dev-route-matcher-manager.ts"],"names":["RouteKind","DefaultRouteMatcherManager","path","Log","chalk","DevRouteMatcherManager","constructor","production","ensurer","dir","test","pathname","options","match","validate","matcher","duplicated","some","duplicate","definition","kind","APP_PAGE","APP_ROUTE","PAGES","PAGES_API","matchAll","reload","development","ensure","matchers","Object","entries","duplicates","identity","slice","warn","map","cyan","relative","filename","join"],"mappings":"AAAA,SAASA,SAAS,QAAQ,gBAAe;AAGzC,SAASC,0BAA0B,QAAQ,kCAAiC;AAE5E,OAAOC,UAAU,sCAAqC;AACtD,YAAYC,SAAS,4BAA2B;AAChD,OAAOC,WAAW,2BAA0B;AAO5C,OAAO,MAAMC,+BAA+BJ;IAC1CK,YACmBC,YACAC,SACAC,IACjB;QACA,KAAK;0BAJYF;uBACAC;mBACAC;IAGnB;IAEA,MAAaC,KAAKC,QAAgB,EAAEC,OAAqB,EAAoB;QAC3E,mDAAmD;QACnD,MAAMC,QAAQ,MAAM,KAAK,CAACA,MAAMF,UAAUC;QAE1C,wEAAwE;QACxE,uEAAuE;QACvE,qCAAqC;QACrC,OAAOC,UAAU;IACnB;IAEUC,SACRH,QAAgB,EAChBI,OAAqB,EACrBH,OAAqB,EACF;QACnB,MAAMC,QAAQ,KAAK,CAACC,SAASH,UAAUI,SAASH;QAEhD,0EAA0E;QAC1E,eAAe;QACf,8DAA8D;QAC9D,IACEC,SACAE,QAAQC,UAAU,IAClBD,QAAQC,UAAU,CAACC,IAAI,CACrB,CAACC,YACCA,UAAUC,UAAU,CAACC,IAAI,KAAKpB,UAAUqB,QAAQ,IAChDH,UAAUC,UAAU,CAACC,IAAI,KAAKpB,UAAUsB,SAAS,KAErDP,QAAQC,UAAU,CAACC,IAAI,CACrB,CAACC,YACCA,UAAUC,UAAU,CAACC,IAAI,KAAKpB,UAAUuB,KAAK,IAC7CL,UAAUC,UAAU,CAACC,IAAI,KAAKpB,UAAUwB,SAAS,GAErD;YACA,OAAO;QACT;QAEA,OAAOX;IACT;IAEA,OAAcY,SACZd,QAAgB,EAChBC,OAAqB,EACoD;QACzE,kCAAkC;QAClC,+GAA+G;QAC/G,MAAM,KAAK,CAACc;QAEZ,uEAAuE;QACvE,gBAAgB;QAChB,WAAW,MAAMC,eAAe,KAAK,CAACF,SAASd,UAAUC,SAAU;YACjE,qEAAqE;YACrE,gEAAgE;YAChE,MAAM,IAAI,CAACJ,OAAO,CAACoB,MAAM,CAACD;YAC1B,MAAM,IAAI,CAACpB,UAAU,CAACmB,MAAM;YAE5B,yEAAyE;YACzE,sEAAsE;YACtE,WAAW,MAAMnB,cAAc,IAAI,CAACA,UAAU,CAACkB,QAAQ,CACrDd,UACAC,SACC;gBACD,MAAML;YACR;QACF;QAEA,4EAA4E;QAC5E,gCAAgC;QAChC,OAAO;IACT;IAEA,MAAamB,SAAwB;QACnC,uCAAuC;QACvC,MAAM,IAAI,CAACnB,UAAU,CAACmB,MAAM;QAE5B,kCAAkC;QAClC,MAAM,KAAK,CAACA;QAEZ,wCAAwC;QACxC,KAAK,MAAM,CAACf,UAAUkB,SAAS,IAAIC,OAAOC,OAAO,CAC/C,IAAI,CAACF,QAAQ,CAACG,UAAU,EACvB;YACD,0EAA0E;YAC1E,4BAA4B;YAC5B,MAAMC,WAAWJ,QAAQ,CAAC,EAAE,CAACI,QAAQ;YACrC,IAAIJ,SAASK,KAAK,CAAC,GAAGjB,IAAI,CAAC,CAACF,UAAYA,QAAQkB,QAAQ,KAAKA,WAAW;gBACtE;YACF;YAEA9B,IAAIgC,IAAI,CACN,CAAC,yBAAyB,EAAEN,SACzBO,GAAG,CAAC,CAACrB,UACJX,MAAMiC,IAAI,CAACnC,KAAKoC,QAAQ,CAAC,IAAI,CAAC7B,GAAG,EAAEM,QAAQI,UAAU,CAACoB,QAAQ,IAE/DC,IAAI,CAAC,SAAS,YAAY,EAAEpC,MAAMiC,IAAI,CAAC1B,UAAU,CAAC;QAEzD;IACF;AACF"}

View File

@@ -0,0 +1,3 @@
export { };
//# sourceMappingURL=route-matcher-manager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/future/route-matcher-managers/route-matcher-manager.ts"],"names":[],"mappings":"AAAA,WAiEC"}