/** * Reading and updating the dev.pdsjs.npm.package record value. */ import { NPM_PACKAGE_COLLECTION } from './lexicon.js'; /** * @typedef {Object} BlobRef * @property {'blob'} $type * @property {{$link: string}} ref * @property {string} mimeType * @property {number} size */ /** * @typedef {Object} VersionEntry * @property {string} version * @property {BlobRef} tarball * @property {BlobRef} manifest * @property {string} integrity * @property {string} [shasum] * @property {string} [publishedAt] */ /** * @typedef {Object} NpmPackageRecord * @property {string} name * @property {Array<{tag: string, version: string}>} distTags * @property {VersionEntry[]} versions * @property {string} createdAt * @property {string} [updatedAt] */ /** * @param {string} cid * @param {string} mimeType * @param {number} size * @returns {BlobRef} */ export function blobRef(cid, mimeType, size) { return { $type: 'blob', ref: { $link: cid }, mimeType, size }; } /** * @param {unknown} value * @returns {BlobRef|null} */ function asBlobRef(value) { if (!value || typeof value !== 'object') return null; const ref = /** @type {{ref?: {$link?: unknown}, mimeType?: unknown, size?: unknown}} */ ( value ); if (typeof ref.ref?.$link !== 'string') return null; return { $type: 'blob', ref: { $link: ref.ref.$link }, mimeType: typeof ref.mimeType === 'string' ? ref.mimeType : 'application/octet-stream', size: typeof ref.size === 'number' ? ref.size : 0, }; } /** * @param {unknown} value - record value from getRecord * @returns {NpmPackageRecord|null} */ export function parsePackageRecord(value) { if (!value || typeof value !== 'object') return null; const record = /** @type {Record} */ (value); if (typeof record.name !== 'string') return null; /** @type {Array<{tag: string, version: string}>} */ const distTags = []; for (const entry of Array.isArray(record.distTags) ? record.distTags : []) { if ( entry && typeof entry.tag === 'string' && typeof entry.version === 'string' ) { distTags.push({ tag: entry.tag, version: entry.version }); } } /** @type {VersionEntry[]} */ const versions = []; for (const entry of Array.isArray(record.versions) ? record.versions : []) { const tarball = asBlobRef(entry?.tarball); const manifest = asBlobRef(entry?.manifest); if ( entry && typeof entry.version === 'string' && typeof entry.integrity === 'string' && tarball && manifest ) { versions.push({ version: entry.version, tarball, manifest, integrity: entry.integrity, shasum: typeof entry.shasum === 'string' ? entry.shasum : undefined, publishedAt: typeof entry.publishedAt === 'string' ? entry.publishedAt : undefined, }); } } return { name: record.name, distTags, versions, createdAt: typeof record.createdAt === 'string' ? record.createdAt : new Date(0).toISOString(), updatedAt: typeof record.updatedAt === 'string' ? record.updatedAt : undefined, }; } /** * The record value to store, with `$type` set and empty updatedAt dropped. * @param {NpmPackageRecord} record * @returns {Record} */ export function serializePackageRecord(record) { return { $type: NPM_PACKAGE_COLLECTION, name: record.name, distTags: record.distTags, versions: record.versions.map((entry) => ({ version: entry.version, tarball: entry.tarball, manifest: entry.manifest, integrity: entry.integrity, ...(entry.shasum ? { shasum: entry.shasum } : {}), ...(entry.publishedAt ? { publishedAt: entry.publishedAt } : {}), })), createdAt: record.createdAt, ...(record.updatedAt ? { updatedAt: record.updatedAt } : {}), }; } /** * Every blob CID the record references, for the link table. * @param {NpmPackageRecord} record * @returns {string[]} */ export function recordBlobCids(record) { const cids = new Set(); for (const entry of record.versions) { cids.add(entry.tarball.ref.$link); cids.add(entry.manifest.ref.$link); } return [...cids]; }