[{"data":1,"prerenderedAt":858},["ShallowReactive",2],{"navigation_docs":3,"-guide-purl":72,"-guide-purl-surround":853},[4,40],{"title":5,"path":6,"stem":7,"children":8,"icon":39},"Guide","\u002Fguide","1.guide\u002F1.index",[9,11,15,19,23,27,31,35],{"title":10,"path":6,"stem":7},"Getting Started",{"title":12,"path":13,"stem":14},"PURLs","\u002Fguide\u002Fpurl","1.guide\u002F2.purl",{"title":16,"path":17,"stem":18},"Lookups","\u002Fguide\u002Flookups","1.guide\u002F3.lookups",{"title":20,"path":21,"stem":22},"Caching","\u002Fguide\u002Fcache","1.guide\u002F4.cache",{"title":24,"path":25,"stem":26},"CLI","\u002Fguide\u002Fcli","1.guide\u002F5.cli",{"title":28,"path":29,"stem":30},"Agents","\u002Fguide\u002Fagents","1.guide\u002F6.agents",{"title":32,"path":33,"stem":34},"Custom Registries","\u002Fguide\u002Fcustom","1.guide\u002F7.custom",{"title":36,"path":37,"stem":38},"Explorer","\u002Fguide\u002Fexplorer","1.guide\u002F8.explorer","i-lucide-book-open",{"title":41,"path":42,"stem":43,"children":44,"icon":71},"Registries","\u002Fregistries","2.registries\u002F0.index",[45,47,51,55,59,63,67],{"title":46,"path":42,"stem":43},"Overview",{"title":48,"path":49,"stem":50},"npm","\u002Fregistries\u002Fnpm","2.registries\u002F01.npm",{"title":52,"path":53,"stem":54},"crates.io","\u002Fregistries\u002Fcargo","2.registries\u002F02.cargo",{"title":56,"path":57,"stem":58},"PyPI","\u002Fregistries\u002Fpypi","2.registries\u002F03.pypi",{"title":60,"path":61,"stem":62},"RubyGems","\u002Fregistries\u002Frubygems","2.registries\u002F04.rubygems",{"title":64,"path":65,"stem":66},"Packagist","\u002Fregistries\u002Fpackagist","2.registries\u002F05.packagist",{"title":68,"path":69,"stem":70},"Arch Linux","\u002Fregistries\u002Falpm","2.registries\u002F06.alpm","i-lucide-library",{"id":73,"title":12,"body":74,"description":846,"extension":847,"links":848,"meta":849,"navigation":191,"path":13,"seo":851,"stem":14,"__hash__":852},"docs\u002F1.guide\u002F2.purl.md",{"type":75,"value":76,"toc":837},"minimark",[77,82,94,105,131,135,253,274,285,289,496,503,507,619,639,643,657,661,686,695,699,827,833],[78,79,81],"h2",{"id":80},"the-scheme","The scheme",[83,84,85,86,93],"p",{},"A package URL, PURL for short, is defined by ",[87,88,92],"a",{"href":89,"rel":90},"https:\u002F\u002Fgithub.com\u002Fpackage-url\u002Fpurl-spec",[91],"nofollow","ECMA-427",":",[95,96,102],"pre",{"className":97,"code":99,"language":100,"meta":101},[98],"language-text","pkg:type\u002Fnamespace\u002Fname@version?qualifiers#subpath\n","text","",[103,104,99],"code",{"__ignoreMap":101},[83,106,107,110,111,114,115,118,119,122,123,126,127,130],{},[103,108,109],{},"type"," picks the adapter. ",[103,112,113],{},"namespace"," is the npm scope, the Composer vendor or the Arch repository. ",[103,116,117],{},"version"," is optional everywhere except for a dependency lookup. ",[103,120,121],{},"qualifiers"," carry one key the library reads, ",[103,124,125],{},"repository_url",", which overrides the adapter's base URL. ",[103,128,129],{},"subpath"," is parsed and ignored.",[78,132,134],{"id":133},"parse","Parse",[95,136,140],{"className":137,"code":138,"language":139,"meta":101,"style":101},"language-ts shiki shiki-themes github-light github-light poimandres","import { parsePURL, fullName } from \"@agntn\u002Fregistries\";\n\nconst parsed = parsePURL(\"pkg:npm\u002F%40vue\u002Fcore@3.5.0\");\n\u002F\u002F { type: \"npm\", namespace: \"@vue\", name: \"core\", version: \"3.5.0\", qualifiers: {}, subpath: \"\" }\n\nfullName(parsed); \u002F\u002F \"@vue\u002Fcore\", the name the registry expects\n","ts",[103,141,142,186,193,223,230,235],{"__ignoreMap":101},[143,144,147,151,155,159,162,165,168,172,176,180,183],"span",{"class":145,"line":146},"line",1,[143,148,150],{"class":149},"sP0CR","import",[143,152,154],{"class":153},"sKlNE"," {",[143,156,158],{"class":157},"snHMy"," parsePURL",[143,160,161],{"class":153},",",[143,163,164],{"class":157}," fullName",[143,166,167],{"class":153}," }",[143,169,171],{"class":170},"stCPd"," from",[143,173,175],{"class":174},"scVjq"," \"",[143,177,179],{"class":178},"sQ7BG","@agntn\u002Fregistries",[143,181,182],{"class":174},"\"",[143,184,185],{"class":153},";\n",[143,187,189],{"class":145,"line":188},2,[143,190,192],{"emptyLinePlaceholder":191},true,"\n",[143,194,196,200,204,207,210,213,215,218,220],{"class":145,"line":195},3,[143,197,199],{"class":198},"s1TYA","const",[143,201,203],{"class":202},"saoiD"," parsed",[143,205,206],{"class":198}," =",[143,208,158],{"class":209},"sULi6",[143,211,212],{"class":153},"(",[143,214,182],{"class":174},[143,216,217],{"class":178},"pkg:npm\u002F%40vue\u002Fcore@3.5.0",[143,219,182],{"class":174},[143,221,222],{"class":153},");\n",[143,224,226],{"class":145,"line":225},4,[143,227,229],{"class":228},"sjhu3","\u002F\u002F { type: \"npm\", namespace: \"@vue\", name: \"core\", version: \"3.5.0\", qualifiers: {}, subpath: \"\" }\n",[143,231,233],{"class":145,"line":232},5,[143,234,192],{"emptyLinePlaceholder":191},[143,236,238,241,243,247,250],{"class":145,"line":237},6,[143,239,240],{"class":209},"fullName",[143,242,212],{"class":153},[143,244,246],{"class":245},"spVlQ","parsed",[143,248,249],{"class":153},"); ",[143,251,252],{"class":228},"\u002F\u002F \"@vue\u002Fcore\", the name the registry expects\n",[83,254,255,258,259,262,263,266,267,270,271,273],{},[103,256,257],{},"parsePURL"," is the single parser in the library. Every helper, the CLI and the AI tool go through it, so a PURL that works in one place works everywhere. It percent-decodes each component, lowercases the type, and applies the two normalizations the spec asks for: PyPI names are lowercased with runs of ",[103,260,261],{},"-",", ",[103,264,265],{},"_"," and ",[103,268,269],{},"."," collapsed to ",[103,272,261],{},"; Arch names are lowercased.",[83,275,276,277,280,281,284],{},"A PURL that cannot be parsed throws ",[103,278,279],{},"InvalidPURLError"," with the reason: a missing ",[103,282,283],{},"pkg:"," scheme, an empty type, an empty name, or malformed percent-encoding.",[78,286,288],{"id":287},"build","Build",[95,290,292],{"className":137,"code":291,"language":139,"meta":101,"style":101},"import { buildPURL } from \"@agntn\u002Fregistries\";\n\nbuildPURL({ type: \"cargo\", name: \"serde\", version: \"1.0.0\" });\n\u002F\u002F \"pkg:cargo\u002Fserde@1.0.0\"\n\nbuildPURL({ type: \"npm\", namespace: \"@vue\", name: \"core\" });\n\u002F\u002F \"pkg:npm\u002F%40vue\u002Fcore\"\n\nbuildPURL({ type: \"npm\", name: \"lodash\", qualifiers: { repository_url: \"https:\u002F\u002Fnpm.example\" } });\n\u002F\u002F \"pkg:npm\u002Flodash?repository_url=https%3A%2F%2Fnpm.example\"\n",[103,293,294,315,319,370,375,379,424,430,435,490],{"__ignoreMap":101},[143,295,296,298,300,303,305,307,309,311,313],{"class":145,"line":146},[143,297,150],{"class":149},[143,299,154],{"class":153},[143,301,302],{"class":157}," buildPURL",[143,304,167],{"class":153},[143,306,171],{"class":170},[143,308,175],{"class":174},[143,310,179],{"class":178},[143,312,182],{"class":174},[143,314,185],{"class":153},[143,316,317],{"class":145,"line":188},[143,318,192],{"emptyLinePlaceholder":191},[143,320,321,324,327,330,332,334,337,339,341,344,346,348,351,353,355,358,360,362,365,367],{"class":145,"line":195},[143,322,323],{"class":209},"buildPURL",[143,325,326],{"class":153},"({",[143,328,329],{"class":157}," type",[143,331,93],{"class":153},[143,333,175],{"class":174},[143,335,336],{"class":178},"cargo",[143,338,182],{"class":174},[143,340,161],{"class":153},[143,342,343],{"class":157}," name",[143,345,93],{"class":153},[143,347,175],{"class":174},[143,349,350],{"class":178},"serde",[143,352,182],{"class":174},[143,354,161],{"class":153},[143,356,357],{"class":157}," version",[143,359,93],{"class":153},[143,361,175],{"class":174},[143,363,364],{"class":178},"1.0.0",[143,366,182],{"class":174},[143,368,369],{"class":153}," });\n",[143,371,372],{"class":145,"line":225},[143,373,374],{"class":228},"\u002F\u002F \"pkg:cargo\u002Fserde@1.0.0\"\n",[143,376,377],{"class":145,"line":232},[143,378,192],{"emptyLinePlaceholder":191},[143,380,381,383,385,387,389,391,393,395,397,400,402,404,407,409,411,413,415,417,420,422],{"class":145,"line":237},[143,382,323],{"class":209},[143,384,326],{"class":153},[143,386,329],{"class":157},[143,388,93],{"class":153},[143,390,175],{"class":174},[143,392,48],{"class":178},[143,394,182],{"class":174},[143,396,161],{"class":153},[143,398,399],{"class":157}," namespace",[143,401,93],{"class":153},[143,403,175],{"class":174},[143,405,406],{"class":178},"@vue",[143,408,182],{"class":174},[143,410,161],{"class":153},[143,412,343],{"class":157},[143,414,93],{"class":153},[143,416,175],{"class":174},[143,418,419],{"class":178},"core",[143,421,182],{"class":174},[143,423,369],{"class":153},[143,425,427],{"class":145,"line":426},7,[143,428,429],{"class":228},"\u002F\u002F \"pkg:npm\u002F%40vue\u002Fcore\"\n",[143,431,433],{"class":145,"line":432},8,[143,434,192],{"emptyLinePlaceholder":191},[143,436,438,440,442,444,446,448,450,452,454,456,458,460,463,465,467,470,472,474,477,479,481,484,486,488],{"class":145,"line":437},9,[143,439,323],{"class":209},[143,441,326],{"class":153},[143,443,329],{"class":157},[143,445,93],{"class":153},[143,447,175],{"class":174},[143,449,48],{"class":178},[143,451,182],{"class":174},[143,453,161],{"class":153},[143,455,343],{"class":157},[143,457,93],{"class":153},[143,459,175],{"class":174},[143,461,462],{"class":178},"lodash",[143,464,182],{"class":174},[143,466,161],{"class":153},[143,468,469],{"class":157}," qualifiers",[143,471,93],{"class":153},[143,473,154],{"class":153},[143,475,476],{"class":157}," repository_url",[143,478,93],{"class":153},[143,480,175],{"class":174},[143,482,483],{"class":178},"https:\u002F\u002Fnpm.example",[143,485,182],{"class":174},[143,487,167],{"class":153},[143,489,369],{"class":153},[143,491,493],{"class":145,"line":492},10,[143,494,495],{"class":228},"\u002F\u002F \"pkg:npm\u002Flodash?repository_url=https%3A%2F%2Fnpm.example\"\n",[83,497,498,499,502],{},"Output is canonical: components are percent-encoded, qualifier keys are lowercased and sorted, and a duplicate or invalid key throws instead of producing a PURL that would parse differently on the way back. Every adapter's ",[103,500,501],{},"urls().purl(name, version)"," uses it, so a PURL the library prints is one the library reads.",[78,504,506],{"id":505},"namespaces-by-ecosystem","Namespaces by ecosystem",[508,509,510,526],"table",{},[511,512,513],"thead",{},[514,515,516,520,523],"tr",{},[517,518,519],"th",{},"Ecosystem",[517,521,522],{},"Namespace",[517,524,525],{},"Example",[527,528,529,549,562,573,584,596],"tbody",{},[514,530,531,534,540],{},[532,533,48],"td",{},[532,535,536,537],{},"scope, with ",[103,538,539],{},"@",[532,541,542,545,546],{},[103,543,544],{},"pkg:npm\u002F%40vue\u002Fcore"," or ",[103,547,548],{},"pkg:npm\u002F@vue\u002Fcore",[514,550,551,554,557],{},[532,552,553],{},"Cargo",[532,555,556],{},"none",[532,558,559],{},[103,560,561],{},"pkg:cargo\u002Fserde",[514,563,564,566,568],{},[532,565,56],{},[532,567,556],{},[532,569,570],{},[103,571,572],{},"pkg:pypi\u002Fflask",[514,574,575,577,579],{},[532,576,60],{},[532,578,556],{},[532,580,581],{},[103,582,583],{},"pkg:gem\u002Frails",[514,585,586,588,591],{},[532,587,64],{},[532,589,590],{},"vendor, required",[532,592,593],{},[103,594,595],{},"pkg:composer\u002Flaravel\u002Fframework",[514,597,598,600,611],{},[532,599,68],{},[532,601,602,545,605,262,608,610],{},[103,603,604],{},"arch",[103,606,607],{},"aur",[103,609,604],{}," by default",[532,612,613,262,616],{},[103,614,615],{},"pkg:alpm\u002Fpacman",[103,617,618],{},"pkg:alpm\u002Faur\u002Fparu",[83,620,621,623,624,627,628,630,631,266,633,635,636,269],{},[103,622,240],{}," joins namespace and name with ",[103,625,626],{},"\u002F",", which is what every adapter expects as its registry name. Packagist refuses a name without a vendor with ",[103,629,279],{},". The Arch adapter refuses a namespace other than ",[103,632,604],{},[103,634,607],{}," with ",[103,637,638],{},"NotFoundError",[78,640,642],{"id":641},"shorthand-in-the-cli","Shorthand in the CLI",[83,644,645,646,266,649,652,653,656],{},"The CLI accepts a PURL without its scheme. ",[103,647,648],{},"registries info npm\u002Flodash",[103,650,651],{},"registries info pkg:npm\u002Flodash"," are the same call. The library does not: ",[103,654,655],{},"fetchPackageFromPURL(\"npm\u002Flodash\")"," throws, because a silent guess about the scheme would hide a typo in the type.",[78,658,660],{"id":659},"pointing-at-another-registry","Pointing at another registry",[95,662,664],{"className":137,"code":663,"language":139,"meta":101,"style":101},"await fetchPackageFromPURL(\"pkg:npm\u002Flodash?repository_url=https:\u002F\u002Fregistry.npmmirror.com\");\n",[103,665,666],{"__ignoreMap":101},[143,667,668,672,675,677,679,682,684],{"class":145,"line":146},[143,669,671],{"class":670},"sZW6a","await",[143,673,674],{"class":209}," fetchPackageFromPURL",[143,676,212],{"class":153},[143,678,182],{"class":174},[143,680,681],{"class":178},"pkg:npm\u002Flodash?repository_url=https:\u002F\u002Fregistry.npmmirror.com",[143,683,182],{"class":174},[143,685,222],{"class":153},[83,687,688,690,691,694],{},[103,689,125],{}," becomes the adapter's base URL for that call. The adapter's ",[103,692,693],{},"urls()"," still point at the public registry, since those are display links, not API calls.",[78,696,698],{"id":697},"from-a-purl-to-an-adapter","From a PURL to an adapter",[95,700,702],{"className":137,"code":701,"language":139,"meta":101,"style":101},"import { createFromPURL } from \"@agntn\u002Fregistries\";\n\nconst [registry, name, version] = createFromPURL(\"pkg:pypi\u002Fflask@3.1.1\");\n\nregistry.ecosystem(); \u002F\u002F \"pypi\"\nname; \u002F\u002F \"flask\"\nversion; \u002F\u002F \"3.1.1\"\nawait registry.fetchDependencies(name, version);\n",[103,703,704,725,729,767,771,786,796,805],{"__ignoreMap":101},[143,705,706,708,710,713,715,717,719,721,723],{"class":145,"line":146},[143,707,150],{"class":149},[143,709,154],{"class":153},[143,711,712],{"class":157}," createFromPURL",[143,714,167],{"class":153},[143,716,171],{"class":170},[143,718,175],{"class":174},[143,720,179],{"class":178},[143,722,182],{"class":174},[143,724,185],{"class":153},[143,726,727],{"class":145,"line":188},[143,728,192],{"emptyLinePlaceholder":191},[143,730,731,733,736,739,741,744,746,748,751,754,756,758,760,763,765],{"class":145,"line":195},[143,732,199],{"class":198},[143,734,735],{"class":153}," [",[143,737,738],{"class":202},"registry",[143,740,262],{"class":153},[143,742,743],{"class":202},"name",[143,745,262],{"class":153},[143,747,117],{"class":202},[143,749,750],{"class":153},"] ",[143,752,753],{"class":198},"=",[143,755,712],{"class":209},[143,757,212],{"class":153},[143,759,182],{"class":174},[143,761,762],{"class":178},"pkg:pypi\u002Fflask@3.1.1",[143,764,182],{"class":174},[143,766,222],{"class":153},[143,768,769],{"class":145,"line":225},[143,770,192],{"emptyLinePlaceholder":191},[143,772,773,775,777,780,783],{"class":145,"line":232},[143,774,738],{"class":245},[143,776,269],{"class":153},[143,778,779],{"class":209},"ecosystem",[143,781,782],{"class":153},"(); ",[143,784,785],{"class":228},"\u002F\u002F \"pypi\"\n",[143,787,788,790,793],{"class":145,"line":237},[143,789,743],{"class":245},[143,791,792],{"class":153},"; ",[143,794,795],{"class":228},"\u002F\u002F \"flask\"\n",[143,797,798,800,802],{"class":145,"line":426},[143,799,117],{"class":245},[143,801,792],{"class":153},[143,803,804],{"class":228},"\u002F\u002F \"3.1.1\"\n",[143,806,807,809,812,814,817,819,821,823,825],{"class":145,"line":432},[143,808,671],{"class":670},[143,810,811],{"class":245}," registry",[143,813,269],{"class":153},[143,815,816],{"class":209},"fetchDependencies",[143,818,212],{"class":153},[143,820,743],{"class":245},[143,822,262],{"class":153},[143,824,117],{"class":245},[143,826,222],{"class":153},[83,828,829,832],{},[103,830,831],{},"createFromPURL"," is what the helpers wrap. Use it when you want several lookups on one package without parsing the PURL four times.",[834,835,836],"style",{},"html pre.shiki code .sP0CR, html code.shiki .sP0CR{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#5DE4C7}html pre.shiki code .sKlNE, html code.shiki .sKlNE{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#A6ACCD}html pre.shiki code .snHMy, html code.shiki .snHMy{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#ADD7FF}html pre.shiki code .stCPd, html code.shiki .stCPd{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#A6ACCD}html pre.shiki code .scVjq, html code.shiki .scVjq{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#A6ACCD}html pre.shiki code .sQ7BG, html code.shiki .sQ7BG{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#5DE4C7}html pre.shiki code .s1TYA, html code.shiki .s1TYA{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#91B4D5}html pre.shiki code .saoiD, html code.shiki .saoiD{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#E4F0FB}html pre.shiki code .sULi6, html code.shiki .sULi6{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#E4F0FBD0}html pre.shiki code .sjhu3, html code.shiki .sjhu3{--shiki-light:#6A737D;--shiki-light-font-style:inherit;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#767C9DB0;--shiki-dark-font-style:italic}html pre.shiki code .spVlQ, html code.shiki .spVlQ{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E4F0FB}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZW6a, html code.shiki .sZW6a{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#5DE4C7C0}",{"title":101,"searchDepth":188,"depth":188,"links":838},[839,840,841,842,843,844,845],{"id":80,"depth":188,"text":81},{"id":133,"depth":188,"text":134},{"id":287,"depth":188,"text":288},{"id":505,"depth":188,"text":506},{"id":641,"depth":188,"text":642},{"id":659,"depth":188,"text":660},{"id":697,"depth":188,"text":698},"The package URL is the only address the library takes. Parse it, build it, and know what each ecosystem does with the namespace.","md",null,{"icon":850},"i-lucide-link",{"title":12,"description":846},"B5t_CCHNw9svMgJiVsEllKi09dyxzJ3JXe6hSh1OoJ0",[854,856],{"title":10,"path":6,"stem":7,"description":855,"children":-1},"Install the package, resolve one PURL, and get the same package shape from six registries.",{"title":16,"path":17,"stem":18,"description":857,"children":-1},"Package, versions, dependencies and maintainers, what each returns, and what changes between registries.",1788462725129]