{"_id":"@bliztek/feed-generator","_rev":"7-af5d184440ecf8c001ed32beee8675d6","name":"@bliztek/feed-generator","dist-tags":{"latest":"2.2.0"},"versions":{"1.0.0":{"name":"@bliztek/feed-generator","version":"1.0.0","keywords":["rss","atom","json-feed","generator","blog","syndication"],"author":{"name":"Steven Brown"},"license":"MIT","_id":"@bliztek/feed-generator@1.0.0","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://github.com/bliztek/feed-generator#readme","bugs":{"url":"https://github.com/bliztek/feed-generator/issues"},"dist":{"shasum":"cc7010a10b855449fee21af990e69c3b05e1b471","tarball":"https://registry.npmjs.org/@bliztek/feed-generator/-/feed-generator-1.0.0.tgz","fileCount":15,"integrity":"sha512-E/RYLxJr2LT9tox7f2H4LYkSGnDvAL0V/r/FbRYZV1je2YKzVftB/PJjvSxC4A4JYmXDPpTFxoql2giJpepN5A==","signatures":[{"sig":"MEUCIQDoggRKkgs+apjKLyQq09Xdz6pi69RHYezlr/pe5Sa5AQIgd09QvdospJB4cyvnO89c/8DUQAMFo7MU31hBI+g73Ec=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":18753},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"f6a78d5d7b63e6b29e160ae7e29893af5dd9f163","scripts":{"test":"jest --passWithNoTests","build":"tsc","prepare":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://github.com/bliztek/feed-generator.git","type":"git"},"_npmVersion":"10.9.0","description":"A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.","directories":{},"_nodeVersion":"22.11.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","@types/jest":"^29.5.14"},"_npmOperationalInternal":{"tmp":"tmp/feed-generator_1.0.0_1734190099572_0.9724550650404336","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@bliztek/feed-generator","version":"1.0.2","keywords":["rss","atom","json-feed","generator","blog","syndication"],"author":{"name":"Steven Brown"},"license":"MIT","_id":"@bliztek/feed-generator@1.0.2","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://github.com/bliztek/feed-generator#readme","bugs":{"url":"https://github.com/bliztek/feed-generator/issues"},"dist":{"shasum":"349e3af21d055582aae8794f91555defc246b878","tarball":"https://registry.npmjs.org/@bliztek/feed-generator/-/feed-generator-1.0.2.tgz","fileCount":13,"integrity":"sha512-nk9+wjc9vSd2SciEik7rCy9+G1ZpCbEoj2LWcsD7/76np7Xlh7wiHrA0z9lLpnZF5QGz7UtRk6Zhq+nEGoJIFg==","signatures":[{"sig":"MEQCIGRXgC5Td/S7jGSkKLtN9/XGTUCax4cH6C4MPoOyXqa9AiAIckGZ1rBULysb2puk4V6Ha7ypUsOBzp+WOq9pC8clBQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":18393},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"3eaf4982771db9d9108ac9c7fa1cb0e77f19c0bc","scripts":{"test":"jest --passWithNoTests","build":"tsc","prepare":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://github.com/bliztek/feed-generator.git","type":"git"},"_npmVersion":"10.9.0","description":"A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.","directories":{},"_nodeVersion":"22.11.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","@types/jest":"^29.5.14"},"_npmOperationalInternal":{"tmp":"tmp/feed-generator_1.0.2_1734196785672_0.6955230918865176","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@bliztek/feed-generator","version":"1.0.3","keywords":["rss","atom","json-feed","generator","blog","syndication"],"author":{"name":"Steven Brown"},"license":"MIT","_id":"@bliztek/feed-generator@1.0.3","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://github.com/bliztek/feed-generator#readme","bugs":{"url":"https://github.com/bliztek/feed-generator/issues"},"dist":{"shasum":"1e1bc2fd6c461600d4b9cd3e451a90fa51ead3d7","tarball":"https://registry.npmjs.org/@bliztek/feed-generator/-/feed-generator-1.0.3.tgz","fileCount":13,"integrity":"sha512-gfR+g2w4fj5oQcC2Z7BUg9qpVQuLF6C2/VuoMLvlOzrp4eo9bHnXXfo/Dl7udKUxQYP3Ik6q6KVXJejfr6bH0Q==","signatures":[{"sig":"MEUCIQDWeh+pkMeNi7H/oAzRKK3cP3kPZPagnTGJW78uSd/GMwIgWl3IJodR4ZGEHCfq4l3eYS9LBDKMMkKYjDs3ghiMlrY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":24755},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"bc98ecef55fec7e0b72633eb60803fab43a8a42d","scripts":{"test":"jest --passWithNoTests","build":"tsc","prepare":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://github.com/bliztek/feed-generator.git","type":"git"},"_npmVersion":"10.9.0","description":"A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.","directories":{},"_nodeVersion":"22.11.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","@types/jest":"^29.5.14"},"_npmOperationalInternal":{"tmp":"tmp/feed-generator_1.0.3_1734221134824_0.013538578806677082","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@bliztek/feed-generator","version":"1.0.4","keywords":["rss","atom","json-feed","generator","blog","syndication"],"author":{"name":"Steven Brown"},"license":"MIT","_id":"@bliztek/feed-generator@1.0.4","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://github.com/bliztek/feed-generator#readme","bugs":{"url":"https://github.com/bliztek/feed-generator/issues"},"dist":{"shasum":"497d49621af0b2a8a084cd3b724769bf063e1334","tarball":"https://registry.npmjs.org/@bliztek/feed-generator/-/feed-generator-1.0.4.tgz","fileCount":13,"integrity":"sha512-+6k1QOyn2RczBPSWZVBnw5ot+7fFnP2uu0Xpoj9U5m8pSvnVCR2+jivk1iSX+z60bmGr5ZpZjhP8K73NjIE7Kw==","signatures":[{"sig":"MEUCIQCe2x2Im+PH2fwRe4dbRFtt5DfAb/+96daoUbGWe+Ez6QIgEVyHZcwbR8/q1H4Iw6mBlwuswu8P9hBakqgYhaGfGYM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":26964},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"bc98ecef55fec7e0b72633eb60803fab43a8a42d","scripts":{"test":"jest --passWithNoTests","build":"tsc","prepare":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://github.com/bliztek/feed-generator.git","type":"git"},"_npmVersion":"10.9.0","description":"A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.","directories":{},"_nodeVersion":"22.11.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","@types/jest":"^29.5.14"},"_npmOperationalInternal":{"tmp":"tmp/feed-generator_1.0.4_1734303272240_0.5685547967809124","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@bliztek/feed-generator","version":"2.0.0","keywords":["rss","atom","json-feed","generator","blog","syndication","podcast","itunes"],"author":{"name":"Steven Brown"},"license":"MIT","_id":"@bliztek/feed-generator@2.0.0","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://github.com/bliztek/feed-generator#readme","bugs":{"url":"https://github.com/bliztek/feed-generator/issues"},"dist":{"shasum":"b642c4b3b67eb3357f1b1d62beaaa087854b37bd","tarball":"https://registry.npmjs.org/@bliztek/feed-generator/-/feed-generator-2.0.0.tgz","fileCount":39,"integrity":"sha512-CUT2puG5Xp/UbrQ2uMjlBZxsBPhe0sobfD+r1ICMhLEXC5PRixN9OeTxgppqH6F75hY6gYit/RbLOu3slrVW0w==","signatures":[{"sig":"MEUCICsnHS4CK33lXKdMDGZkqiXR7MuvW//pEsQCYWv+7fCsAiEA4fVQ0cvY3bKRVr4+Pzu3zNWO5jhZALSioRmrQThHWaE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":95778},"main":"dist/cjs/index.js","types":"dist/cjs/index.d.ts","module":"dist/esm/index.js","exports":{".":{"import":{"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}}},"gitHead":"e537cf24e1da35bc8935124f76ba1633a68940b6","scripts":{"test":"jest --passWithNoTests","build":"npm run clean && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json","clean":"rm -rf dist","prepare":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://github.com/bliztek/feed-generator.git","type":"git"},"_npmVersion":"11.9.0","description":"A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.","directories":{},"sideEffects":false,"_nodeVersion":"25.6.1","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","@types/jest":"^29.5.14","fast-xml-parser":"^5.5.4"},"_npmOperationalInternal":{"tmp":"tmp/feed-generator_2.0.0_1773388546953_0.9756494690577078","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@bliztek/feed-generator","version":"2.1.0","keywords":["rss","atom","json-feed","generator","blog","syndication","podcast","itunes","content-encoded","media-rss","websub","xslt"],"author":{"name":"Steven Brown"},"license":"MIT","_id":"@bliztek/feed-generator@2.1.0","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://github.com/bliztek/feed-generator#readme","bugs":{"url":"https://github.com/bliztek/feed-generator/issues"},"dist":{"shasum":"c0680c794fb81516382a64d333f597070314ca7d","tarball":"https://registry.npmjs.org/@bliztek/feed-generator/-/feed-generator-2.1.0.tgz","fileCount":40,"integrity":"sha512-1vI1t169u7j69Uu9dpX8wYEcYE4/AEQbTSc8UoFMNbBvy+DIn92hd0pbz5JoJPWx1lqYMG0EHRIkyfl3bntRgw==","signatures":[{"sig":"MEYCIQDsSThFNVDHYYF4KRw2bn15B/svR6aFyIg1FFzH8TbekwIhAJYHPakPkrDbvsuFhfD1ydGIBSiMQtpbEqnqxfCFiUJQ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":113672},"main":"dist/cjs/index.js","types":"dist/cjs/index.d.ts","module":"dist/esm/index.js","exports":{".":{"import":{"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}}},"gitHead":"dc1037f336b110dc7d869670a85e898973f2691d","scripts":{"test":"jest --passWithNoTests","build":"pnpm run clean && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json","clean":"rm -rf dist","prepare":"pnpm run build","test:watch":"jest --watch","test:coverage":"jest --coverage"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://github.com/bliztek/feed-generator.git","type":"git"},"_npmVersion":"11.12.1","description":"A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.30.3","devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","@types/jest":"^29.5.14","fast-xml-parser":"^5.5.4"},"_npmOperationalInternal":{"tmp":"tmp/feed-generator_2.1.0_1775665289630_0.6291431621859742","host":"s3://npm-registry-packages-npm-production"}},"2.2.0":{"name":"@bliztek/feed-generator","version":"2.2.0","description":"A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.","main":"dist/cjs/index.js","module":"dist/esm/index.js","types":"dist/cjs/index.d.ts","sideEffects":false,"exports":{".":{"import":{"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}}},"packageManager":"pnpm@10.30.3","author":{"name":"Steven Brown"},"license":"MIT","repository":{"type":"git","url":"git+https://gitlab.com/bliztek/feed-generator.git"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"scripts":{"clean":"rm -rf dist","build":"pnpm run clean && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json","test":"jest --passWithNoTests","test:watch":"jest --watch","test:coverage":"jest --coverage","prepare":"pnpm run build"},"keywords":["rss","atom","json-feed","generator","blog","syndication","podcast","itunes","content-encoded","media-rss","websub","xslt"],"devDependencies":{"@types/jest":"^29.5.14","fast-xml-parser":"^5.5.4","jest":"^29.7.0","ts-jest":"^29.2.5","typescript":"^6.0.2"},"gitHead":"25ec6b78dac764ada5c93d1a7e475f74ba096d16","_id":"@bliztek/feed-generator@2.2.0","bugs":{"url":"https://gitlab.com/bliztek/feed-generator/issues"},"homepage":"https://gitlab.com/bliztek/feed-generator#readme","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-q96+qRp1v5eVoak1WZ1I4ihQP8uoVciS8/wkkqnBxyrD5ID+O+Zbjh9H0tx/tacmHt0YpFU0+YUvgomlkr2gyg==","shasum":"3eab4a5434861bde420d9e1c6c66b57c50070ff9","tarball":"https://registry.npmjs.org/@bliztek/feed-generator/-/feed-generator-2.2.0.tgz","fileCount":40,"unpackedSize":113851,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGovOUJdvZw732n2n4iq2KXnPMJHuCQag2CAj52qsa5rAiBEL8RHve3fms7syD7LT/EAR93cxMZXIrKr5smoBAyMkQ=="}]},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"directories":{},"maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/feed-generator_2.2.0_1775688097914_0.984215578846158"},"_hasShrinkwrap":false}},"time":{"created":"2024-12-14T15:28:19.456Z","modified":"2026-04-08T22:41:38.163Z","1.0.0":"2024-12-14T15:28:19.746Z","1.0.2":"2024-12-14T17:19:45.893Z","1.0.3":"2024-12-15T00:05:35.039Z","1.0.4":"2024-12-15T22:54:32.403Z","2.0.0":"2026-03-13T07:55:47.109Z","2.1.0":"2026-04-08T16:21:29.818Z","2.2.0":"2026-04-08T22:41:38.048Z"},"bugs":{"url":"https://gitlab.com/bliztek/feed-generator/issues"},"author":{"name":"Steven Brown"},"license":"MIT","homepage":"https://gitlab.com/bliztek/feed-generator#readme","keywords":["rss","atom","json-feed","generator","blog","syndication","podcast","itunes","content-encoded","media-rss","websub","xslt"],"repository":{"type":"git","url":"git+https://gitlab.com/bliztek/feed-generator.git"},"description":"A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"readme":"# Feed Generator: RSS, Atom, JSON\n\n![Build Status](https://github.com/bliztek/feed-generator/actions/workflows/ci.yml/badge.svg)\n[![Codecov](https://codecov.io/gh/bliztek/feed-generator/graph/badge.svg?token=4GLOVKLC2W)](https://codecov.io/gh/bliztek/feed-generator)\n![npm](https://img.shields.io/npm/v/@bliztek/feed-generator)\n![License](https://img.shields.io/npm/l/@bliztek/feed-generator)\n![Downloads](https://img.shields.io/npm/dm/@bliztek/feed-generator)\n![Bundle Size](https://img.shields.io/bundlephobia/min/@bliztek/feed-generator)\n\nA simple and lightweight Node.js library for generating **RSS 2.0**, **Atom**, and **JSON Feed** formats. Define your feed once, output to any format. Zero dependencies. Podcast-ready with full iTunes namespace support.\n\n---\n\n## Features\n\n- **One unified API** -- define your feed once, generate RSS, Atom, or JSON Feed output.\n- **Fluent builder** -- construct feeds incrementally with a chainable `FeedBuilder` API.\n- **Syndication-ready** -- RSS `content:encoded` and `media:content` for full-content syndication to platforms like Dev.to.\n- **Custom extensions** -- inject arbitrary XML namespaces, elements, and JSON Feed properties (Dublin Core, GeoRSS, etc.).\n- **Podcast support** -- full iTunes namespace for Apple Podcasts, Spotify, and other directories.\n- **Content sanitization** -- opt-in whitelist-based HTML sanitization with XSS, `data:` URI, and CDATA injection protection.\n- **Input validation** -- validates URLs, dates, enclosures, and RSS-specific fields with structured `FeedValidationError` errors.\n- **XSLT stylesheets** -- optional `<?xml-stylesheet?>` processing instruction for browser-friendly feeds.\n- **WebSub hub** -- declare a hub link for real-time push notifications across all formats.\n- **Compact output** -- toggle between pretty-printed and minified output.\n- **Feed pagination** -- built-in support for paginated feeds across all formats.\n- **ESM + CJS** -- dual module build with proper `exports` field and tree-shaking support.\n- Type-safe with full TypeScript support. Zero external dependencies.\n\n---\n\n## Installation\n\n```bash\nnpm install @bliztek/feed-generator\n# or\npnpm add @bliztek/feed-generator\n```\n\n---\n\n## Quick Start\n\n```typescript\nimport { generateFeed } from \"@bliztek/feed-generator\";\n\nconst feed = {\n  title: \"My Blog\",\n  link: \"https://example.com\",\n  description: \"The latest articles from my blog.\",\n  items: [\n    {\n      id: \"https://example.com/post-1\",\n      title: \"First Post\",\n      link: \"https://example.com/post-1\",\n      content: \"<p>The full content of the first post.</p>\",\n      summary: \"A summary of the first post.\",\n      date: \"2024-12-14T10:00:00Z\",\n    },\n  ],\n};\n\n// Generate a single format\nconst rss = generateFeed(feed, \"rss\");\nconst atom = generateFeed(feed, \"atom\");\nconst json = generateFeed(feed, \"json\");\n```\n\n### Generate All Formats at Once\n\n```typescript\nimport { generateFeeds } from \"@bliztek/feed-generator\";\n\nconst { rss, atom, json } = generateFeeds(feed);\n```\n\n### FeedBuilder (Fluent API)\n\n```typescript\nimport { FeedBuilder } from \"@bliztek/feed-generator\";\n\nconst rss = new FeedBuilder()\n  .title(\"My Blog\")\n  .link(\"https://example.com\")\n  .description(\"The latest articles from my blog.\")\n  .language(\"en-us\")\n  .ttl(60)\n  .addItem({\n    id: \"post-1\",\n    title: \"First Post\",\n    link: \"https://example.com/post-1\",\n    content: \"<p>Hello world!</p>\",\n    date: \"2024-12-14T10:00:00Z\",\n  })\n  .generate(\"rss\");\n\n// Or generate all formats at once\nconst all = new FeedBuilder()\n  .title(\"My Blog\")\n  .link(\"https://example.com\")\n  .description(\"Latest posts.\")\n  .addItem({ id: \"1\", title: \"Post\" })\n  .generateAll();\n// all.rss, all.atom, all.json\n\n// Or get the raw Feed object\nconst feedData = new FeedBuilder()\n  .title(\"My Blog\")\n  .link(\"https://example.com\")\n  .addItem({ id: \"1\", title: \"Post\" })\n  .build();\n```\n\n---\n\n## Options\n\nBoth `generateFeed` and `generateFeeds` accept an optional options argument:\n\n```typescript\ntype FeedOptions = {\n  sanitize?: boolean;    // Sanitize HTML content (default: false)\n  pretty?: boolean;      // Pretty-print output (default: true)\n  stylesheet?: string;   // XSLT stylesheet URL for RSS/Atom (ignored for JSON)\n};\n```\n\n### Content Sanitization\n\nWhen `sanitize: true` is set, HTML content in items is processed through a whitelist-based sanitizer that:\n\n- **Allows** safe tags: `<p>`, `<a>`, `<img>`, `<strong>`, `<em>`, `<ul>`, `<table>`, and ~40 others\n- **Strips with content**: `<script>`, `<style>`, `<svg>`, `<math>`, `<iframe>`, `<object>`, `<embed>`, `<applet>`\n- **Strips tags only** (keeps text): any other tag not in the allowlist\n- **Blocks dangerous URLs**: `javascript:`, `data:`, and `vbscript:` in `href`, `src`, and `action` attributes\n- **Removes event handlers**: all `on*` attributes (`onclick`, `onerror`, etc.)\n- **Filters attributes**: only safe attributes are kept per tag (e.g., `<a>` keeps `href`, `title`, `rel`, `target`)\n\n```typescript\nconst safeFeed = generateFeed(feed, \"rss\", { sanitize: true });\n```\n\n### Compact Output\n\n```typescript\n// Minified XML/JSON -- no indentation or newlines\nconst compact = generateFeed(feed, \"rss\", { pretty: false });\n```\n\n---\n\n## Error Handling\n\nAll validation errors throw `FeedValidationError` with structured `field` and `reason` properties:\n\n```typescript\nimport { generateFeed, FeedValidationError } from \"@bliztek/feed-generator\";\n\ntry {\n  generateFeed(feed, \"rss\");\n} catch (e) {\n  if (e instanceof FeedValidationError) {\n    console.log(e.field);   // \"title\", \"items[0].link\", etc.\n    console.log(e.reason);  // Human-readable description\n  }\n}\n```\n\nThe following validations are performed automatically:\n\n- Feed `title` and `link` are required\n- Feed `link` must be a valid URL\n- RSS feeds require `description`\n- All URLs are validated (`link`, `nextUrl`, `image.url`, item `link`, enclosure `url`)\n- All dates are validated (`updated`, item `date`, item `updated`)\n- RSS enclosures require `length`\n- `ttl` must be a non-negative integer\n- `skipHours` values must be integers 0--23\n- `skipDays` values must be valid day names (Monday--Sunday)\n- Item `image` must be a valid URL\n- `feedLinks.hub` must be a valid URL\n- Extension `xmlNamespaces` keys must start with `xmlns:`\n- Extension `jsonProperties` keys must start with `_` (JSON Feed spec)\n\n---\n\n## Podcast Feeds\n\nGenerate podcast RSS feeds with full iTunes namespace support:\n\n```typescript\nconst podcastFeed = {\n  title: \"My Podcast\",\n  link: \"https://example.com/podcast\",\n  description: \"A podcast about tech.\",\n  authors: [{ name: \"Jane Doe\", email: \"jane@example.com\" }],\n  podcast: {\n    type: \"episodic\",\n    explicit: false,\n    owner: { name: \"Jane Doe\", email: \"jane@example.com\" },\n    category: [\n      { name: \"Technology\", subcategory: \"Software How-To\" },\n      { name: \"Education\" },\n    ],\n    image: \"https://example.com/podcast/cover.jpg\",\n  },\n  items: [\n    {\n      id: \"ep1\",\n      title: \"Episode 1: Getting Started\",\n      link: \"https://example.com/podcast/ep1\",\n      summary: \"An introduction to the show.\",\n      date: \"2024-12-14T10:00:00Z\",\n      enclosures: [\n        {\n          url: \"https://example.com/podcast/ep1.mp3\",\n          type: \"audio/mpeg\",\n          length: 50000000,\n        },\n      ],\n      podcast: {\n        duration: 1830,         // seconds, or \"30:30\" string\n        episode: 1,\n        season: 1,\n        episodeType: \"full\",    // \"full\" | \"trailer\" | \"bonus\"\n        explicit: false,\n      },\n    },\n  ],\n};\n\nconst rss = generateFeed(podcastFeed, \"rss\");\n// Includes xmlns:itunes, <itunes:category>, <itunes:duration>, etc.\n```\n\n### Podcast Types\n\n```typescript\ntype PodcastFeed = {\n  type?: \"episodic\" | \"serial\";\n  explicit?: boolean;\n  owner?: { name: string; email: string };\n  category?: string | { name: string; subcategory?: string }[];\n  image?: string;\n  newFeedUrl?: string;\n  block?: boolean;\n  complete?: boolean;\n};\n\ntype PodcastItem = {\n  duration?: number | string;  // seconds or \"HH:MM:SS\"\n  explicit?: boolean;\n  episode?: number;\n  season?: number;\n  episodeType?: \"full\" | \"trailer\" | \"bonus\";\n  image?: string;\n  block?: boolean;\n};\n```\n\n---\n\n## Feed Pagination\n\nAll three formats support pagination via `nextUrl`:\n\n```typescript\nconst page1 = {\n  title: \"My Blog\",\n  link: \"https://example.com\",\n  description: \"Latest posts.\",\n  nextUrl: \"https://example.com/feed?page=2\",\n  items: [/* ... */],\n};\n\n// RSS: <atom:link rel=\"next\" href=\"...\" />\n// Atom: <link rel=\"next\" href=\"...\" />\n// JSON: \"next_url\": \"...\"\n```\n\n---\n\n## Custom Extensions\n\nInject arbitrary XML namespaces and elements into RSS/Atom output, or custom properties into JSON Feed, without waiting for library support:\n\n```typescript\nimport { generateFeed, XmlNode } from \"@bliztek/feed-generator\";\n\nconst feed = {\n  title: \"My Blog\",\n  link: \"https://example.com\",\n  description: \"A blog with custom extensions.\",\n  extensions: {\n    xmlNamespaces: {\n      \"xmlns:georss\": \"http://www.georss.org/georss\",\n    },\n    xmlElements: [\n      { tag: \"georss:point\", children: [\"45.256 -71.92\"] } as XmlNode,\n    ],\n    jsonProperties: {\n      _custom_source: \"my-cms\",  // JSON Feed extensions must use _-prefixed keys\n    },\n  },\n  items: [\n    {\n      id: \"1\",\n      title: \"Post\",\n      content: \"<p>Hello</p>\",\n      extensions: {\n        xmlElements: [\n          { tag: \"georss:point\", children: [\"40.714 -74.006\"] } as XmlNode,\n        ],\n        jsonProperties: { _reading_time: 5 },\n      },\n    },\n  ],\n};\n\nconst rss = generateFeed(feed, \"rss\");\n// RSS output includes xmlns:georss and <georss:point> elements\n```\n\nValidation enforces that XML namespace keys start with `xmlns:` and JSON Feed extension keys start with `_` per the JSON Feed spec.\n\n---\n\n## XSLT Stylesheets\n\nMake RSS/Atom feeds render nicely in browsers by adding an XSLT stylesheet reference:\n\n```typescript\nconst rss = generateFeed(feed, \"rss\", { stylesheet: \"/feed.xsl\" });\n// Output: <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n//         <?xml-stylesheet type=\"text/xsl\" href=\"/feed.xsl\"?>\n//         <rss ...>\n```\n\nThe `stylesheet` option is ignored for JSON Feed output.\n\n---\n\n## WebSub Hub\n\nDeclare a WebSub hub for real-time push notifications of feed updates:\n\n```typescript\nconst feed = {\n  title: \"My Blog\",\n  link: \"https://example.com\",\n  description: \"A blog.\",\n  feedLinks: {\n    rss: \"https://example.com/rss.xml\",\n    hub: \"https://hub.example.com\",\n  },\n  items: [/* ... */],\n};\n\n// RSS: <atom:link rel=\"hub\" href=\"https://hub.example.com\" />\n// Atom: <link rel=\"hub\" href=\"https://hub.example.com\" />\n// JSON: \"hubs\": [{ \"type\": \"WebSub\", \"url\": \"https://hub.example.com\" }]\n```\n\n---\n\n## Feed Data Reference\n\n### `Feed`\n\n| Field | Type | Required | Description |\n|---|---|---|---|\n| `title` | `string` | Yes | Feed title |\n| `link` | `string` | Yes | Site URL (validated) |\n| `description` | `string` | RSS only | Feed description (required for RSS) |\n| `id` | `string` | No | Feed ID (defaults to `link`) |\n| `language` | `string` | No | Language code (e.g., `\"en-us\"`) |\n| `copyright` | `string` | No | Copyright notice |\n| `updated` | `string \\| Date` | No | Last update (auto-derived from items if omitted) |\n| `generator` | `string` | No | Generator name |\n| `image` | `FeedImage` | No | Feed image/logo |\n| `favicon` | `string` | No | Favicon URL (JSON Feed) |\n| `authors` | `FeedAuthor[]` | No | Feed authors |\n| `categories` | `FeedCategory[]` | No | Feed categories |\n| `items` | `FeedItem[]` | Yes | Feed items/entries |\n| `ttl` | `number` | No | Time-to-live in minutes (RSS cache hint) |\n| `skipHours` | `number[]` | No | Hours (0--23) aggregators can skip (RSS) |\n| `skipDays` | `string[]` | No | Day names aggregators can skip (RSS) |\n| `nextUrl` | `string` | No | Next page URL for paginated feeds |\n| `podcast` | `PodcastFeed` | No | Podcast metadata (RSS only) |\n| `feedLinks` | `object` | No | Self-referencing URLs (`{ rss?, atom?, json?, hub? }`) |\n| `extensions` | `FeedExtensions` | No | Custom XML namespaces, elements, and JSON properties |\n\n### `FeedItem`\n\n| Field | Type | Required | Description |\n|---|---|---|---|\n| `id` | `string` | Yes | Unique item identifier |\n| `title` | `string` | No | Item title |\n| `link` | `string` | No | Item URL (validated) |\n| `content` | `string` | No | HTML content |\n| `contentText` | `string` | No | Plain text content (JSON Feed) |\n| `summary` | `string` | No | Short summary |\n| `date` | `string \\| Date` | No | Publication date (validated) |\n| `updated` | `string \\| Date` | No | Last modified date (validated) |\n| `authors` | `FeedAuthor[]` | No | Item authors (all rendered in RSS) |\n| `categories` | `FeedCategory[]` | No | Item categories/tags |\n| `image` | `string` | No | Item image URL (RSS, Atom, JSON Feed) |\n| `enclosures` | `FeedEnclosure[]` | No | Attached media (podcasts, etc.) |\n| `language` | `string` | No | Item language (JSON Feed) |\n| `podcast` | `PodcastItem` | No | Podcast episode metadata (RSS only) |\n| `extensions` | `FeedItemExtensions` | No | Custom XML elements and JSON properties |\n\n### Supporting Types\n\n```typescript\ntype FeedAuthor = {\n  name: string;\n  url?: string;\n  email?: string;\n  avatar?: string;\n};\n\ntype FeedCategory = {\n  name: string;\n  domain?: string;\n  label?: string;\n};\n\ntype FeedEnclosure = {\n  url: string;\n  type: string;       // MIME type\n  length?: number;     // Size in bytes (required for RSS)\n  title?: string;\n  duration?: number;   // Duration in seconds\n};\n\ntype FeedImage = {\n  url: string;\n  title?: string;\n  link?: string;\n  width?: number;\n  height?: number;\n};\n```\n\n---\n\n## API Reference\n\n### `generateFeed(feed, format, options?): string`\n\nGenerate a feed in a single format.\n\n- **`format`**: `\"rss\"` | `\"atom\"` | `\"json\"`\n- **`options`**: `{ sanitize?: boolean, pretty?: boolean, stylesheet?: string }`\n- **Returns**: XML string (RSS/Atom) or JSON string\n- **Throws**: `FeedValidationError` if feed data is invalid\n\n### `generateFeeds(feed, options?): FeedOutput`\n\nGenerate all three formats at once.\n\n- **Returns**: `{ rss: string, atom: string, json: string }`\n- **Throws**: `FeedValidationError` if feed data is invalid\n\n### `FeedBuilder`\n\nFluent builder for constructing feeds incrementally.\n\n| Method | Description |\n|---|---|\n| `.title(s)` | Set feed title |\n| `.link(s)` | Set feed link |\n| `.description(s)` | Set feed description |\n| `.id(s)` | Set feed ID |\n| `.language(s)` | Set language code |\n| `.copyright(s)` | Set copyright notice |\n| `.updated(s)` | Set last update date |\n| `.generator(s)` | Set generator name |\n| `.image(img)` | Set feed image |\n| `.favicon(s)` | Set favicon URL |\n| `.author(a)` | Add an author (can be called multiple times) |\n| `.category(c)` | Add a category (can be called multiple times) |\n| `.nextUrl(s)` | Set pagination URL |\n| `.ttl(n)` | Set TTL in minutes |\n| `.skipHours(h[])` | Set skip hours |\n| `.skipDays(d[])` | Set skip days |\n| `.podcast(p)` | Set podcast metadata |\n| `.feedLinks(links)` | Set self-referencing URLs |\n| `.hub(url)` | Set WebSub hub URL |\n| `.extensions(ext)` | Set custom extensions |\n| `.stylesheet(url)` | Set XSLT stylesheet URL |\n| `.addItem(item)` | Add a single item |\n| `.addItems(items)` | Add multiple items |\n| `.build()` | Return the raw `Feed` object |\n| `.generate(format, options?)` | Render a single format |\n| `.generateAll(options?)` | Render all three formats |\n\n### `FeedValidationError`\n\nStructured error class thrown on invalid feed data.\n\n- **`field`**: `string` -- path to the invalid field (e.g., `\"items[0].link\"`)\n- **`reason`**: `string` -- human-readable description of the issue\n\n---\n\n## Example Output\n\n### RSS\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n     xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n  <channel>\n    <title>My Blog</title>\n    <link>https://example.com</link>\n    <description>The latest articles from my blog.</description>\n    <item>\n      <title>First Post</title>\n      <link>https://example.com/post-1</link>\n      <guid isPermaLink=\"true\">https://example.com/post-1</guid>\n      <description><![CDATA[<p>The full content of the first post.</p>]]></description>\n      <content:encoded><![CDATA[<p>The full content of the first post.</p>]]></content:encoded>\n      <pubDate>Sat, 14 Dec 2024 10:00:00 GMT</pubDate>\n    </item>\n  </channel>\n</rss>\n```\n\n### Atom\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<feed xmlns=\"http://www.w3.org/2005/Atom\">\n  <title type=\"text\">My Blog</title>\n  <link href=\"https://example.com\" rel=\"alternate\" type=\"text/html\" />\n  <id>https://example.com</id>\n  <updated>2024-12-14T10:00:00.000Z</updated>\n  <entry>\n    <title type=\"text\">First Post</title>\n    <link href=\"https://example.com/post-1\" rel=\"alternate\" type=\"text/html\" />\n    <id>https://example.com/post-1</id>\n    <published>2024-12-14T10:00:00.000Z</published>\n    <content type=\"html\">&lt;p&gt;The full content of the first post.&lt;/p&gt;</content>\n    <summary type=\"text\">A summary of the first post.</summary>\n  </entry>\n</feed>\n```\n\n### JSON Feed\n\n```json\n{\n  \"version\": \"https://jsonfeed.org/version/1.1\",\n  \"title\": \"My Blog\",\n  \"home_page_url\": \"https://example.com\",\n  \"items\": [\n    {\n      \"id\": \"https://example.com/post-1\",\n      \"url\": \"https://example.com/post-1\",\n      \"title\": \"First Post\",\n      \"content_html\": \"<p>The full content of the first post.</p>\",\n      \"summary\": \"A summary of the first post.\",\n      \"date_published\": \"2024-12-14T10:00:00.000Z\"\n    }\n  ]\n}\n```\n\n---\n\n## Feed Validation\n\n- **RSS/Atom:** [W3C Feed Validator](https://validator.w3.org/feed/)\n- **JSON Feed:** [JSON Feed Validator](https://validator.jsonfeed.org/)\n\n---\n\n## Upgrading from 2.0\n\nv2.1 is fully backwards compatible. No code changes are required.\n\n**What changes in output:**\n- RSS feeds with `item.content` now include an additional `<content:encoded>` element alongside the existing `<description>`. The `<description>` element is unchanged.\n- RSS feeds with `item.image` now include `<media:content>` elements and the `xmlns:media` namespace declaration.\n- Atom feeds with `item.image` now include an enclosure `<link>` element.\n\nThese are additive changes. Existing feed consumers will not be affected. If you need the previous output exactly, pin to `2.0.x`.\n\n**New optional features** (opt-in, no effect unless used):\n- `extensions` on `Feed` and `FeedItem` for custom namespaces and elements\n- `stylesheet` in `FeedOptions` for XSLT support\n- `feedLinks.hub` for WebSub hub declarations\n\nSee the full [CHANGELOG](./CHANGELOG.md) for details.\n\n---\n\n## Contributing\n\nContributions are welcome! If you'd like to improve this package:\n\n1. Fork the repository.\n2. Create a new branch:\n   ```bash\n   git checkout -b feature-name\n   ```\n3. Make your changes and commit:\n   ```bash\n   git commit -m \"Add new feature\"\n   ```\n4. Push your branch and open a Pull Request.\n\n---\n\n## More Information\n\nThis package is built and maintained by [Bliztek](https://bliztek.com). We design and develop customized websites and web applications to help businesses of all sizes achieve their goals. Our solutions prioritize scalability, security, and performance to drive growth and ensure long-term success.\n\n- Follow [@Dev4TheWeb](https://x.com/Dev4TheWeb) on Twitter/X for updates from the creator\n- Follow [@Bliztek](https://x.com/bliztek) on Twitter/X for updates from the company side\n- Read our [Company blog](https://bliztek.com/blog) to learn more about our contributions to open source!\n- Read my [Personal blog](https://stevendevelops.com) to learn more about what I do!\n\n---\n\n## License\n\nThis package is licensed under the [MIT License](./LICENSE).\n","readmeFilename":"README.md"}