Skip to the content

Capsid menu

app/lib/content/corpus.mjs

103 lines and 14 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of corpus.mjs
FunctionLinesComplexityCoveredCRAPMutation
newestThenSlug13 to 155100%OkCRAP 5No data: no mutants in this code
withRelated24 to 581100%OkCRAP 1No data: no mutants in this code
<anonymous:26>26 to 571100%OkCRAP 1No data: no mutants in this code
<anonymous:30>30 to 352100%OkCRAP 2No data: no mutants in this code
<anonymous:37>37 to 432100%OkCRAP 2No data: no mutants in this code
<anonymous:41>41 to 411100%OkCRAP 1No data: no mutants in this code
<anonymous:44>44 to 441100%OkCRAP 1No data: no mutants in this code
<anonymous:47>47 to 482100%OkCRAP 2No data: no mutants in this code
<anonymous:55>55 to 551100%OkCRAP 1No data: no mutants in this code
outgoingPostLinks67 to 7320%WatchCRAP 6No data: no mutants in this code
withBacklinks81 to 11384%CriticalCRAP 64.3No data: no mutants in this code
<anonymous:83>83 to 8310%OkCRAP 2No data: no mutants in this code
<anonymous:105>105 to 11220%WatchCRAP 6No data: no mutants in this code
<anonymous:110>110 to 11010%OkCRAP 2No data: no mutants in this code

Imports

It imports app/lib/search/visibility.mjs. Imported by app/lib/content/pipeline.mjs.

Source

// Corpus-wide passes, run over every post at once in both writers: related posts and backlinks.

import { isPubliclyVisible, statusForDraft } from "../search/visibility.mjs";

const RELATED_LIMIT = 3;

/**
 * Newest first, then by slug: a total order, so a tie is never left to array order.
 *
 * @param {{ publishAt: string, slug: string }} a
 * @param {{ publishAt: string, slug: string }} b
 */
function newestThenSlug(a, b) {
  return (a.publishAt < b.publishAt ? 1 : a.publishAt > b.publishAt ? -1 : 0) || (a.slug < b.slug ? -1 : 1);
}

/**
 * Must run over the whole corpus in both writers: relatedness is a property of the set.
 * isPubliclyVisible, not !draft, because a scheduled post is not a draft. blog.$slug.tsx filters
 * again because a listed post can be unpublished after this list is stored.
 *
 * @param {any[]} posts
 */
export function withRelated(posts) {
  const now = Date.now();
  return posts.map((post) => {
    const tags = new Set(post.tags);
    const scored = posts
      .filter(
        (other) =>
          other.slug !== post.slug &&
          isPubliclyVisible(
            { status: statusForDraft(other.draft), publishAt: other.publishAt },
            now,
          ),
      )
      .map((other) => ({
        slug: other.slug,
        title: other.title,
        description: other.description ?? null,
        shared: other.tags.filter((/** @type {string} */ t) => tags.has(t)).length,
        publishAt: other.publishAt,
      }))
      .filter((other) => other.shared > 0);

    scored.sort(
      (a, b) =>
        b.shared - a.shared || newestThenSlug(a, b),
    );

    return {
      ...post,
      related: scored
        .slice(0, RELATED_LIMIT)
        .map(({ slug, title, description, shared }) => ({ slug, title, description, shared })),
    };
  });
}

/**
 * Read from rendered HTML, which unifies inline, reference and raw links. Deliberately loose: the
 * corpus in withBacklinks is the filter.
 *
 * @param {string} html
 * @returns {Set<string>}
 */
function outgoingPostLinks(html) {
  const found = new Set();
  for (const match of html.matchAll(/href="\/writing\/([^"#?]+)(?:[#?][^"]*)?"/g)) {
    found.add(match[1]);
  }
  return found;
}

/**
 * Whole corpus, like withRelated. The SOURCE is filtered for visibility so a draft cannot put its title
 * on a public page; blog.$slug.tsx re-checks. No limit: a cap would silently drop real links.
 *
 * @param {any[]} posts
 */
export function withBacklinks(posts) {
  const now = Date.now();
  const isPost = new Set(posts.map((post) => post.slug));
  /** @type {Map<string, Array<{ slug: string, title: string, publishAt: string }>>} */
  const incoming = new Map();

  for (const post of posts) {
    if (
      !isPubliclyVisible(
        { status: statusForDraft(post.draft), publishAt: post.publishAt },
        now,
      )
    ) {
      continue;
    }
    for (const target of outgoingPostLinks(post.html ?? "")) {
      // A post linking to itself is a table of contents, not a backlink.
      if (target === post.slug || !isPost.has(target)) continue;
      const list = incoming.get(target) ?? [];
      list.push({ slug: post.slug, title: post.title, publishAt: post.publishAt });
      incoming.set(target, list);
    }
  }

  return posts.map((post) => {
    // Never leave a tie to array order, which is not stable input.
    const list = (incoming.get(post.slug) ?? []).sort(newestThenSlug);
    return {
      ...post,
      backlinks: list.map(({ slug, title }) => ({ slug, title })),
    };
  });
}