~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
159 lines · 4.2 KB Raw History
/**
 * 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<string, unknown>} */ (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<string, unknown>}
 */
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];
}