chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
/**
* Lexicon for npm packages stored natively in an atproto repo.
*
* One record per package (rkey = encoded package name). The record holds
* the dist-tags and, for every published version, blob references for the
* tarball and the version manifest, which keeps those blobs out of orphan
* cleanup. npm integrity is SHA-512, so unlike an OCI digest it cannot be
* read back out of a blob CID; the record carries the integrity strings.
*/
export const NPM_PACKAGE_COLLECTION = 'dev.pdsjs.npm.package';
const NAME_PART = /^[a-z0-9~-][a-z0-9._~-]*$/;
const MAX_NAME_LENGTH = 214;
/**
* npm package name grammar: an optional `@scope/`, then a name that does
* not begin with `.` or `_`.
* @param {string} name
* @returns {boolean}
*/
export function isValidName(name) {
if (!name || name.length > MAX_NAME_LENGTH) return false;
if (name.startsWith('@')) {
const parts = name.slice(1).split('/');
if (parts.length !== 2) return false;
return parts.every((part) => NAME_PART.test(part));
}
return NAME_PART.test(name);
}
/**
* A package name may hold `@` and `/`, which a record key may not. A
* scoped name encodes as `scope:name`: `:` is a valid record-key character
* that no npm name contains, so the two substitute cleanly.
* @param {string} name
* @returns {string}
*/
export function nameToRkey(name) {
if (name.startsWith('@')) return name.slice(1).replace('/', ':');
return name;
}
/**
* @param {string} rkey
* @returns {string}
*/
export function rkeyToName(rkey) {
if (rkey.includes(':')) return `@${rkey.replace(':', '/')}`;
return rkey;
}
export const npmPackageLexicon = {
lexicon: 1,
id: NPM_PACKAGE_COLLECTION,
defs: {
main: {
type: 'record',
description:
'An npm package: its dist-tags plus a tarball and manifest blob for every published version.',
key: 'any',
record: {
type: 'object',
required: ['name', 'distTags', 'versions', 'createdAt'],
properties: {
name: {
type: 'string',
maxLength: 512,
description:
'Package name. The record key encodes this name with the leading `@` dropped and `/` replaced by `:`.',
},
description: { type: 'string', maxLength: 3000 },
distTags: {
type: 'array',
items: { type: 'ref', ref: '#distTag' },
},
versions: {
type: 'array',
items: { type: 'ref', ref: '#version' },
},
createdAt: { type: 'string', format: 'datetime' },
updatedAt: { type: 'string', format: 'datetime' },
},
},
},
distTag: {
type: 'object',
required: ['tag', 'version'],
properties: {
tag: { type: 'string', maxLength: 128 },
version: { type: 'string', maxLength: 256 },
},
},
version: {
type: 'object',
required: ['version', 'tarball', 'manifest', 'integrity'],
properties: {
version: { type: 'string', maxLength: 256 },
tarball: {
type: 'blob',
description: 'The package tarball, exactly as published.',
},
manifest: {
type: 'blob',
description:
'The version manifest as JSON bytes, exactly as published. The packument is assembled from these.',
},
integrity: {
type: 'string',
maxLength: 512,
description: 'SRI string for the tarball, e.g. sha512-....',
},
shasum: {
type: 'string',
maxLength: 64,
description: 'SHA-1 hex of the tarball, for older clients.',
},
publishedAt: { type: 'string', format: 'datetime' },
},
},
},
};