@molecule/api-slug

Utility · slug · API (Node) · v1.0.1 · Apache-2.0

URL slugs from titles and names: accent-folding slugify, and unique slugs checked against your store (a table column or any lookup).

npm install @molecule/api-slug

npm · Source on GitHub

How it works

@molecule/api-slug is a utility package for the API (Node) side (slug).

import { slugify, uniqueSlugInTable } from '@molecule/api-slug'

slugify('Café au lait: a how-to!') // 'cafe-au-lait-a-how-to'

// In a create handler: the first "Hello World" gets hello-world, the next hello-world-2.
const slug = await uniqueSlugInTable('articles', 'slug', input.title)

// On update, a row keeps its own slug:
const next = await uniqueSlugInTable('articles', 'slug', input.title, { excludeId: article.id })

Reference

URL slugs for titles and names.

slugify folds accents and punctuation into a lowercase, hyphenated slug; uniqueSlug finds one your store does not have yet; uniqueSlugInTable does that against a table column through the bonded DataStore.

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

import { slugify, uniqueSlugInTable } from '@molecule/api-slug'

slugify('Café au lait: a how-to!') // 'cafe-au-lait-a-how-to'

// In a create handler: the first "Hello World" gets hello-world, the next hello-world-2.
const slug = await uniqueSlugInTable('articles', 'slug', input.title)

// On update, a row keeps its own slug:
const next = await uniqueSlugInTable('articles', 'slug', input.title, { excludeId: article.id })

Type

utility

Installation

npm install @molecule/api-slug @molecule/api-database

API

Interfaces

SlugifyOptions

How slugify shapes a slug.

interface SlugifyOptions {
  /** Joins the words (default `'-'`). */
  separator?: string
  /** Longest slug kept, cut at a word boundary when one is near (default 80). */
  maxLength?: number
}

UniqueSlugOptions

How uniqueSlug looks for a free slug.

interface UniqueSlugOptions extends SlugifyOptions {
  /** Numbered tries (`slug-2` … `slug-N`) before a random suffix (default 20). */
  maxNumbered?: number
  /** Always end in a random suffix, even when the plain slug is free (default false). */
  alwaysSuffix?: boolean
  /** The base when the text has no letters or digits (default `'item'`). */
  fallback?: string
}

Functions

slugify(text, options)

Turn a title or name into a URL slug: accents folded ("Café" → "cafe"), lowercase, every run of other characters one separator, no separator at either end, cut to maxLength at a word boundary when one is near.

function slugify(text: string, options?: SlugifyOptions): string
  • text — The title or name.
  • options — Separator and maximum length.

Returns: The slug; empty when the text has no letters or digits at all.

uniqueSlug(text, isTaken, options)

A slug for text that isTaken says is free: the plain slug, then slug-2, slug-3 … up to maxNumbered, then slug-<random>.

A check-then-insert can still race another writer: keep a UNIQUE index on the column and retry the insert on a conflict (call this again).

function uniqueSlug(
  text: string,
  isTaken: (slug: string) => Promise<boolean> | boolean,
  options?: UniqueSlugOptions,
): Promise<string>
  • text — The title or name.
  • isTaken — Whether a slug is already used (your store's lookup).
  • options — Slug options, the numbered tries and a fallback for text with no letters.

Returns: A slug isTaken reported free.

uniqueSlugInTable(table, column, text, options)

uniqueSlug checked against a table column through the bonded DataStore (@molecule/api-database).

function uniqueSlugInTable(
  table: string,
  column: string,
  text: string,
  options?: UniqueSlugOptions & { excludeId?: string },
): Promise<string>
  • table — The table, e.g. 'articles'.
  • column — The slug column, e.g. 'slug'.
  • text — The title or name.
  • options — As for uniqueSlug; excludeId lets a row keep its own slug on update.

Returns: A slug no other row of the table uses.

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-database ^1.0.1

Runtime Dependencies

  • @molecule/api-database

  • Keep a UNIQUE index on the slug column. Checking and then inserting can race another request; the index makes the second insert fail, and calling uniqueSlugInTable again finds the next free slug.

  • alwaysSuffix: true gives every row a random tail (hello-world-k3x9q2), for specs that expect slugs not to collide or not to be guessable.

  • Text with no letters or digits ('!!!') slugifies to ''; the unique variants fall back to fallback (default 'item').

  • uniqueSlugInTable needs a DataStore bond (@molecule/api-database); slugify and uniqueSlug need nothing.