<?xml version="1.0" encoding="UTF-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us"><id>https://blog.pgxn.org/tags/json/</id><title>JSON</title><updated>2011-05-13T03:58:33Z</updated><link rel="self" type="application/atom+xml" href="https://blog.pgxn.org/tags/json/feed.xml"/><link rel="alternate" type="text/html" href="https://blog.pgxn.org/tags/json/"/><author><name>The PGXN Maintainers</name></author><generator uri="https://gohugo.io/" version="0.167.0">Hugo</generator><entry><id>https://blog.pgxn.org/post/5441748625</id><title type="html">Using JSONP Callbacks</title><link rel="alternate" type="text/html" href="https://blog.pgxn.org/2011/jsonp-callbacks/"/><updated>2026-10-07T16:13:48Z</updated><published>2011-05-13T03:58:33Z</published><author><name>David E. Wheeler</name></author><category scheme="https://blog.pgxn.org/tags" term="jsonp" label="JSONP"/><category scheme="https://blog.pgxn.org/tags" term="callback" label="Callback"/><category scheme="https://blog.pgxn.org/tags" term="blog" label="Blog"/><category scheme="https://blog.pgxn.org/tags" term="json" label="Json"/><summary type="html"><![CDATA[<p>Using the new <a href="https://github.com/pgxn/pgxn-api/wiki/JSONP">JSONP callback support</a> in the PGXN API, I just added a sidebar
to <a href="https://www.justatheory.com/" title="Just a Theory">my personal blog</a> that shows a list of all my PGXN distributions. The code
is simple:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;links&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>PGXN Code<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nx">pgxn_distros</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s1">&#39;&lt;dl&gt;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="nx">dist</span> <span class="k">in</span> <span class="nx">data</span><span class="p">.</span><span class="nx">releases</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;&lt;dt&gt;&lt;a href=https://pgxn.org/dist/&#39;</span> <span class="o">+</span> <span class="nx">dist</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;&gt;&#39;</span> <span class="o">+</span> <span class="nx">dist</span> <span class="o">+</span> <span class="s1">&#39;&lt;/a&gt;&lt;/dt&gt;&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;&lt;dd&gt;&#39;</span> <span class="o">+</span> <span class="nx">data</span><span class="p">.</span><span class="nx">releases</span><span class="p">[</span><span class="nx">dist</span><span class="p">].</span><span class="kr">abstract</span> <span class="o">+</span> <span class="s1">&#39;&lt;/dd&gt;&#39;</span>
</span></span><span class="line"><span class="cl">            <span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s1">&#39;&lt;/dl&gt;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://api.pgxn.org/user/theory.json?callback=pgxn_distros&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>And the output looks like this:</p>
<p><img src="/2011/jsonp-callbacks/pgxn-code.png" alt="PGXN Code"></p>
<p>Not bad, eh? AS you can see, JSONP is dead easy to use with the JSON files
served by the JSON API. Try that, CPAN!</p>]]></summary><content type="html" xml:base="https://blog.pgxn.org/" xml:space="preserve"><![CDATA[<p>Using the new <a href="https://github.com/pgxn/pgxn-api/wiki/JSONP">JSONP callback support</a> in the PGXN API, I just added a sidebar
to <a href="https://www.justatheory.com/" title="Just a Theory">my personal blog</a> that shows a list of all my PGXN distributions. The code
is simple:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;links&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>PGXN Code<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nx">pgxn_distros</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s1">&#39;&lt;dl&gt;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="nx">dist</span> <span class="k">in</span> <span class="nx">data</span><span class="p">.</span><span class="nx">releases</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;&lt;dt&gt;&lt;a href=https://pgxn.org/dist/&#39;</span> <span class="o">+</span> <span class="nx">dist</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;&gt;&#39;</span> <span class="o">+</span> <span class="nx">dist</span> <span class="o">+</span> <span class="s1">&#39;&lt;/a&gt;&lt;/dt&gt;&#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;&lt;dd&gt;&#39;</span> <span class="o">+</span> <span class="nx">data</span><span class="p">.</span><span class="nx">releases</span><span class="p">[</span><span class="nx">dist</span><span class="p">].</span><span class="kr">abstract</span> <span class="o">+</span> <span class="s1">&#39;&lt;/dd&gt;&#39;</span>
</span></span><span class="line"><span class="cl">            <span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s1">&#39;&lt;/dl&gt;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://api.pgxn.org/user/theory.json?callback=pgxn_distros&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>And the output looks like this:</p>
<p><img src="/2011/jsonp-callbacks/pgxn-code.png" alt="PGXN Code"></p>
<p>Not bad, eh? AS you can see, JSONP is dead easy to use with the JSON files
served by the JSON API. Try that, CPAN!</p>
]]></content></entry><entry><id>https://blog.pgxn.org/post/4854707157</id><title type="html">About the Infrastructure: PGXN Manager</title><link rel="alternate" type="text/html" href="https://blog.pgxn.org/2011/pgxn-manager/"/><updated>2026-10-07T16:13:48Z</updated><published>2011-04-23T02:54:34Z</published><author><name>David E. Wheeler</name></author><category scheme="https://blog.pgxn.org/tags" term="pgxnmanager" label="PGXN::Manager"/><category scheme="https://blog.pgxn.org/tags" term="infrastructure" label="Infrastructure"/><category scheme="https://blog.pgxn.org/tags" term="code" label="Code"/><category scheme="https://blog.pgxn.org/tags" term="perl" label="Perl"/><category scheme="https://blog.pgxn.org/tags" term="semver" label="SemVer"/><category scheme="https://blog.pgxn.org/tags" term="json" label="Json"/><category scheme="https://blog.pgxn.org/tags" term="normalization" label="Normalization"/><category scheme="https://blog.pgxn.org/tags" term="validation" label="Validation"/><category scheme="https://blog.pgxn.org/tags" term="zip" label="Zip"/><summary type="html"><![CDATA[<p>The PGXN infrastructure is currently made up of four parts:</p>
<ul>
<li>
<p>The network of <a href="https://api.pgxn.org/mirror/meta/mirrors.json">mirrors</a>, derived from the <a href="https://master.pgxn.org/">master mirror</a>, and synchronized
on various schedules via <code>rsync</code> (<a href="https://pgxn.org/mirroring/">host a mirror</a>).</p>
</li>
<li>
<p><a href="https://manager.pgxn.org/">PGXN Manager</a> (<a href="https://github.com/pgxn/pgxn-manager/" title="Fork PGXN::Manager on GitHub">code</a>) is the core of the network. It provides the
interface for users to upload releases, processes those releases, indexes
them, and puts them on the master mirror. Details below.</p>
</li>
<li>
<p><a href="https://api.pgxn.org">PGXN API</a> (<a href="https://github.com/pgxn/pgxn-api" title="Fork PGXN::API on GitHub">code</a>) is a mirror server with benefits. Once an hour, it
<code>rsync</code>s from the master mirror, and does extra processing of new and
modified files, notably full-text indexing. I&rsquo;ll write up some details next
week.</p>
</li>
<li>
<p><a href="https://pgxn.org">PGXN Site</a> (<a href="https://github.com/pgxn/pgxn-site" title="Fork PGXN::Site on GitHub">code</a>) powers the main site. It&rsquo;s a thin wrapper around the
API server, using <a href="https://github.com/pgxn/www-pgxn" title="Fork WWW::PGXN on GitHub">WWW::PGXN</a> to fetch JSON files and convert them to HTML.
I&rsquo;ll write more about this bit next week, too.</p>
</li>
</ul>]]></summary><content type="html" xml:base="https://blog.pgxn.org/" xml:space="preserve"><![CDATA[<p>The PGXN infrastructure is currently made up of four parts:</p>
<ul>
<li>
<p>The network of <a href="https://api.pgxn.org/mirror/meta/mirrors.json">mirrors</a>, derived from the <a href="https://master.pgxn.org/">master mirror</a>, and synchronized
on various schedules via <code>rsync</code> (<a href="https://pgxn.org/mirroring/">host a mirror</a>).</p>
</li>
<li>
<p><a href="https://manager.pgxn.org/">PGXN Manager</a> (<a href="https://github.com/pgxn/pgxn-manager/" title="Fork PGXN::Manager on GitHub">code</a>) is the core of the network. It provides the
interface for users to upload releases, processes those releases, indexes
them, and puts them on the master mirror. Details below.</p>
</li>
<li>
<p><a href="https://api.pgxn.org">PGXN API</a> (<a href="https://github.com/pgxn/pgxn-api" title="Fork PGXN::API on GitHub">code</a>) is a mirror server with benefits. Once an hour, it
<code>rsync</code>s from the master mirror, and does extra processing of new and
modified files, notably full-text indexing. I&rsquo;ll write up some details next
week.</p>
</li>
<li>
<p><a href="https://pgxn.org">PGXN Site</a> (<a href="https://github.com/pgxn/pgxn-site" title="Fork PGXN::Site on GitHub">code</a>) powers the main site. It&rsquo;s a thin wrapper around the
API server, using <a href="https://github.com/pgxn/www-pgxn" title="Fork WWW::PGXN on GitHub">WWW::PGXN</a> to fetch JSON files and convert them to HTML.
I&rsquo;ll write more about this bit next week, too.</p>
</li>
</ul>
<p>Some details on Manager. This is by far the most complicated part of the
system. Which is funny, because I hadn&rsquo;t anticipated that when I started work
on PGXN (I&rsquo;d estimated half as many hours as for implementing the site). But
as I worked through design issues and wrote the code, the need for that
complexity became apparent &ndash; and not just because it&rsquo;s the only part that
offers authentication. <em>The main reason it&rsquo;s complex is so that no other part
needs to be.</em></p>
<p>Allow me to explain. People can upload almost anything as a distribution. So
long as the <code>META.json</code> adheres to <a href="https://pgxn.org/spec/">the spec</a>, the rest can be just about
anything. But the upload doesn&rsquo;t necessarily end up on the network unmodified.
Sure, if you follow the guidelines of the <a href="https://manager.pgxn.org/howto">HOWTO</a> what you uploaded will be
exactly what ends up on the network. But I didn&rsquo;t want to be that strict about
PGXN Manager would accept. So in addition to verifying the structure of your
<code>META.json</code> file, <a href="https://github.com/pgxn/pgxn-manager/blob/master/lib/PGXN/Manager/Distribution.pm" title="PGXN::Manager::Distribution on GitHub">PGXN::Manager::Distribution</a> also:</p>
<ul>
<li>
<p>Extracts the archive. If it&rsquo;s not a zip file, a zip archive is created from
the contents of the uploaded file. You can upload any kind of archive
readable by <a href="https://search.cpan.org/perldoc?Archive::Extract">Archive::Extract</a>. The currently supported formats are: <code>.tar</code>,
<code>.tar.gz</code>, <code>.gz</code>, <code>.Z</code>, <code>tar.bz2</code>, <code>.tbz</code>, <code>.bz2</code>, <code>.zip</code>, <code>.xz,</code>, <code>.txz</code>,
<code>.tar.xz</code> and <code>.lzma</code>. By always converting to a zip file, PGXN client apps
can be quite simple, not having to worry about the archive format.</p>
</li>
<li>
<p>Validates the <code>META.json</code>. This part isn&rsquo;t perfect, yet. I fixed a bug today
where it would die and return a 500 on a missing version number. It&rsquo;d
probably be worthwhile to adapt <a href="https://search.cpan.org/perldoc?CPAN::Meta::Validator">CPAN::Meta::Validator</a> to validate PGXN
<code>META.json</code> files at some point, both for Manager and for developers wanting
to validate before uploading.</p>
</li>
<li>
<p>Normalizes all version numbers in the <code>META.json</code> into <a href="https://semver.org/">semantic versions</a>.
You can specify the distribution version, prerequisite versions, and
extension versions as simple numbers and they&rsquo;ll be converted to semantic
versions. A version like &ldquo;1.20&rdquo;, for example, becomes &ldquo;1.2.0&rdquo;. See the
<a href="https://search.cpan.org/~dwheeler/SemVer-v0.2.0/lib/SemVer.pm#Usage">SemVer documentation</a> for details on how versions are normalized via the
<code>declare()</code> method. This normalization is done so that client applications
will get known valid semantic versions to compare when determining
dependencies. However, it&rsquo;s best that they be semantic versions to begin
with. Normalized versions will be written back to the archive <code>META.json</code>
file (with the &ldquo;generated_by&rdquo; key updated to reflect that PGXN Manager
regenerated the file). If no versions need validating, the archive
<code>META.json</code> will be left alone.</p>
</li>
<li>
<p>Makes sure the zip archive has a directory prefix named <code>&quot;$dist-$version/&quot;</code>.
If the archive has no directory prefix, or if the prefix is not
<code>&quot;$dist-$version/&quot;</code>, the archive is rewritten with that prefix. This ensures
that the archive will always extract into a directory with the same name as
the archive and not spray files all over your desktop when you unzip it.</p>
</li>
<li>
<p>Copies or writes out a new zip file named <code>&quot;$dist-$version.pgz&quot;</code>. Think of
<code>.pgz</code> as &ldquo;PostgreSQL Zip&rdquo; or, if you&rsquo;d rather, &ldquo;PGXN Zip&rdquo;. Either way, it&rsquo;s
just a zip archive.</p>
</li>
</ul>
<p>That processing done, with a good <code>META.json</code> and zip archive, the JSON,
username, and SHA1 of the zip archive are handed off to the database for more
processing. The <a href="https://github.com/pgxn/pgxn-manager/wiki/DB-API#add_distribution"><code>add_distribution()</code></a> database function does all the heavy
lifting here. It:</p>
<ul>
<li>
<p>Parses the JSON string, validates that all required keys are present, and
normalizes version numbers. Yes, this is redundant, but I don&rsquo;t think I need
to lecture the reads of this blog about database integrity. :-)</p>
</li>
<li>
<p>Creates a new metadata structure and stores all the required and many of the
optional meta spec keys, as well as the SHA1 of the distribution file, the
date, and the user&rsquo;s nickname.</p>
</li>
<li>
<p>Sets the &ldquo;release_status&rdquo; to &ldquo;stable&rdquo; if there was no status in the original
JSON.</p>
</li>
<li>
<p>Adds a &ldquo;provides&rdquo; section to the metadata if none was included in the
original JSON. In such a case, it assumes that the distribution contains one
extension and that it has the same name and version as the distribution
itself.</p>
</li>
<li>
<p>Validates that the uploading user is owner or co-owner of all provided
extensions. If no one is listed as owner of one or more included extensions,
the user will be assigned ownership. If the user is not owner or co-owner of
any included extensions, an exception will be thrown.</p>
</li>
<li>
<p>Records the distribution, extensions, and tags in the database.</p>
</li>
</ul>
<p>Once all this work is done, <code>add_distribution()</code> returns all the JSON that
needs to be written to the mirror. These files make up the &ldquo;index&rdquo; on the
network, and include:</p>
<ul>
<li>The <code>META.json</code> file (<a href="https://api.pgxn.org/mirror/dist/semver/0.2.1/META.json">example</a>). This file is derived from the <code>META.json</code>
included in the archive (<a href="https://api.pgxn.org/src/semver/semver-0.2.1/META.json">example</a>), but reflects all the normalization
changes and added keys outlined above.</li>
<li>A short JSON file summarizing all releases of the distribution
(<a href="https://api.pgxn.org/mirror/dist/semver.json">example</a>).</li>
<li>JSON files describing all releases of all extensions included in the
distribution (<a href="https://api.pgxn.org/mirror/extension/semver.json">example</a>).</li>
<li>A JSON file for the user, which includes data about all releases made by
that user (<a href="https://api.pgxn.org/mirror/user/theory.json">example</a>).</li>
<li>JSON files for each tag associated with the distribution. Each of these
files lists all the distributions the tag is associated with (<a href="https://api.pgxn.org/mirror/tag/datatype.json">example</a>).</li>
</ul>
<p>It also returns JSON for network statistics files. These are updated every
time a new release is uploaded:</p>
<ul>
<li><a href="https://api.pgxn.org/mirror/stats/dist.json"><code>dist.json</code></a> lists the 56 most recent releases and has a count of all
distributions and of releases of those distributions.</li>
<li><a href="https://api.pgxn.org/mirror/stats/extension.json"><code>extension.json</code></a> lists the 56 most recent extension releases and a count
of all extensions on the network.</li>
<li><a href="https://api.pgxn.org/mirror/stats/user.json"><code>user.json</code></a> lists of the 56 most prolific users (based on the number of
distributions) and a count of distributions and releases for each.</li>
<li><a href="https://api.pgxn.org/mirror/stats/tag.json"><code>tag.json</code></a> lists the 56 most popular tags (measured by the number of
distributions they&rsquo;re associated with) and a count of all tags on the
network.</li>
<li><a href="https://api.pgxn.org/mirror/stats/summary.json"><code>summary.json</code></a> has basic summary information about the network, which is
just counts of distributions, releases, extensions, users, tags, and
mirrors.</li>
</ul>
<p>If you think that&rsquo;s a lot of data to be updated, you&rsquo;re right! But since
releases are relatively infrequent (a couple a day at the moment), it&rsquo;s best
to generate all this stuff as static files that are <code>rsync</code>ed to all mirrors.
In this way, <em>any</em> mirror can function as a very simple, lightweight REST API.
And indeed, that&rsquo;s just how the planned <a href="https://github.com/pgxn/pgxn-client/" title="Fork PGXN::Client on GitHub">PGXN client</a> will behave. The
<a href="https://github.com/pgxn/www-pgxn/" title="Fork WWW::PGXN on GitHub">WWW::PGXN</a> already provides the interface it will use.</p>
<p>I guess that was a lot of information. Let this be a reference document for
interested hackers, then. The core functionality is all there, but there&rsquo;s a
<a href="https://github.com/pgxn/pgxn-manager/issues/">lot more to be done</a>:</p>
<ul>
<li>Add a UI for users to assign extensions to co-owners (when more than one
user is allowed to upload an extension) and to transfer ownership (<a href="https://github.com/pgxn/pgxn-manager/issues/6">#6</a>).</li>
<li>Add a user administration interface (<a href="https://github.com/pgxn/pgxn-manager/issues/7">#7</a>).</li>
<li>Add a UI where admins can transfer ownership of extensions when an owner
disappears (<a href="https://github.com/pgxn/pgxn-manager/issues/8">#8</a>).</li>
<li>Add a callback architecture to trigger other actions on successful upload.
These would include tweeting new releases (currently just handled in the
controller) and running arbitrary command-line utilities (such as syncing
the PGXN API server so it can be up-to-date as quickly as possible) (<a href="https://github.com/pgxn/pgxn-manager/issues/9">#9</a>).</li>
<li>Add support for &ldquo;instant mirroring&rdquo; via <a href="https://search.cpan.org/perldoc?File::Rsync::Mirror::Recent"><code>rrr</code></a> (<a href="https://github.com/pgxn/pgxn-manager/issues/10">#10</a>).</li>
<li>Add an Atom feed for recent releases (basically a dupe of the distribution
stats file) (<a href="https://github.com/pgxn/pgxn-manager/issues/17">#17</a>).</li>
<li>Add a command-line interface to the API. The HTTP server is written in such
a way that it can act as an API serving JSON, but it&rsquo;s not well-tested and
nothing uses it yet (that I&rsquo;m aware of) (<a href="https://github.com/pgxn/pgxn-client/issues/1">#1</a>).</li>
<li>Add UI for re-indexing a distribution (<a href="https://github.com/pgxn/pgxn-manager/issues/11">#11</a>).</li>
<li>Add ability to delete distributions (<a href="https://github.com/pgxn/pgxn-manager/issues/12">#12</a>)?</li>
<li>Add an email gateway so that email addressed to <code>$nickname@pgxn.org</code> will be
forwarded to a user&rsquo;s actual address. This would allow us to remove literal
email addresses from the JSON files and the site (the site obfuscates them,
but still&hellip;). Anyone got some good postfix chops for this? The <a href="https://github.com/pgxn/pgxn-manager/blob/master/sql/03-users.sql"><code>users</code>
table</a> is quite simple (<a href="https://github.com/pgxn/pgxn-manager/issues/13">#13</a>).</li>
</ul>
<p>Want to help out? Fork <a href="https://manager.pgxn.org/" title="For PGXN::Manager on GitHub">PGXN Manager</a> and have at it. Hell, at this point
I&rsquo;d really appreciate a code review, as I&rsquo;m pretty sure there&rsquo;s only been one
set of eyes on this code so far.</p>
<p>Next week, I plan to blog about</p>
<ul>
<li>Project status: where the hours went and where they&rsquo;re going.</li>
<li><a href="https://tools.ietf.org/html/draft-gregorio-uritemplate-04">URI templates</a> and how they provide a method index for the mirror API</li>
<li>Mirrors and mirroring via <code>rsync</code></li>
<li><a href="https://api.pgxn.org/">PGXN API</a>: how it provides a superset of the mirror API, including
full-text search.</li>
<li><a href="https://pgxn.org/">PGXN Site</a>: how it&rsquo;s just a very thing wrapper over the API (but can
read the API directly from the file system!)</li>
<li>Plans for the PGXN client</li>
</ul>
<p>But given how these things go, and how I need to start writing mirror API and
API server documentation, it might take me a longer to get to them all.</p>
]]></content></entry><entry><id>https://blog.pgxn.org/post/4809393274</id><title type="html">What Media Types for Browsing?</title><link rel="alternate" type="text/html" href="https://blog.pgxn.org/2011/safe-media-types/"/><updated>2026-10-07T16:13:48Z</updated><published>2011-04-21T16:56:43Z</published><author><name>David E. Wheeler</name></author><category scheme="https://blog.pgxn.org/tags" term="mime" label="MIME"/><category scheme="https://blog.pgxn.org/tags" term="media-type" label="Media Type"/><category scheme="https://blog.pgxn.org/tags" term="mime-type" label="MIME Type"/><category scheme="https://blog.pgxn.org/tags" term="browse" label="Browse"/><category scheme="https://blog.pgxn.org/tags" term="source" label="Source"/><category scheme="https://blog.pgxn.org/tags" term="user-submitted" label="User Submitted"/><category scheme="https://blog.pgxn.org/tags" term="content" label="Content"/><category scheme="https://blog.pgxn.org/tags" term="text" label="Text"/><category scheme="https://blog.pgxn.org/tags" term="plain-text" label="Plain Text"/><category scheme="https://blog.pgxn.org/tags" term="html" label="HTML"/><category scheme="https://blog.pgxn.org/tags" term="c" label="C"/><category scheme="https://blog.pgxn.org/tags" term="json" label="JSON"/><summary type="html"><![CDATA[<p>Thanks to a comment from &ldquo;Anon,&rdquo; I&rsquo;m auditing the media types used to serve
files from the &ldquo;Browse&rdquo; links on the site. The browse interface is managed by
<a href="https://search.cpan.org/perldoc?Plack::App::Directory">Plack::App::Directory</a>, which gets its media type mapping from <a href="https://github.com/miyagawa/Plack/blob/master/lib/Plack/MIME.pm">this file</a>.
What PGXN::API does is simply change the mappings of some of those types to
<code>text/plain</code>. As of <a href="https://github.com/pgxn/pgxn-api/commit/0157c18cbc0835b627fa2e42b2433337f5f3fff5">this commit</a>, files are served as plain text if:</p>
<ul>
<li>The strings &ldquo;html&rdquo;, &ldquo;x-c&rdquo;, &ldquo;xml&rdquo;, &ldquo;calendar&rdquo;, or &ldquo;vcard&rdquo; appear in the
default media type; or</li>
<li>The file name extension is one of <code>.bat</code>, <code>.css</code>, <code>.eml</code>, <code>.js</code>, <code>.json</code>,
<code>.mime</code>, or <code>.swf</code>.</li>
</ul>
<p>Are there other <a href="https://github.com/miyagawa/Plack/blob/master/lib/Plack/MIME.pm">media types</a> that should be disabled for safe
browsing of user-submitted content?</p>]]></summary><content type="html" xml:base="https://blog.pgxn.org/" xml:space="preserve"><![CDATA[<p>Thanks to a comment from &ldquo;Anon,&rdquo; I&rsquo;m auditing the media types used to serve
files from the &ldquo;Browse&rdquo; links on the site. The browse interface is managed by
<a href="https://search.cpan.org/perldoc?Plack::App::Directory">Plack::App::Directory</a>, which gets its media type mapping from <a href="https://github.com/miyagawa/Plack/blob/master/lib/Plack/MIME.pm">this file</a>.
What PGXN::API does is simply change the mappings of some of those types to
<code>text/plain</code>. As of <a href="https://github.com/pgxn/pgxn-api/commit/0157c18cbc0835b627fa2e42b2433337f5f3fff5">this commit</a>, files are served as plain text if:</p>
<ul>
<li>The strings &ldquo;html&rdquo;, &ldquo;x-c&rdquo;, &ldquo;xml&rdquo;, &ldquo;calendar&rdquo;, or &ldquo;vcard&rdquo; appear in the
default media type; or</li>
<li>The file name extension is one of <code>.bat</code>, <code>.css</code>, <code>.eml</code>, <code>.js</code>, <code>.json</code>,
<code>.mime</code>, or <code>.swf</code>.</li>
</ul>
<p>Are there other <a href="https://github.com/miyagawa/Plack/blob/master/lib/Plack/MIME.pm">media types</a> that should be disabled for safe
browsing of user-submitted content?</p>
]]></content></entry><entry><id>https://blog.pgxn.org/post/3099288750</id><title type="html">PGXN API RFC</title><link rel="alternate" type="text/html" href="https://blog.pgxn.org/2011/pgxn-api-rfc/"/><updated>2026-10-07T16:13:48Z</updated><published>2011-02-04T04:05:33Z</published><author><name>David E. Wheeler</name></author><category scheme="https://blog.pgxn.org/tags" term="search" label="Search"/><category scheme="https://blog.pgxn.org/tags" term="api" label="API"/><category scheme="https://blog.pgxn.org/tags" term="mirror" label="Mirror"/><category scheme="https://blog.pgxn.org/tags" term="json" label="JSON"/><category scheme="https://blog.pgxn.org/tags" term="cpan" label="CPAN"/><category scheme="https://blog.pgxn.org/tags" term="metacpan" label="metacpan"/><category scheme="https://blog.pgxn.org/tags" term="javascript" label="JavaScript"/><category scheme="https://blog.pgxn.org/tags" term="application" label="Application"/><summary type="html"><![CDATA[<p>Things slowed up a bit over the last couple of months, I admit. There are any
number of reasons for that, not the least were the intrusion of the holidays
and a <a href="https://www.designsceneapp.com/">little side project</a> I&rsquo;ve been hacking on after-hours (and sometimes
during-hours). But I&rsquo;m ramping things up again now and need <em>your</em> feedback on
my current plans. Here&rsquo;s what I&rsquo;m working on: the search site.</p>
<h2 id="search-sites-and-apis">Search Sites and APIs</h2>
<p>Well, sort of. First of all, I&rsquo;ve decided that the &ldquo;search site&rdquo; should not be
a separate thing. The <a href="https://pgxn.org/">main site</a> will be the search site. This is following
the example of <a href="https://openjsan.org">JSAN</a>, as well as feedback from <a href="https://search.cpan.org/~gbarr/">Graham Barr</a>, who created and
maintains <a href="https://search.cpan.org/">CPAN Search</a>. Apparently people are often confused that
<a href="https://search.cpan.org/">search.cpan.org</a> is separate from <a href="https://www.cpan.org">www.cpan.org</a>. No point in
adding in confusion from the beginning. And besides, now that the PGXN
fund-raising <a href="https://blog.pgxn.org/post/2063677299/goooooooaaaal">is over</a>, I don&rsquo;t know what else would go on the home page.</p>]]></summary><content type="html" xml:base="https://blog.pgxn.org/" xml:space="preserve"><![CDATA[<p>Things slowed up a bit over the last couple of months, I admit. There are any
number of reasons for that, not the least were the intrusion of the holidays
and a <a href="https://www.designsceneapp.com/">little side project</a> I&rsquo;ve been hacking on after-hours (and sometimes
during-hours). But I&rsquo;m ramping things up again now and need <em>your</em> feedback on
my current plans. Here&rsquo;s what I&rsquo;m working on: the search site.</p>
<h2 id="search-sites-and-apis">Search Sites and APIs</h2>
<p>Well, sort of. First of all, I&rsquo;ve decided that the &ldquo;search site&rdquo; should not be
a separate thing. The <a href="https://pgxn.org/">main site</a> will be the search site. This is following
the example of <a href="https://openjsan.org">JSAN</a>, as well as feedback from <a href="https://search.cpan.org/~gbarr/">Graham Barr</a>, who created and
maintains <a href="https://search.cpan.org/">CPAN Search</a>. Apparently people are often confused that
<a href="https://search.cpan.org/">search.cpan.org</a> is separate from <a href="https://www.cpan.org">www.cpan.org</a>. No point in
adding in confusion from the beginning. And besides, now that the PGXN
fund-raising <a href="https://blog.pgxn.org/post/2063677299/goooooooaaaal">is over</a>, I don&rsquo;t know what else would go on the home page.</p>
<p>The other thing that&rsquo;s happened is, just as I was getting my butt in gear on
this stuff, a new CPAN search site came to my attention, <a href="https://search.metacpan.org/">μετα CPAN</a>. This is
an interesting project. What they did instead of creating a monolithic HTML
search site is to create a <a href="https://github.com/CPAN-API/cpan-api/wiki/API-docs">simple API</a> that serves nothing but JSON. It has
search and displays metadata for CPAN objects (distributions, maintainers,
modules, etc.). The search site, then, is not really a site at all, but a pure
JavaScript application. Once you load it, it just uses the API server to get
all the data. There are a few tricks server-side to proxy the API server so as
to avoid cross-site scripting issues. But otherwise it just works in the
browser.</p>
<p>Now I&rsquo;m not sure I&rsquo;ll do the same thing, exactly, but there&rsquo;s a lot of appeal
in creating a RESTful API server that&rsquo;s independent of the search site, and
then building the search site to use it. It also has the advantage of being
useful for other projects to just use. Want to create a PGXN search widget for
your blog? Yeah, there&rsquo;s an API for that.</p>
<h2 id="a-super-restful-directory">A Super RESTful Directory</h2>
<p>Of course, thanks to the &ldquo;RESTful Directory&rdquo; design for the mirrors (described
<a href="https://blog.pgxn.org/post/988613682/restful-directory" title="A RESTful Directory">here</a> and revised <a href="https://blog.pgxn.org/post/1138292188/arch-and-extension-json" title="Architecture, New Extension JSON Format">here</a>), any mirror is a lightweight API already.
There&rsquo;s a <em>lot</em> of metadata one can get just from the static JSON files it
generates. The design is flexible&ndash;but designed with a command-line client in
mind. As such, many commands executed in a command-line client would likely
requires multiple requests to a mirror. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">&gt;</span> install pgtap
</span></span></code></pre></div><p>This would request <code>/by/extension/pgtap.json</code> from the server. It would then
parse that file and see that the latest stable version of pgTAP is in the
distribution &ldquo;pgTAP&rdquo; at version &ldquo;0.25.0&rdquo;. So it would then download
<code>/dist/pgTAP-0.25.0.pgz</code> to install.</p>
<p>This is great for a command-line client, but wouldn&rsquo;t be so great for a search
site to be responsive. Ideally, a site should send a single request to get all
the data it needs for a particular page.</p>
<p>So here&rsquo;s what I&rsquo;m thinking for a PGXN API server: It will offer a superset of
the functionality of any other PGXN mirror. That is, all the JSON files in a
mirror will be present, but many of them will have more information than they
would on other mirrors. And then, of course, there will be other URIs to offer
additional API calls.</p>
<h3 id="details">Details</h3>
<p>So what does that look like? Let&rsquo;s take the pgTAP distribution, which I
released on PGXN earlier this week. To find the pgTAP distribution, one
requests:</p>
  <blockquote>
    <p><a href="https://master.pgxn.org/by/dist/pgTAP.json"><code>/by/dist/pgTAP.json</code></a></p>
  </blockquote>
<p>From that, one can see that the latest table release is 0.25.0, and so one can
then request</p>
  <blockquote>
    <p><a href="https://master.pgxn.org/dist/pgTAP/pgTAP-0.25.0.json"><code>/dist/pgTAP/pgTAP-0.25.0.json</code></a></p>
  </blockquote>
<p>to get all the metadata for that particular release. What I propose, to avoid
the two requests, is to include the contents of the second file in the first.
That would then have all the data necessary to generate the <a href="https://theory.github.com/pgxn/pgtapdist.html">pgTAP
distribution page</a> on the PGXN site.</p>
<p>The API would offer similar supersets of data for the <a href="https://master.pgxn.org/by/extension/pgtap.json">extension</a> , <a href="https://master.pgxn.org/by/owner/theory.json">owner</a> ,
and <a href="https://master.pgxn.org/by/tag/testing.json">tag</a> metadata files, to have the data necessary for the design of the
corresponding <a href="https://theory.github.com/pgxn/pgtap.html">extension</a>, <a href="https://theory.github.com/pgxn/theory.html">owner</a> and tag layouts of the site.</p>
<h3 id="additional-resources">Additional Resources</h3>
<p>In addition to adding metadata to the existing mirrored JSON files, there
would be other resources available for request from the API server. They would
include:</p>
<ul>
<li>
<p>Extension Documentation. Each distribution may include documentation for
included extensions in the <code>doc</code> subdirectory. These will go under the
directory for a specific distribution such as
<code>/dist/pgTAP/pgTAP-0.35.0/doc/pgtap.html</code>. The latest version of each
document would also be available under <code>/by/extension</code>, as in
<code>/by/extension/pgtap.html</code>. This requires that the documentation file have
the same base name as the extension file itself.</p>
</li>
<li>
<p>Other documentation. I&rsquo;d like to support arbitrary documentation, such as
for included binary executables, HOWTOs, etc. The canonical copies will go
under the versioned distribution URL, of course, but I&rsquo;m not sure about
permalinks. That might require an extension of the <a href="https://pgxn.org/meta/spec.txt">Meta Spec</a>; I haven&rsquo;t
quite figured that out, yet.</p>
</li>
<li>
<p>Source code. There will be an interface to browse an unpacked copy of any
distribution as plain text. This will be under <code>/src</code>, as in
<code>/src/pgTAP/pgTAP-0.35.0/</code>.</p>
</li>
</ul>
<h3 id="search-api">Search API</h3>
<p>Of course. This is the big one, really. I think it makes sense to have the
<code>/by</code> URI respond to search requests. Thus, a request for</p>
<pre tabindex="0"><code>/by?q=testing
</code></pre><p>would search everything. If you only want to search a certain category of
object, you&rsquo;d hit the appropriate URI:</p>
<pre tabindex="0"><code>/by/dist?q=tap
/by/owner?q=clochard
/by/tag?q=test
/by/extension?q=gis
</code></pre><p>The nice thing about this is that it retains the existing entity URLs. The
directory level determines which entities you get.</p>
<h2 id="your-thoughts">Your Thoughts?</h2>
<p>So that&rsquo;s my thinking on the search API. I&rsquo;m going to start hacking on it in
earnest tomorrow, and perhaps next week I can get a very early version out
(basically just another mirror to start with).</p>
<p>But what do you think? Seem like a sane approach? Am I missing anything
obvious or doing anything clearly stupid? Please let me know in the comments!</p>
]]></content></entry><entry><id>https://blog.pgxn.org/post/1352326020</id><title type="html">First Upload to PGXN</title><link rel="alternate" type="text/html" href="https://blog.pgxn.org/2010/first-upload/"/><updated>2026-10-07T16:13:48Z</updated><published>2010-10-19T17:46:44Z</published><author><name>David E. Wheeler</name></author><category scheme="https://blog.pgxn.org/tags" term="pgxn" label="PGXN"/><category scheme="https://blog.pgxn.org/tags" term="upload" label="Upload"/><category scheme="https://blog.pgxn.org/tags" term="distribution" label="Distribution"/><category scheme="https://blog.pgxn.org/tags" term="metadata" label="Metadata"/><category scheme="https://blog.pgxn.org/tags" term="json" label="JSON"/><category scheme="https://blog.pgxn.org/tags" term="pair" label="Pair"/><summary type="html"><![CDATA[<p>Last night I deployed <a href="https://github.com/theory/pgxn-manager/">PGXN::Manager</a> v0.2.1 and uploaded the first
distribution, <a href="https://master.pgxn.org/dist/pair/">pair</a>. If you follow that link you&rsquo;ll see three files:</p>
<ul>
<li><a href="https://master.pgxn.org/dist/pair/pair-0.1.0.json"><code>pair-0.1.0.json</code></a> is metadata file generated by PGXN manager to describe
the distribution. Most of its data is taken from the <a href="https://github.com/theory/kv-pair/blob/master/META.json"><code>META.json</code></a> included
in the uploaded zip file, but a few keys, like &ldquo;sha1&rdquo;, are generated, and
others, like &ldquo;release_status&rdquo; are added if they&rsquo;re not in the included
<code>META.json</code>.</li>
<li><a href="https://master.pgxn.org/dist/pair/pair-0.1.0.readme">pair-0.0.1.readme</a> is a copy of the <code>README</code> file distributed with pair.</li>
<li><a href="https://master.pgxn.org/dist/pair/pair-0.1.0.pgz">pair-0.1.0.pgz</a> is the distribution zip file. That&rsquo;s the file you want to
download, unzip, and build and install.</li>
</ul>
<p>Following the spec I previously <a href="https://blog.pgxn.org/post/988613682/restful-directory">wrote up</a>, there are a number of other files
that get created when a new distribution is uploaded to PGXN. For the pair
extension, we got:</p>]]></summary><content type="html" xml:base="https://blog.pgxn.org/" xml:space="preserve"><![CDATA[<p>Last night I deployed <a href="https://github.com/theory/pgxn-manager/">PGXN::Manager</a> v0.2.1 and uploaded the first
distribution, <a href="https://master.pgxn.org/dist/pair/">pair</a>. If you follow that link you&rsquo;ll see three files:</p>
<ul>
<li><a href="https://master.pgxn.org/dist/pair/pair-0.1.0.json"><code>pair-0.1.0.json</code></a> is metadata file generated by PGXN manager to describe
the distribution. Most of its data is taken from the <a href="https://github.com/theory/kv-pair/blob/master/META.json"><code>META.json</code></a> included
in the uploaded zip file, but a few keys, like &ldquo;sha1&rdquo;, are generated, and
others, like &ldquo;release_status&rdquo; are added if they&rsquo;re not in the included
<code>META.json</code>.</li>
<li><a href="https://master.pgxn.org/dist/pair/pair-0.1.0.readme">pair-0.0.1.readme</a> is a copy of the <code>README</code> file distributed with pair.</li>
<li><a href="https://master.pgxn.org/dist/pair/pair-0.1.0.pgz">pair-0.1.0.pgz</a> is the distribution zip file. That&rsquo;s the file you want to
download, unzip, and build and install.</li>
</ul>
<p>Following the spec I previously <a href="https://blog.pgxn.org/post/988613682/restful-directory">wrote up</a>, there are a number of other files
that get created when a new distribution is uploaded to PGXN. For the pair
extension, we got:</p>
<ul>
<li>
<p><a href="https://master.pgxn.org/by/dist/pair.json"><code>by/dist/pair.json</code></a>, which will be updated with information for every
release of the &ldquo;pair&rdquo; distribution.</p>
</li>
<li>
<p><a href="https://master.pgxn.org/by/extension/pair.json"><code>by/extension/pair.json</code></a>, which will be updated for every upload
containing the &ldquo;pair&rdquo; extension.</p>
</li>
<li>
<p><a href="https://master.pgxn.org/by/owner/theory.json"><code>by/owner/theory.json</code></a>, which will be updated every time I upload a
distribution.</p>
</li>
<li>
<p>Files for every tag listed in the <a href="https://master.pgxn.org/dist/pair/pair-0.1.0.json">metadata</a> are also
created. In this case, that includes:</p>
<ul>
<li><a href="https://master.pgxn.org/by/tag/key%20value%20pair.json"><code>by/tag/key value pair.json</code></a></li>
<li><a href="https://master.pgxn.org/by/tag/key%20value.json"><code>by/tag/key value.json</code></a></li>
<li><a href="https://master.pgxn.org/by/tag/ordered%20pair.json"><code>by/tag/ordered pair.json</code></a></li>
<li><a href="https://master.pgxn.org/by/tag/pair.json"><code>by/tag/pair.json</code></a></li>
<li><a href="https://master.pgxn.org/by/tag/variadic%20function.json"><code>by/tag/variadic function.json</code></a></li>
</ul>
<p>Each of these files will be updated every time a distribution is uploaded
containing the relevant tag.</p>
</li>
</ul>
<p>You&rsquo;ll soon be able to upload your own extension distributions to PGXN. If
you&rsquo;re interested, please subscribe to the <a href="https://groups.google.com/group/pgxn-users">mail list</a>, where I&rsquo;ll soon be
inviting folks to get an account and start uploading.</p>
<p>But first, a blog post on how to create a PGXN-friendly distribution archive.
Coming up shortly.</p>
]]></content></entry><entry><id>https://blog.pgxn.org/post/988613682</id><title type="html">A RESTful Directory</title><link rel="alternate" type="text/html" href="https://blog.pgxn.org/2010/restful-directory/"/><updated>2026-10-07T16:13:48Z</updated><published>2010-08-21T18:52:00Z</published><author><name>David E. Wheeler</name></author><category scheme="https://blog.pgxn.org/tags" term="mirror" label="Mirror"/><category scheme="https://blog.pgxn.org/tags" term="directory" label="Directory"/><category scheme="https://blog.pgxn.org/tags" term="structure" label="Structure"/><category scheme="https://blog.pgxn.org/tags" term="meta" label="Meta"/><category scheme="https://blog.pgxn.org/tags" term="metadata" label="Metadata"/><category scheme="https://blog.pgxn.org/tags" term="json" label="JSON"/><category scheme="https://blog.pgxn.org/tags" term="api" label="API"/><category scheme="https://blog.pgxn.org/tags" term="scalability" label="Scalability"/><category scheme="https://blog.pgxn.org/tags" term="rest" label="REST"/><summary type="html"><![CDATA[Following my post outlining a possible network <a href="https://blog.pgxn.org/post/954535657/thoughts-on-the-network-directory-structure">directory structure</a>,
<a href="https://plasmasturm.org/">Aristotle Pagaltzis</a> saw fit to bug me via email about a different approach.
I couldn&rsquo;t understand WTF he was talking about until today. Then it lit my
brain on fire. As a result, I now think that there is a much better way to
organize the metadata files for the PGXN &ndash; one that happens not to include
any symbolic links (which is something that <a href="https://search.cpan.org/~andk/">Andreas König</a> has been flagging,
via email, as a possible bottleneck).]]></summary><content type="html" xml:base="https://blog.pgxn.org/" xml:space="preserve"><![CDATA[<p>Following my post outlining a possible network <a href="https://blog.pgxn.org/post/954535657/thoughts-on-the-network-directory-structure">directory structure</a>,
<a href="https://plasmasturm.org/">Aristotle Pagaltzis</a> saw fit to bug me via email about a different approach.
I couldn&rsquo;t understand WTF he was talking about until today. Then it lit my
brain on fire. As a result, I now think that there is a much better way to
organize the metadata files for the PGXN &ndash; one that happens not to include
any symbolic links (which is something that <a href="https://search.cpan.org/~andk/">Andreas König</a> has been flagging,
via email, as a possible bottleneck).</p>
<p>First, the <code>/dist</code> directory will be the same as before. Releases of pgTAP
would be in:</p>
<pre tabindex="0"><code>dist/p/pg/pgtap/pgtap-0.23.pgz
dist/p/pg/pgtap/pgtap-0.23.json
dist/p/pg/pgtap/pgtap-0.23.readme
dist/p/pg/pgtap/pgtap-0.24.pgz
dist/p/pg/pgtap/pgtap-0.24.json
dist/p/pg/pgtap/pgtap-0.24.readme
dist/p/pg/pgtap/pgtap-0.25.pgz
dist/p/pg/pgtap/pgtap-0.25.json
dist/p/pg/pgtap/pgtap-0.25.readme
</code></pre><p>The only change is that the <code>pgtap.json</code> symlink is gone.</p>
<p>Now, the new stuff. In the root directory will be a file, <code>index.json</code>, that
contains templates for URIs. It will look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;dist&#34;</span><span class="p">:</span>   <span class="s2">&#34;/dist/$a/$ab/$dist/$dist-$version.pgz&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;readme&#34;</span><span class="p">:</span> <span class="s2">&#34;/dist/$a/$ab/$dist/$dist-$version.readme&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;meta&#34;</span><span class="p">:</span>   <span class="s2">&#34;/dist/$a/$ab/$dist/$dist-$version.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;by-dist&#34;</span><span class="p">:</span>      <span class="s2">&#34;/by/dist/$a/$ab/$dist.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;by-extension&#34;</span><span class="p">:</span> <span class="s2">&#34;/by/extension/$a/$ab/$extension.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;by-owner&#34;</span><span class="p">:</span>     <span class="s2">&#34;/by/owner/$a/$ab/$owner.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;by-manager&#34;</span><span class="p">:</span>   <span class="s2">&#34;/by/manager/$a/$ab/$manager.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The PGXN client will always fetch this file before it does anything else,
because the file tells it how to find stuff. The advantage here is that the
client doesn&rsquo;t have to know anything about how the directory is actually
organized, just what the template variables might be. They are:</p>
<ul>
<li><code>$dist</code>: A <a href="https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec#name">distribution name</a></li>
<li><code>$version</code>: A <a href="https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec#version">version number</a></li>
<li><code>$extension</code>: An <a href="https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec#provides">extension name</a></li>
<li><code>$owner</code>: An <a href="https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec#owner">owner&rsquo;s name</a></li>
<li><code>$manager</code>: A release manager&rsquo;s name (managers are the people who upload
distributions to PGXN)</li>
<li><code>$a</code>: The first letter of a distribution, extension, owner, or manager name.</li>
<li><code>$ab</code>: The first two letters of a distribution, extension, owner, or manager
name.</li>
</ul>
<p>I&rsquo;m not thrilled about using prefix-staggering to avoid having too many files
in a directory. But the truth is that this approach allows me to punt. I could
also make sure the client supports, for example, <code>$bc</code> and <code>$cd</code>, so that one
could stagger things differently. And then the nice thing is that I don&rsquo;t have
to use those at all. The templates will tell the client exactly how to
construct the URIs for things, and the templates needn&rsquo;t include those
staggering variables if they&rsquo;re not appropriate. The client won&rsquo;t care because
it will have no built-in knowledge of how things are organized. It will have
to find out from <code>index.json</code>.</p>
<p>From the URI templates, you can now see where the other metadata will be
stored. For extension names, a hypothetical pgTAP distribution with two
extensions will have a JSON file for each extension:</p>
<pre tabindex="0"><code>/by/extension/p/pg/pgtap.json
/by/extension/s/sc/schematap.json
</code></pre><p>The <code>pgtap.json</code> file will look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;stable&#34;</span><span class="err">:</span>   <span class="s2">&#34;0.25.0&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;testing&#34;</span><span class="err">:</span>  <span class="s2">&#34;0.26.0b1&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;unstable&#34;</span><span class="err">:</span> <span class="s2">&#34;0.30.0u&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;versions&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.26.0b1&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;dist&#34;</span><span class="p">:</span> <span class="s2">&#34;pgtap&#34;</span><span class="p">,</span> <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.26.0b1&#34;</span><span class="p">,</span> <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;testing&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.30.0u&#34;</span><span class="p">:</span>  <span class="p">{</span> <span class="nt">&#34;dist&#34;</span><span class="p">:</span> <span class="s2">&#34;pgtap&#34;</span><span class="p">,</span> <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.30.0u&#34;</span><span class="p">,</span>  <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;unstable&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.25.0&#34;</span><span class="p">:</span>   <span class="p">{</span> <span class="nt">&#34;dist&#34;</span><span class="p">:</span> <span class="s2">&#34;pgtap&#34;</span><span class="p">,</span> <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.25.0&#34;</span><span class="p">,</span>   <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;stable&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.24.0&#34;</span><span class="p">:</span>   <span class="p">{</span> <span class="nt">&#34;dist&#34;</span><span class="p">:</span> <span class="s2">&#34;pgtap&#34;</span><span class="p">,</span> <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.24.0&#34;</span><span class="p">,</span>   <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;stable&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.25.0&#34;</span><span class="p">:</span>   <span class="p">{</span> <span class="nt">&#34;dist&#34;</span><span class="p">:</span> <span class="s2">&#34;pgtap&#34;</span><span class="p">,</span> <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.23.0&#34;</span><span class="p">,</span>   <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;stable&#34;</span>  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Right at the top, it would always list the most recent stable, testing, and
unstable version number, and then it would have a list metadata for all
versions. Said metadata would include the associated distribution name,
version, and release status.</p>
<p>Here&rsquo;s how it would work. Say I ask the client to install pgtap:</p>
<pre tabindex="0"><code>PGXN&gt; install extension pgtap
</code></pre><p>The client would first fetch <code>/index.json</code>, then look for the URI template for
&ldquo;by-extension&rdquo;, which is <code>/by/extension/$a/$ab/$extension.json</code>. Filling in
the template, it would know to request <code>/by/extension/p/pg/pgtap.json</code>. With
that file, it would see that the most recent stable version is in the &ldquo;pgtap&rdquo;
distribution version 0.25.0. Using the <code>dist</code> URI template, which is
<code>/dist/$a/$ab/$dist-$version.pgz</code>, it would then fetch
<code>/dist/p/pg/pgtap/pgtap-0.25.0.pgz</code>.</p>
<p>The advantage here is that there are no symbolic links and no knowledge of the
directory structure built into clients. The client just knows to fetch
<code>/index.json</code> and then to use the templates in that file to fetch other
information. That&rsquo;s the whole interface. Very <a href="https://en.wikipedia.org/wiki/REST">REST</a>ful.</p>
<p>The structure of the other <code>/by</code> files would be similar. For</p>
<pre><code>PGXN&gt; install dist pgtap
</code></pre>
<p>the client would use the &ldquo;by-dist&rdquo; URI template to construct the URL
<code>/by/dist/p/pg/pgtap.json</code>. That file would have something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;stable&#34;</span><span class="err">:</span>   <span class="s2">&#34;0.25.0&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;testing&#34;</span><span class="err">:</span>  <span class="s2">&#34;0.26.0b1&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;unstable&#34;</span><span class="err">:</span> <span class="s2">&#34;0.30.0u&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;versions&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.26.0b1&#34;</span><span class="p">:</span> <span class="s2">&#34;testing&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.30.0u&#34;</span><span class="p">:</span>  <span class="s2">&#34;unstable&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.25.0&#34;</span><span class="p">:</span>   <span class="s2">&#34;stable&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.24.0&#34;</span><span class="p">:</span>   <span class="s2">&#34;stable&#34;</span> <span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0.23.0&#34;</span><span class="p">:</span>   <span class="s2">&#34;stable&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>So then the client would know that &ldquo;0.25.0&rdquo; was the most recent version, and
use the <code>dist</code> URI template to request <code>/dist/p/pg/pgtap/pgtap-0.25.0.pgz</code>.</p>
<p>If The client command had been:</p>
<pre tabindex="0"><code>PGXN&gt; readme dist pgtap
</code></pre><p>It would use the <code>readme</code> URI template. And the command:</p>
<pre tabindex="0"><code>PGXN&gt; meta dist pgtap
</code></pre><p>Would use the <code>meta</code> URI template to fetch the metadata for the distribution.</p>
<p>If the client had requested a specific version:</p>
<pre tabindex="0"><code>PGXN&gt; install dist 0.23.0
</code></pre><p>It could either use the <code>by-dist</code> URI template to download the list of all
versions to see if 0.23.0 was valid, or just use the <code>dist</code> URI template to
try to download the distribution itself.</p>
<p>And finally, the owner and manager JSON files, such as</p>
<pre tabindex="0"><code>/owner/t/th/theory.json
</code></pre><p>Would look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;full_name&#34;</span><span class="err">:</span> <span class="s2">&#34;David Wheeler&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;email&#34;</span><span class="err">:</span> <span class="s2">&#34;theory@pgxn.org&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;uri&#34;</span><span class="err">:</span> <span class="s2">&#34;https://justatheory.com&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;distributions&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;pgtap&#34;</span><span class="p">:</span> <span class="p">[</span> <span class="s2">&#34;0.25.0&#34;</span><span class="p">,</span> <span class="s2">&#34;0.24.0&#34;</span><span class="p">,</span> <span class="s2">&#34;0.23.0&#34;</span> <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;pair&#34;</span><span class="p">:</span> <span class="p">[</span> <span class="s2">&#34;0.2.0&#34;</span><span class="p">,</span> <span class="s2">&#34;0.1.0&#34;</span><span class="p">,</span> <span class="s2">&#34;0.0.5&#34;</span> <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>With that, the client can be asked to fetch metadata for a given owner name
and use it to figure out what distributions and versions the the owner, um,
owns. One could then fetch the metadata, readme, or distribution file for any
of those distributions and versions.</p>
<p>Overall, I think that this is a much better solution than I outlined
<a href="https://blog.pgxn.org/post/954535657/thoughts-on-the-network-directory-structure">before</a>. If only I could figure out something more
elegant that the prefix-staggering/hashing stuff, it would be just about
perfect.</p>
<p>Thoughts?</p>
]]></content></entry><entry><id>https://blog.pgxn.org/post/966989188</id><title type="html">RFC: The PGXN Metadata Specification</title><link rel="alternate" type="text/html" href="https://blog.pgxn.org/2010/meta-spec-rfc/"/><updated>2026-10-07T16:13:48Z</updated><published>2010-08-17T13:00:00Z</published><author><name>David E. Wheeler</name></author><category scheme="https://blog.pgxn.org/tags" term="meta" label="Meta"/><category scheme="https://blog.pgxn.org/tags" term="meta-spec" label="Meta Spec"/><category scheme="https://blog.pgxn.org/tags" term="metadata" label="Metadata"/><category scheme="https://blog.pgxn.org/tags" term="json" label="JSON"/><category scheme="https://blog.pgxn.org/tags" term="cpan-meta-spec" label="CPAN Meta Spec"/><category scheme="https://blog.pgxn.org/tags" term="david-golden" label="David Golden"/><category scheme="https://blog.pgxn.org/tags" term="version-numbers" label="Version Numbers"/><category scheme="https://blog.pgxn.org/tags" term="prerequisites" label="Prerequisites"/><category scheme="https://blog.pgxn.org/tags" term="license" label="License"/><summary type="html"><![CDATA[<p>I&rsquo;ve posted a draft of the &ldquo;PGXN distribution metadata specification,&rdquo; or
<a href="https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec">PGXN Meta Spec</a>.&quot; This document specifies the structure and format of the
<code>META.json</code> file that PGXN will require in every distribution. In fact, this
is the <em>only</em> required file in a distribution. Its job is to describe the
distribution, its extensions, and its dependencies, among other things. This
file is key to the whole thing.</p>
<p>To create it, I&rsquo;ve ported the <a href="https://search.cpan.org/perldoc?CPAN::Meta::Sepc">CPAN Meta Spec</a>, version 2, but with all
deprecated fields removed, and some of the more complex stuff taken out. I
also made a couple of the &ldquo;required&rdquo; fields &ldquo;optional.&rdquo; At its simplest, the
file might look something like this:</p>]]></summary><content type="html" xml:base="https://blog.pgxn.org/" xml:space="preserve"><![CDATA[<p>I&rsquo;ve posted a draft of the &ldquo;PGXN distribution metadata specification,&rdquo; or
<a href="https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec">PGXN Meta Spec</a>.&quot; This document specifies the structure and format of the
<code>META.json</code> file that PGXN will require in every distribution. In fact, this
is the <em>only</em> required file in a distribution. Its job is to describe the
distribution, its extensions, and its dependencies, among other things. This
file is key to the whole thing.</p>
<p>To create it, I&rsquo;ve ported the <a href="https://search.cpan.org/perldoc?CPAN::Meta::Sepc">CPAN Meta Spec</a>, version 2, but with all
deprecated fields removed, and some of the more complex stuff taken out. I
also made a couple of the &ldquo;required&rdquo; fields &ldquo;optional.&rdquo; At its simplest, the
file might look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;pgTAP&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;abstract&#34;</span><span class="p">:</span> <span class="s2">&#34;Unit testing for PostgreSQL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.25.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;owner&#34;</span><span class="p">:</span> <span class="s2">&#34;David E. Wheeler &lt;theory@pgxn.org&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;license&#34;</span><span class="p">:</span> <span class="s2">&#34;postgresql&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;meta-spec&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Not too bad, eh? The URL for the spec might change (might move it to the main
site and/or the mirrors), but otherwise, I think this is pretty solid. Not too
much work to deal with, and reasonably easy to create by hand (which is likely
how we&rsquo;ll all start out).</p>
<p>And additional key that will be really important for the PGXN client is
<code>prereqs</code>. This key allows you to identify the prerequisites (from PGXN or the
PostgreSQL core contrib extensions) required to build, test, and/or use a
distribution. For example, if I were to release an <a href="https://justatheory.com/computers/databases/postgresql/key-value-pairs.html">ordered pair</a> extension,
it of course would include tests written with <a href="https://pgtap.org/">pgTAP</a>. So I&rsquo;d have something
like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;pair&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;abstract&#34;</span><span class="p">:</span> <span class="s2">&#34;An ordered pair data type&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;owner&#34;</span><span class="p">:</span> <span class="s2">&#34;David E. Wheeler &lt;theory@pgxn.org&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="err">license:</span> <span class="nt">&#34;postgresql&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;meta-spec&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;prereqs&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;runtime&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;requires&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;PostgreSQL&#34;</span><span class="p">:</span> <span class="s2">&#34;8.0.0&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;recommends&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;PostgreSQL&#34;</span><span class="p">:</span> <span class="s2">&#34;8.4.0&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;test&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;requires&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;pgTAP&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s saying that the &ldquo;pair&rdquo; distribution requires PostgreSQL 8.0.0 or higher
and any version of pgTAP to run the test suite. I&rsquo;ve also recommended
PostgreSQL 8.4, as that&rsquo;s where it will run best.</p>
<p>Of course, to get the real power of PGXN, you&rsquo;ll also want to use the
<code>provides</code> key, which allows you to identify the extensions included in your
distribution. Say that I finally got around to breaking out the schema testing
assertions from the logical testing assertions in pgTAP. I might call the
second module &ldquo;schematap.&rdquo; So to spell it out, I&rsquo;d add this to the first
example above:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl">  <span class="s2">&#34;pgtap&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;file&#34;</span><span class="p">:</span> <span class="s2">&#34;sql/pgtap.sql.in&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.25.0&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;schematap&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;file&#34;</span><span class="p">:</span> <span class="s2">&#34;sql/schematap.sql.in&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span></code></pre></div><p>So now the indexer will know that the &ldquo;pgtap&rdquo; extension is in
<code>sql/pgtap.sql.in</code> and the &ldquo;schematap&rdquo; extension is in <code>sql/schematap.sql.in</code>.
This is important because it allows other distributions to specify &ldquo;schematap&rdquo;
as a prerequisite. It also means that, in the PGXN client, you can type
something like:</p>
<pre tabindex="0"><code>PGXN&gt; install schematap
</code></pre><p>And, because &ldquo;schematap&rdquo; will have been indexed on the network, the client
will be able to find the pgTAP distribution and install it, complete with the
&ldquo;schematap&rdquo; extension.</p>
<p>A final note. Version numbers in the Perl community are a <a href="https://www.dagolden.com/index.php/369/version-numbers-should-be-boring/" title="Version numbers should be boring">disaster</a>. Wanting
to avoid that whole morass, I had originally intended to require numeric
version numbers. But <a href="https://www.dagolden.com/">David Golden</a> &ndash; the current maintainer of the CPAN Meta
Spec &ndash; pointed me to <a href="https://semver.org/">Semantic Versioning</a>, a version number specification by
GitHub&rsquo;s <a href="https://tom.preston-werner.com/">Tom Preston-Werner</a>. This style of version numbering is great for
PGXN for a few reasons:</p>
<ul>
<li>It closely matches how PostgreSQL itself is versioned.</li>
<li>It&rsquo;s very easy to compare version strings.</li>
<li>Someone else has already dealt with the pain of writing a spec</li>
</ul>
<p>So this is the standard that PGXN will require. Every version number will be
dotted-integer with three integers (X.Y.Z) and an optional ASCII string at the
end. That&rsquo;s it. PGXN won&rsquo;t invest any special meaning in the version string
the way CPAN does. It will just compare version numbers.</p>
<p>Anyway, please review <a href="https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec">the spec</a> itself and leave any comments
or questions below. I expect to start hacking on this stuff this week!</p>
<p><strong>Update 2010-08-24:</strong> I&rsquo;ve just updated <a href="https://github.com/theory/pgxn/wiki/PGXN-Meta-Spec">the spec</a> to change
&ldquo;owner&rdquo; to &ldquo;maintainer.&rdquo; I think that the latter term is much better for this
purpose. And then I can use &ldquo;owner&rdquo; in PGXN to identify the person who uploads
a distribution.</p>
]]></content></entry><entry><id>https://blog.pgxn.org/post/954535657</id><title type="html">Thoughts on the Network Directory Structure</title><link rel="alternate" type="text/html" href="https://blog.pgxn.org/2010/thoughts-on-the-network-directory-structure/"/><updated>2026-10-07T16:13:48Z</updated><published>2010-08-14T23:59:12Z</published><author><name>David E. Wheeler</name></author><category scheme="https://blog.pgxn.org/tags" term="mirror" label="Mirror"/><category scheme="https://blog.pgxn.org/tags" term="directory" label="Directory"/><category scheme="https://blog.pgxn.org/tags" term="structure" label="Structure"/><category scheme="https://blog.pgxn.org/tags" term="metadata" label="Metadata"/><category scheme="https://blog.pgxn.org/tags" term="json" label="JSON"/><category scheme="https://blog.pgxn.org/tags" term="api" label="API"/><category scheme="https://blog.pgxn.org/tags" term="scalability" label="Scalability"/><category scheme="https://blog.pgxn.org/tags" term="rest" label="REST"/><summary type="html"><![CDATA[<p>I&rsquo;ve been thinking about the arrangement of stuff to be distributed to the
mirrors. In doing so, I&rsquo;ve kept three goals in mind:</p>
<ol>
<li>Allow things to scale.</li>
<li>Try to put things in intuitive locations.</li>
<li>Allow for metadata to be stored to act as a Web API.</li>
</ol>
<p>The first two are a bit mutually-contradicting. Thinking scalability mainly
means minimizing the chances that too many files can be put into a single
directory. <a href="https://www.cpan.org/misc/ZCAN.html" title="Zen of Comprehensive Archive Networks (look for “Naming”)">CPAN</a> started out with all author directories in a single
directory. Given the sheer number of CPAN contributors, this quickly got to be
a bottleneck. To correct for that, they started &ldquo;hashing&rdquo; the first two
letters of author names to create subdirectories. My CPAN directory, for
example, is <a href="https://www.cpan.org/authors/id/D/DW/DWHEELER/"><code>D/DW/DWHEELER</code></a>. That doesn&rsquo;t quite make for an intuitive
location, but it&rsquo;s not bad, and the tradeoff seems sufficient to keep things
sane.</p>]]></summary><content type="html" xml:base="https://blog.pgxn.org/" xml:space="preserve"><![CDATA[<p>I&rsquo;ve been thinking about the arrangement of stuff to be distributed to the
mirrors. In doing so, I&rsquo;ve kept three goals in mind:</p>
<ol>
<li>Allow things to scale.</li>
<li>Try to put things in intuitive locations.</li>
<li>Allow for metadata to be stored to act as a Web API.</li>
</ol>
<p>The first two are a bit mutually-contradicting. Thinking scalability mainly
means minimizing the chances that too many files can be put into a single
directory. <a href="https://www.cpan.org/misc/ZCAN.html" title="Zen of Comprehensive Archive Networks (look for “Naming”)">CPAN</a> started out with all author directories in a single
directory. Given the sheer number of CPAN contributors, this quickly got to be
a bottleneck. To correct for that, they started &ldquo;hashing&rdquo; the first two
letters of author names to create subdirectories. My CPAN directory, for
example, is <a href="https://www.cpan.org/authors/id/D/DW/DWHEELER/"><code>D/DW/DWHEELER</code></a>. That doesn&rsquo;t quite make for an intuitive
location, but it&rsquo;s not bad, and the tradeoff seems sufficient to keep things
sane.</p>
<p>The storage of metadata is important, too, as I plan to have the <a href="https://pgxn.org/status.html#client">client</a> send
requests for JSON files to mirrors in order to find distributions. Basically,
this came down to a naming convention, as well as a recipe for how to find
metadata files for people and extensions. What I&rsquo;ve come up with is three
directories:</p>
<ul>
<li><code>meta</code> will contain PGXN metadata</li>
<li><code>dist</code> will contain distributions</li>
<li><code>by</code> will contain query-able JSON files</li>
</ul>
<p>The <code>meta</code> directory will contain PGXN metadata (<code>mirrors.json</code>, timestamp,
other detritus). If I ever create an index file that lists all distributions,
it would go there, too. I&rsquo;m not going to discuss it any further here, except
to note that it already has <code>mirrors.json</code>, which clients will be able to use
to present users with a list of mirrors to choose from.</p>
<p>The <code>dist</code> directory will be organized with directories named with &ldquo;hashes&rdquo; of
the first two letters of distribution names. Let&rsquo;s say that I&rsquo;m releasing
<a href="https://pgtan.org/">pgTAP</a> 0.25 on PGXN. To distribute it, the management application will create
the directory (if it doesn&rsquo;t already exist):</p>
<pre tabindex="0"><code>dist/p/pg/pgtap/
</code></pre><p>Then, for the 0.25 release, it will add three files to that directory:</p>
<pre tabindex="0"><code>dist/p/pg/pgtap/pgtap-0.25.pgz
dist/p/pg/pgtap/pgtap-0.25.json
dist/p/pg/pgtap/pgtap-0.25.readme
</code></pre><p>The <code>pgtap-0.25.pgz</code> file will contain the <a href="https://wiki.postgresql.org/wiki/PGXN#Distribution_Layout">zipped distribution</a> ready for
download. <code>pgtap-0.25.json</code> will contain metadata about the distribution, such
as the owner&rsquo;s name, the manager&rsquo;s name (more on these folks below), list of
included extensions, location of the <code>.pgz</code> and <code>.readme</code>, and its SHA1.
<code>pgtap-0.25.readme</code> will of course contain the <code>README</code> for the distribution
(if it has one).</p>
<p>Every release of pgTAP will have these three files, so after several releases,
the <code>pgtap</code> directory might have these files:</p>
<pre tabindex="0"><code>dist/p/pg/pgtap/pgtap-0.23.pgz
dist/p/pg/pgtap/pgtap-0.23.json
dist/p/pg/pgtap/pgtap-0.23.readme
dist/p/pg/pgtap/pgtap-0.24.pgz
dist/p/pg/pgtap/pgtap-0.24.json
dist/p/pg/pgtap/pgtap-0.24.readme
dist/p/pg/pgtap/pgtap-0.25.pgz
dist/p/pg/pgtap/pgtap-0.25.json
dist/p/pg/pgtap/pgtap-0.25.readme
dist/p/pg/pgtap/pgtap.json
</code></pre><p>The last file there, <code>pgtap.json</code>, will actually be a symlink to the JSON file
for latest production release of pgTAP. In this case, it would link to
<code>pgtap-0.25.json</code>. The nice thing about this is that, if a client wants to
find the information about the latest release of the pgtap distribution, all
it will have to do is send an HTTP GET request for
<code>dist/p/pg/pgtap/pgtap.json</code> to any mirror.</p>
<p>The <code>by</code> directory will also contain JSON files for clients to request. The
idea is that you want to find information &ldquo;by&rdquo; something. To start with, there
will be three subdirectories:</p>
<pre tabindex="0"><code>by/extension/
by/manager/
by/owner/
</code></pre><p>The first directory, <code>by/extension/</code>, will contain links to JSON files for
extensions. Say that the pgTAP distribution offers two extensions to
PostgreSQL named &ldquo;pgtap&rdquo; and &ldquo;schematap&rdquo;. The links would be:</p>
<pre tabindex="0"><code>by/extension/p/pg/pgtap/pgtap-0.23.json
by/extension/p/pg/pgtap/pgtap-0.24.json
by/extension/p/pg/pgtap/pgtap-0.25.json
by/extension/p/pg/pgtap/pgtap.json
</code></pre><p>Each of these will simply be symlinks pointing to the appropriate distribution
files:</p>
<pre tabindex="0"><code>dist/p/pg/pgtap/pgtap-0.23.json
dist/p/pg/pgtap/pgtap-0.24.json
dist/p/pg/pgtap/pgtap-0.25.json
dist/p/pg/pgtap/pgtap.json
</code></pre><p>Yes, the last one is a symlink to a symlink. Similarly, these files for
&ldquo;schematap&rdquo;:</p>
<pre tabindex="0"><code>by/extension/s/sc/schematap/schematap-0.23.json
by/extension/s/sc/schematap/schematap-0.24.json
by/extension/s/sc/schematap/schematap-0.25.json
by/extension/s/sc/schematap/schematap.json
</code></pre><p>Point to exactly the same files. Essentially, this is a way for extensions to
point to the distributions that contain them.
<code>by/extension/s/sc/schematap/schematap.json</code> points to
<code>dist/p/pg/pgtap/pgtap.json</code>, which contains path to the <code>.pgz</code> file to
download (and lots of other metadata, too).</p>
<p>The idea is that, whatever the name of the extension you want, the client will
be able to easily find the metadata file that tells it where to find the
distribution.</p>
<p>The <code>by/manager</code> and <code>by/owner</code> directories, on the other hand, contain JSON
files with information about managers and owners and their distributions.
Definitions:</p>
<p>An &ldquo;owner&rdquo; is someone who <em>owns</em> a distribution. This will often be the
original author of an extension, but may be someone else if maintenance has
been passed on. Basically, the &ldquo;owner&rdquo; is the person who should be contacted
with bug reports and the like</p>
<p>A &ldquo;manager&rdquo; is someone who manages the release process. This is the user who
will log into the PGXN management application and upload a distribution for
release.</p>
<p>These two people will often be the same person, but not always. I&rsquo;ve avoided
the term &ldquo;author&rdquo; (the term used by CPAN) because the author of an extension
may no longer maintain it. &ldquo;Owner&rdquo; seemed like a better choice (individual
distributions are free to describe their contributors however they wish).</p>
<p>As the <em>owner</em> of a few extensions on PGXN, I&rsquo;d have this file:</p>
<pre tabindex="0"><code>by/owner/t/th/theory.json
</code></pre><p>This file would contain a list of my distributions and perhaps some other
information (like my full name and blog URL).</p>
<p>As the <em>manager</em> of extensions on PGXN (that is, I actually uploaded them), I
would also have:</p>
<pre tabindex="0"><code>by/manager/t/th/theory.json
</code></pre><p>This file would contain a list of the distributions I&rsquo;ve released on PGXN.
This might be exactly the same as the list in my owner file, but may not be.
Perhaps for one release of pgTAP, say 0.26, <a href="https://leto.net/dukeleto.pl/">Duke Leto</a> uploaded a release. In
that case, 0.26 would probably be in my owner file, but not in my manager
file: it would be in Duke&rsquo;s manager file, instead.</p>
<p>So these are the basics of the directory structure for the networked mirrors.
Note that I haven&rsquo;t thought much about everything that will go into the JSON
files (or whether or not they&rsquo;d be versioned). That will likely depend quite a
lot on what the management database ends up looking like. I&rsquo;ll be working on
that next.</p>
<p>But other than that, comments? Questions? Criticisms? Recommendations? Leave a
comment and let me know!</p>
]]></content></entry></feed>