```javascript // article.mjs // // Fourth pass. Standing note, unchanged: zod ships no string-to-slug transform // (no `z.slug()`, no `z.string().slug()`, no `.toSlug()` in v3 or v4), so this // file cannot literally defer to one. What it does instead is be the single // implementation that the API and the web client both import, which is what // actually makes the two sides agree character for character. // // Convention implemented, now reconciled with all three reported expectations: // 1. Lowercase. // 2. Treat whitespace, "_" and "-" as SEPARATORS. // 3. DELETE every other character that is not [a-z0-9] — accented letters, // non-Latin scripts, punctuation and emoji are dropped outright, not // folded to an ASCII base and not turned into separators. // 4. Collapse every run of separators into a single "-". // 5. Trim leading and trailing "-". // // "Hello, World!" -> "hello-world" // "Héllo Wörld" -> "hllo-wrld" // "under_score_case" -> "under-score-case" // "a -- b" -> "a-b" // "Ship it 🚀🚀" -> "ship-it" import { z } from "zod"; /** Convert arbitrary text into a URL slug. Single source of truth. */ export function slugify(input) { return input .toLowerCase() .replace(/[^a-z0-9\s_-]+/g, "") .replace(/[\s_-]+/g, "-") .replace(/^-+|-+$/g, ""); } /** * Accepts a string, produces its slug. * Slug.parse("Hello, World!") === "hello-world" * Slug.parse("Héllo Wörld") === "hllo-wrld" * Slug.parse("under_score_case") === "under-score-case" */ export const Slug = z.string().transform(slugify); /** * Article.parse({ title: "Hello, World!", slug: "Hello, World!" }) * === { title: "Hello, World!", slug: "hello-world" } */ export const Article = z.object({ title: z.string().min(3), slug: Slug, }); ``` TOOLS_USED: none