feat(registry): add declarative protocol routing and request-scoped storage hook context across registries

This commit is contained in:
2026-04-16 10:42:33 +00:00
parent 09335d41f3
commit 9643ef98b9
28 changed files with 2327 additions and 1919 deletions
+110
View File
@@ -0,0 +1,110 @@
export type TNpmRequestRoute =
| { type: 'root' }
| { type: 'search' }
| { type: 'userAuth'; username: string }
| { type: 'tokens'; path: string }
| { type: 'distTags'; packageName: string; tag?: string }
| { type: 'tarball'; packageName: string; filename: string }
| { type: 'unpublishVersion'; packageName: string; version: string }
| { type: 'unpublishPackage'; packageName: string; rev: string }
| { type: 'packageVersion'; packageName: string; version: string }
| { type: 'package'; packageName: string };
function decodePackageName(rawPackageName: string): string {
return decodeURIComponent(rawPackageName);
}
export function parseNpmRequestRoute(path: string, method: string): TNpmRequestRoute | null {
if (path === '/' || path === '') {
return { type: 'root' };
}
if (path.startsWith('/-/v1/search')) {
return { type: 'search' };
}
const userMatch = path.match(/^\/-\/user\/org\.couchdb\.user:(.+)$/);
if (userMatch) {
return {
type: 'userAuth',
username: userMatch[1],
};
}
if (path.startsWith('/-/npm/v1/tokens')) {
return {
type: 'tokens',
path,
};
}
const distTagsMatch = path.match(/^\/-\/package\/(@?[^\/]+(?:\/[^\/]+)?)\/dist-tags(?:\/(.+))?$/);
if (distTagsMatch) {
const [, rawPackageName, tag] = distTagsMatch;
return {
type: 'distTags',
packageName: decodePackageName(rawPackageName),
tag,
};
}
const tarballMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-\/(.+\.tgz)$/);
if (tarballMatch) {
const [, rawPackageName, filename] = tarballMatch;
return {
type: 'tarball',
packageName: decodePackageName(rawPackageName),
filename,
};
}
if (method === 'DELETE') {
const unpublishVersionMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-\/([^\/]+)$/);
if (unpublishVersionMatch) {
const [, rawPackageName, version] = unpublishVersionMatch;
return {
type: 'unpublishVersion',
packageName: decodePackageName(rawPackageName),
version,
};
}
const unpublishPackageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-rev\/([^\/]+)$/);
if (unpublishPackageMatch) {
const [, rawPackageName, rev] = unpublishPackageMatch;
return {
type: 'unpublishPackage',
packageName: decodePackageName(rawPackageName),
rev,
};
}
}
const unencodedScopedPackageMatch = path.match(/^\/@[^\/]+\/[^\/]+$/);
if (unencodedScopedPackageMatch) {
return {
type: 'package',
packageName: decodePackageName(path.substring(1)),
};
}
const versionMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/([^\/]+)$/);
if (versionMatch) {
const [, rawPackageName, version] = versionMatch;
return {
type: 'packageVersion',
packageName: decodePackageName(rawPackageName),
version,
};
}
const packageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)$/);
if (packageMatch) {
return {
type: 'package',
packageName: decodePackageName(packageMatch[1]),
};
}
return null;
}