Skip to main content
jg-packages

Search

    Site metadata

    jg-site

    Structured data, llms.txt files, and theme preferences for websites, as small functions that run in browsers and Node.js.

    v0.1.0UniversalESMMIT

    npm

    npm install jg-site

    pnpm

    pnpm add jg-site

    Usage

    Describe a page to search engines with JSON-LD, and serialize it for a <script type="application/ld+json">. Every < is escaped, so text in the data can’t close the script element:

    ts
    import { buildStructuredData, serializeStructuredData } from 'jg-site';
    
    const json = serializeStructuredData(
      buildStructuredData({
        breadcrumbs: [
          { name: 'Docs', path: '/' },
          { name: 'Install', path: '/install/' },
        ],
        canonicalUrl: 'https://docs.example.com/install/',
        description: 'Install the CLI with npm, pnpm, or Yarn, and check that it runs.',
        site: {
          author: { name: 'Ada Lovelace', url: 'https://ada.example.com/' },
          language: 'en',
          name: 'Example Docs',
          url: 'https://docs.example.com',
        },
        title: 'Install | Example Docs',
        type: 'webpage',
      }),
    );

    A page that builds the rest of its structured data itself can add just the breadcrumb. A step without a URL, such as the current page, has no item:

    ts
    import { buildBreadcrumbStructuredData } from 'jg-site';
    
    buildBreadcrumbStructuredData([
      { name: 'Home', url: 'https://example.com/' },
      { name: 'Blog', url: 'https://example.com/blog' },
      { name: 'Release notes' },
    ]);

    Write an llms.txt in the llmstxt.org format, and an llms-full.txt from the site’s Markdown documents:

    ts
    import { buildLlmsFullText, buildLlmsText } from 'jg-site';
    
    const llmsText = buildLlmsText({
      name: 'Example Docs',
      summary: 'Guides and the API reference for the Example CLI.',
      sections: [
        {
          heading: 'Guides',
          links: [
            { title: 'Install', url: 'https://docs.example.com/install.md', description: 'Set it up.' },
            { title: 'Changelog', url: 'https://docs.example.com/changelog.md' },
          ],
        },
      ],
    });
    const llmsFullText = buildLlmsFullText({
      name: 'Example Docs',
      summary: 'Guides and the API reference for the Example CLI.',
      documents: ['# Install\n\nRun `npm install example-cli`.\n'],
    });

    Read and store a visitor’s theme preference. Stored values are untrusted, so anything unknown reads as 'system', and a browser that blocks storage never throws:

    ts
    import { readThemePreference, resolveThemePreference, writeThemePreference } from 'jg-site';
    
    const preference = readThemePreference('example-theme'); // 'system', 'light', or 'dark'
    const theme = resolveThemePreference(
      preference,
      matchMedia('(prefers-color-scheme: dark)').matches,
    ); // 'light' or 'dark'
    writeThemePreference('example-theme', 'dark'); // false when the browser blocks storage

    SEARCH_TEXT_LIMITS holds the lengths that keep titles and descriptions whole in search results, for build checks.

    Generated from TSDoc

    API reference

    Every export of [email protected], 24 in all, with its signature, its errors, and an example.

    Functions

    buildBreadcrumbStructuredData

    function

    Builds a standalone BreadcrumbList for a page whose structured data is assembled elsewhere, from the same steps its visible breadcrumb shows. A step without a URL, such as the current page, gets no item, as schema.org allows for the last step.

    Signature
    buildBreadcrumbStructuredData(
      items: readonly { name: string; url?: string }[],
    ): Record<string, unknown>

    Parameters

    itemsreadonly { name: string; url?: string }[]

    The steps, from the home page to this page, each with its absolute URL.

    Returns

    Record<string, unknown>

    The BreadcrumbList's JSON-LD.

    Example
    import { buildBreadcrumbStructuredData } from 'jg-site';
    
    buildBreadcrumbStructuredData([
      { name: 'Home', url: 'https://example.com/' },
      { name: 'Blog', url: 'https://example.com/blog' },
      { name: 'Release notes' },
    ]);
    // a BreadcrumbList whose last ListItem has a name and a position, and no item

    buildLlmsFullText

    function

    Gathers a site's documents into one file, as llms-full.txt: the site's name and summary, then each document, separated by a rule.

    Signature
    buildLlmsFullText(options: ILlmsFullTextOptions): string

    Parameters

    optionsILlmsFullTextOptions

    The name, the summary, and the documents, each a Markdown file.

    Returns

    string

    The file's text.

    Example
    import { buildLlmsFullText } from 'jg-site';
    
    buildLlmsFullText({ name: 'jg-packages', summary: 'Packages for agents.', documents: ['# jg-fetch\n'] });
    // the title and the summary, a rule, then the jg-fetch document

    buildLlmsText

    function

    Writes an llms.txt in the llmstxt.org format: the site's name as the title, its summary as a blockquote, optional details, then one ## section per group of links. Each link's label is escaped, and its description, when it has one, kept to one line. A section can carry a Markdown body instead of links, such as an install command in a code block, written as it is.

    Signature
    buildLlmsText(options: ILlmsTextOptions): string

    Parameters

    optionsILlmsTextOptions

    The name, the summary, the details, and the sections.

    Returns

    string

    The file's text.

    Example
    import { buildLlmsText } from 'jg-site';
    
    buildLlmsText({
      name: 'jg-packages',
      summary: 'Small, tested TypeScript packages for coding agents.',
      sections: [
        {
          heading: 'Packages',
          links: [
            {
              title: 'jg-fetch',
              url: 'https://jg-packages.jesusgraterol.dev/packages/jg-fetch/index.md',
              description: 'A fetch wrapper that never throws.',
            },
          ],
        },
      ],
    });
    // a title, a blockquote, and a `## Packages` section with one link

    buildStructuredData

    function

    Builds the JSON-LD for one page: the WebSite, with the pages it lists, for the home page, and a WebPage within it, with its breadcrumb and the source code it documents, for every other page.

    Signature
    buildStructuredData(options: IStructuredDataOptions): Record<string, unknown>

    Parameters

    optionsIStructuredDataOptions

    What the page describes.

    Returns

    Record<string, unknown>

    The page's JSON-LD.

    Example
    import { buildStructuredData } from 'jg-site';
    
    const structuredData = buildStructuredData({
      breadcrumbs: [
        { name: 'jg-packages', path: '/' },
        { name: 'jg-fetch', path: '/packages/jg-fetch/' },
      ],
      canonicalUrl: 'https://jg-packages.jesusgraterol.dev/packages/jg-fetch/',
      description: 'A fetch wrapper that never throws.',
      site: {
        author: { name: 'Jesus Graterol', url: 'https://jesusgraterol.dev/' },
        language: 'en',
        name: 'jg-packages',
        url: 'https://jg-packages.jesusgraterol.dev',
      },
      title: 'jg-fetch: HTTP requests | jg-packages',
      type: 'webpage',
    });
    // a WebPage with its BreadcrumbList, for a <script type="application/ld+json">

    escapeMarkdownLabel

    function

    Escapes text for use as a Markdown link's label, so a bracket in it can't end the label.

    Signature
    escapeMarkdownLabel(label: string): string

    Parameters

    labelstring

    The label, such as a page's name.

    Returns

    string

    The escaped label.

    Example
    import { escapeMarkdownLabel } from 'jg-site';
    
    escapeMarkdownLabel('[draft] notes'); // '\[draft\] notes'

    normalizeDescription

    function

    Collapses prose into one line for a Markdown list item, so a description written across lines stays one item.

    Signature
    normalizeDescription(description: string): string

    Parameters

    descriptionstring

    The description.

    Returns

    string

    The description on one line, without surrounding whitespace.

    Example
    import { normalizeDescription } from 'jg-site';
    
    normalizeDescription('Typed stores,\n  for pages and workers. '); // 'Typed stores, for pages and workers.'

    parseThemePreference

    function

    Reads a stored value as a theme preference, falling back to the system's theme for anything else, because storage holds untrusted text.

    Signature
    parseThemePreference(value: unknown): IThemePreference

    Parameters

    valueunknown

    The stored value.

    Returns

    IThemePreference

    The preference.

    Example
    import { parseThemePreference } from 'jg-site';
    
    parseThemePreference('dark'); // 'dark'
    parseThemePreference('purple'); // 'system'

    readThemePreference

    function

    Reads the preference stored under a key, or the system's theme when there is none or storage is unavailable, such as when the browser blocks it.

    Signature
    readThemePreference(
      storageKey: string,
      storage?: IThemeStorage,
    ): IThemePreference

    Parameters

    storageKeystring

    The key the site stores its preference under, such as 'jg-packages-theme'.

    storage?IThemeStorage

    The storage. Defaults to localStorage.

    Returns

    IThemePreference

    The preference.

    Example
    import { readThemePreference } from 'jg-site';
    
    readThemePreference('jg-packages-theme'); // 'dark' after a visitor chose the dark theme

    resolveThemePreference

    function

    Resolves a preference into the theme the page applies.

    Signature
    resolveThemePreference(
      preference: IThemePreference,
      systemPrefersDark: boolean,
    ): IResolvedTheme

    Parameters

    preferenceIThemePreference

    The preference.

    systemPrefersDarkboolean

    Whether the operating system prefers a dark theme.

    Returns

    IResolvedTheme

    'light' or 'dark'.

    Example
    import { resolveThemePreference } from 'jg-site';
    
    resolveThemePreference('system', true); // 'dark'
    resolveThemePreference('light', true); // 'light'

    serializeStructuredData

    function

    Serializes JSON-LD for a <script type="application/ld+json">, escaping every <, so text in the data, such as a description that mentions </script>, can't end the script element.

    Signature
    serializeStructuredData(structuredData: unknown): string

    Parameters

    structuredDataunknown

    The JSON-LD.

    Returns

    string

    The serialized JSON.

    Example
    import { serializeStructuredData } from 'jg-site';
    
    serializeStructuredData({ name: '</script>' }); // '{"name":"\\u003c/script>"}'

    writeThemePreference

    function

    Stores a preference under a key, and reports whether it could, because the browser can block storage. The page still applies the preference when it can't.

    Signature
    writeThemePreference(
      storageKey: string,
      preference: IThemePreference,
      storage?: IThemeStorage,
    ): boolean

    Parameters

    storageKeystring

    The key the site stores its preference under.

    preferenceIThemePreference

    The preference.

    storage?IThemeStorage

    The storage. Defaults to localStorage.

    Returns

    boolean

    Whether the preference was stored.

    Example
    import { writeThemePreference } from 'jg-site';
    
    writeThemePreference('jg-packages-theme', 'light'); // true

    Constants

    SEARCH_TEXT_LIMITS

    const

    length limits that keep titles and descriptions whole in search results

    Declaration
    const SEARCH_TEXT_LIMITS: { descriptionMaxLength: 160; descriptionMinLength: 70; titleMaxLength: 60 }

    THEME_PREFERENCES

    const

    every preference a visitor can choose

    Declaration
    const THEME_PREFERENCES: readonly IThemePreference[]

    Types

    IListedPage

    type

    one page the home page lists, in display order

    Declaration
    type IListedPage = {
        name: string;
        path: string;
    };

    Members

    • nameproperty
      string
    • pathproperty
      string

    ILlmsFullTextOptions

    type

    a site's documents gathered into one file for a context window

    Declaration
    type ILlmsFullTextOptions = {
        documents: readonly string[];
        name: string;
        summary: string;
    };

    Members

    • documentsproperty
      readonly string[]
    • nameproperty
      string
    • summaryproperty
      string

    ILlmsTextOptions

    type

    what an llms.txt says, in the llmstxt.org format

    Declaration
    type ILlmsTextOptions = {
        details?: readonly string[] | undefined;
        name: string;
        sections: readonly ILlmsTextSection[];
        summary: string;
    };

    Members

    • detailsproperty
      readonly string[]

      paragraphs after the summary, before the sections

    • nameproperty
      string
    • sectionsproperty
      readonly ILlmsTextSection[]
    • summaryproperty
      string

      one sentence on what the site is, the blockquote under the title

    ILlmsTextSection

    type

    one ## section of an llms.txt: a list of links, or a Markdown body, such as an install command in a code block

    Declaration
    type ILlmsTextSection = {
        heading: string;
        links: readonly ILlmsTextLink[];
    } | {
        body: string;
        heading: string;
    };

    IResolvedTheme

    type

    the theme the page applies: light or dark

    Declaration
    type IResolvedTheme = Exclude<IThemePreference, 'system'>;

    IStructuredDataOptions

    type

    the values one page's JSON-LD describes

    Declaration
    type IStructuredDataOptions = {
        breadcrumbs?: readonly {
            name: string;
            path: string;
        }[] | undefined;
        canonicalUrl: string;
        description: string;
        listedPages?: readonly IListedPage[] | undefined;
        site: IStructuredDataSite;
        sourceCode?: IStructuredDataSourceCode | undefined;
        title: string;
        type: 'website' | 'webpage';
    };

    Members

    • breadcrumbsproperty
      readonly { name: string; path: string }[]

      the hierarchy the page's breadcrumb shows, from the home page to this page, as site-relative paths

    • canonicalUrlproperty
      string
    • descriptionproperty
      string
    • listedPagesproperty
      readonly IListedPage[]
    • siteproperty
      IStructuredDataSite
    • sourceCodeproperty
      IStructuredDataSourceCode
    • titleproperty
      string
    • typeproperty
      "website" | "webpage"

      the home page is the website; every other page is a web page within it

    IStructuredDataSite

    type

    the site every page's structured data belongs to

    Declaration
    type IStructuredDataSite = {
        author: {
            name: string;
            url: string;
        };
        language: string;
        name: string;
        url: string;
    };

    Members

    • authorproperty
      { name: string; url: string }
    • languageproperty
      string
    • nameproperty
      string
    • urlproperty
      string

    IStructuredDataSourceCode

    type

    the source code a page documents, such as a package in the repository

    Declaration
    type IStructuredDataSourceCode = {
        codeRepository: string;
        license: string;
        name: string;
        programmingLanguage: string;
        version: string;
    };

    Members

    • codeRepositoryproperty
      string

      where the code lives, such as the package's directory on GitHub

    • licenseproperty
      string

      the license's URL

    • nameproperty
      string
    • programmingLanguageproperty
      string
    • versionproperty
      string

    IThemePreference

    type

    a theme the visitor can choose: the system's, or a fixed light or dark one

    Declaration
    type IThemePreference = 'system' | 'light' | 'dark';

    IThemeStorage

    type

    the storage the preference is read from and written to, such as localStorage

    Declaration
    type IThemeStorage = Pick<Storage, 'getItem' | 'setItem'>;