diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..63e7ae6
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+**/*.ts
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..6e581af
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,98 @@
+///
+
+import stream = require("stream");
+
+export = FeedParser;
+
+declare class FeedParser extends stream.Transform {
+ constructor(options?: FeedParser.Options);
+ meta: FeedParser.Meta;
+ options: FeedParser.Options;
+
+ read(): FeedParser.Item | null;
+ resumeSaxError(): void;
+
+ on(event: 'meta', listener: (meta: FeedParser.Meta) => void): this;
+ on(event: 'readable', listener: (this: FeedParser) => void): this;
+ on(event: 'error', listener: (error: Error) => void): this;
+ on(event: string, listener: (...args: any[]) => void): this;
+
+ addListener(event: 'meta', listener: (meta: FeedParser.Meta) => void): this;
+ addListener(event: 'readable', listener: (this: FeedParser) => void): this;
+ addListener(event: 'error', listener: (error: Error) => void): this;
+ addListener(event: string, listener: (...args: any[]) => void): this;
+
+ once(event: 'meta', listener: (meta: FeedParser.Meta) => void): this;
+ once(event: 'readable', listener: (this: FeedParser) => void): this;
+ once(event: 'error', listener: (error: Error) => void): this;
+ once(event: string, listener: (...args: any[]) => void): this;
+}
+
+declare namespace FeedParser {
+ type Type = "atom" | "rss" | "rdf";
+
+ interface Options {
+ strict?: boolean;
+ normalize?: boolean;
+ addmeta?: boolean;
+ feedurl?: string;
+ resume_saxerror?: boolean;
+ MAX_BUFFER_LENGTH?: number;
+ }
+
+ interface Image {
+ url: string;
+ title: string;
+ }
+
+ interface Meta {
+ "#ns": Array<{ [key: string]: string }>;
+ "#type": Type;
+ "#version": string;
+ "@": { [key: string]: any };
+ title: string;
+ description: string;
+ date: Date | null;
+ pubdate: Date | null;
+ link: string;
+ xmlurl: string;
+ author: string;
+ language: string;
+ image: Image;
+ favicon: string;
+ copyright: string;
+ generator: string;
+ categories: string[];
+ [key: string]: any;
+ }
+
+ interface Enclosure {
+ url: string;
+ type?: string;
+ length?: string;
+ }
+
+ interface Source {
+ title: string;
+ url: string;
+ }
+
+ interface Item {
+ title: string;
+ description: string;
+ summary: string;
+ date: Date | null;
+ pubdate: Date | null;
+ link: string;
+ origlink: string;
+ author: string;
+ guid: string;
+ comments: string;
+ image: { url: string };
+ categories: string[];
+ source: Source;
+ enclosures: Enclosure[];
+ meta: Meta;
+ [key: string]: any;
+ }
+}
diff --git a/lib/feedparser/index.js b/lib/feedparser/index.js
index 916356f..da2861c 100644
--- a/lib/feedparser/index.js
+++ b/lib/feedparser/index.js
@@ -55,8 +55,8 @@ var sax = require('sax')
* - generator {String}
* - categories {Array}
*
- * @param {Object} options
- * @api public
+ * @this {FeedParserInstance}
+ * @param {import('../../index').Options} [options]
*/
function FeedParser (options) {
if (!(this instanceof FeedParser)) return new FeedParser(options);
@@ -72,9 +72,13 @@ function FeedParser (options) {
if (!('normalize' in this.options)) this.options.normalize = true;
if (!('addmeta' in this.options)) this.options.addmeta = true;
if (!('resume_saxerror' in this.options)) this.options.resume_saxerror = true;
+ // MAX_BUFFER_LENGTH is not part of the public API of sax, but we need to be
+ // able to handle nodes that are larger than the 64K default
if ('MAX_BUFFER_LENGTH' in this.options) {
+ // @ts-expect-error - private API of sax
sax.MAX_BUFFER_LENGTH = this.options.MAX_BUFFER_LENGTH; // set to Infinity to have unlimited buffers
} else {
+ // @ts-expect-error
sax.MAX_BUFFER_LENGTH = 16 * 1024 * 1024; // 16M versus the 64K default
}
if (this.options.feedurl) this.xmlbase.unshift({ '#name': 'xml', '#': this.options.feedurl});
@@ -96,6 +100,7 @@ util.inherits(FeedParser, TransformStream);
*
* Initializes the class-variables
*/
+/** @this {FeedParserInstance} */
FeedParser.prototype.init = function (){
this.meta = {
'#ns': [],
@@ -113,6 +118,7 @@ FeedParser.prototype.init = function (){
this.errors = [];
};
+/** @this {FeedParserInstance} */
FeedParser.prototype.handleEnd = function (){
// We made it to the end without throwing, but let's make sure we were actually
// parsing a feed
@@ -123,6 +129,7 @@ FeedParser.prototype.handleEnd = function (){
this.push(null);
};
+/** @this {FeedParserInstance} */
FeedParser.prototype.handleSaxError = function (e) {
this.emit('error', e);
if (this.options.resume_saxerror) {
@@ -130,6 +137,7 @@ FeedParser.prototype.handleSaxError = function (e) {
}
};
+/** @this {FeedParserInstance} */
FeedParser.prototype.resumeSaxError = function () {
if (this.stream._parser) {
this.stream._parser.error = null;
@@ -137,12 +145,17 @@ FeedParser.prototype.resumeSaxError = function () {
}
};
+/** @this {FeedParserInstance} */
FeedParser.prototype.handleError = function (e){
this.emit('error', e);
};
// parses the xml declaration, which looks like:
//
+/**
+ * @this {FeedParserInstance}
+ * @param {SaxProcessingInstruction} node
+ */
FeedParser.prototype.handleProcessingInstruction = function (node) {
if (node.name === 'xml') {
this.meta['#xml'] = node.body.trim().split(/\s+/).reduce(function (map, attr) {
@@ -155,6 +168,10 @@ FeedParser.prototype.handleProcessingInstruction = function (node) {
}
};
+/**
+ * @this {FeedParserInstance}
+ * @param {import('sax').QualifiedTag} node
+ */
FeedParser.prototype.handleOpenTag = function (node){
var n = {};
n['#name'] = node.name; // Avoid namespace collissions later...
@@ -204,6 +221,7 @@ FeedParser.prototype.handleOpenTag = function (node){
this.stack.unshift(n);
};
+/** @this {FeedParserInstance} */
FeedParser.prototype.handleCloseTag = function (el){
var node = {
'#name': el,
@@ -355,6 +373,10 @@ FeedParser.prototype.handleCloseTag = function (el){
}
};
+/**
+ * @this {FeedParserInstance}
+ * @param {string} text
+ */
FeedParser.prototype.handleText = function (text){
if (this.in_xhtml) {
this.xhtml['#'] += text;
@@ -369,6 +391,12 @@ FeedParser.prototype.handleText = function (text){
}
};
+/**
+ * @this {FeedParserInstance}
+ * @param {Object.} attrs
+ * @param {string} el
+ * @returns {Object.}
+ */
FeedParser.prototype.handleAttributes = function handleAttributes (attrs, el) {
/*
* Using the sax.js option { xmlns: true }
@@ -382,14 +410,14 @@ FeedParser.prototype.handleAttributes = function handleAttributes (attrs, el) {
*/
var basepath = ''
- , simplifiedAttributes = {}
+ , simplifiedAttributes = /** @type {Object.} */ ({})
;
if (this.xmlbase && this.xmlbase.length) {
basepath = this.xmlbase[0]['#'];
}
- Object.keys(attrs).forEach(function(key){
+ Object.keys(attrs).forEach(/** @this {FeedParserInstance} */ function(key){
var attr = attrs[key]
, ns = {}
, prefix = ''
@@ -425,6 +453,13 @@ FeedParser.prototype.handleAttributes = function handleAttributes (attrs, el) {
return simplifiedAttributes;
};
+/**
+ * @this {FeedParserInstance}
+ * @param {ParsedNode} node
+ * @param {import('../../index').Type} type
+ * @param {import('../../index').Options} options
+ * @returns {Object}
+ */
FeedParser.prototype.handleMeta = function handleMeta (node, type, options) {
if (!type || !node) return {};
@@ -772,6 +807,13 @@ FeedParser.prototype.handleMeta = function handleMeta (node, type, options) {
return meta;
};
+/**
+ * @this {FeedParserInstance}
+ * @param {ParsedNode} node
+ * @param {import('../../index').Type} type
+ * @param {import('../../index').Options} options
+ * @returns {Object}
+ */
FeedParser.prototype.handleItem = function handleItem (node, type, options){
if (!type || !node) return {};
@@ -830,7 +872,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){
if (link['@']['rel'] == 'self' && !item.link) item.link = link['@']['href'];
if (link['@']['rel'] == 'replies') item.comments = link['@']['href'];
if (link['@']['rel'] == 'enclosure') {
- enclosure = {};
+ enclosure = /** @type {import('../../index').Enclosure} */ ({});
enclosure.url = link['@']['href'];
enclosure.type = _.get(link['@'], 'type');
enclosure.length = _.get(link['@'], 'length');
@@ -853,7 +895,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){
if (el['@']['rel'] == 'self' && !item.link) item.link = el['@']['href'];
if (el['@']['rel'] == 'replies') item.comments = el['@']['href'];
if (el['@']['rel'] == 'enclosure') {
- enclosure = {};
+ enclosure = /** @type {import('../../index').Enclosure} */ ({});
enclosure.url = el['@']['href'];
enclosure.type = _.get(el['@'], 'type');
enclosure.length = _.get(el['@'], 'length');
@@ -932,7 +974,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){
case('enclosure'):
if (Array.isArray(el)) {
el.forEach(function (enc){
- enclosure = {};
+ enclosure = /** @type {import('../../index').Enclosure} */ ({});
enclosure.url = _.get(enc['@'], 'url');
enclosure.type = _.get(enc['@'], 'type');
enclosure.length = _.get(enc['@'], 'length');
@@ -943,7 +985,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){
}
});
} else {
- enclosure = {};
+ enclosure = /** @type {import('../../index').Enclosure} */ ({});
enclosure.url = _.get(el['@'], 'url');
enclosure.type = _.get(el['@'], 'type');
enclosure.length = _.get(el['@'], 'length');
@@ -958,7 +1000,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){
var optionalAttributes = ['bitrate', 'framerate', 'samplingrate', 'duration', 'height', 'width'];
if (Array.isArray(el)) {
el.forEach(function (enc){
- enclosure = {};
+ enclosure = /** @type {import('../../index').Enclosure} */ ({});
enclosure.url = _.get(enc['@'], 'url');
enclosure.type = _.get(enc['@'], 'type') || _.get(enc['@'], 'medium');
enclosure.length = _.get(enc['@'], 'filesize');
@@ -976,7 +1018,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){
}
});
} else {
- enclosure = {};
+ enclosure = /** @type {import('../../index').Enclosure} */ ({});
enclosure.url = _.get(el['@'], 'url');
enclosure.type = _.get(el['@'], 'type') || _.get(el['@'], 'medium');
enclosure.length = _.get(el['@'], 'filesize');
@@ -1112,6 +1154,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){
};
// Naive Stream API
+/** @this {FeedParserInstance} */
FeedParser.prototype._transform = function (data, encoding, done) {
try {
this.stream.write(data);
@@ -1123,6 +1166,7 @@ FeedParser.prototype._transform = function (data, encoding, done) {
}
};
+/** @this {FeedParserInstance} */
FeedParser.prototype._flush = function (done) {
try {
this.stream.end();
@@ -1133,4 +1177,64 @@ FeedParser.prototype._flush = function (done) {
}
};
+/**
+ * @typedef {Object} ParsedNode
+ * The internal accumulator object that handleOpenTag builds and pushes onto
+ * this.stack. Keys accumulate as child elements are parsed. String keys
+ * '#name', '#prefix', '#local', '#uri' hold element namespace info; '@' holds
+ * simplified attributes; '#' holds text content. Named keys hold child element
+ * values which may be strings, nested ParsedNodes, or arrays of either.
+ */
+
+/**
+ * @typedef {Object} XmlBaseEntry
+ * An entry in the this.xmlbase stack. The '#name' key holds the element name
+ * that established the base URL; the '#' key holds the xml:base URL value.
+ */
+
+/**
+ * @typedef {Object} SaxProcessingInstruction
+ * Payload of the sax 'processinginstruction' event.
+ * @property {string} name - Processing instruction target, e.g. "xml"
+ * @property {string} body - The rest of the processing instruction content
+ */
+
+/**
+ * @typedef {Object} AddressParserResult
+ * Shape of each item returned by the addressparser module.
+ * @property {string} [name]
+ * @property {string} [address]
+ */
+
+/**
+ * @typedef {Object} FeedParserState
+ * Instance properties set up by FeedParser.prototype.init and the constructor.
+ * @property {Object} meta - Parsed feed metadata; shape evolves during parsing
+ * @property {import('../../index').Options} options
+ * @property {Object.} _namespaces
+ * @property {boolean} _emitted_meta
+ * @property {Array.} stack
+ * @property {Array.} xmlbase
+ * @property {boolean} in_xhtml
+ * @property {Object} xhtml
+ * @property {Error[]} errors
+ * @property {import('sax').SAXStream} stream - The underlying sax stream
+ * @property {function(): void} init
+ * @property {function(): void} handleEnd
+ * @property {function(Error): void} handleSaxError
+ * @property {function(): void} resumeSaxError
+ * @property {function(Error): void} handleError
+ * @property {function(SaxProcessingInstruction): void} handleProcessingInstruction
+ * @property {function(import('sax').QualifiedTag): void} handleOpenTag
+ * @property {function(string): void} handleCloseTag
+ * @property {function(string): void} handleText
+ * @property {function(Object., string): Object.} handleAttributes
+ * @property {function(ParsedNode, import('../../index').Type, import('../../index').Options): Object} handleMeta
+ * @property {function(ParsedNode, import('../../index').Type, import('../../index').Options): Object} handleItem
+ */
+
+/**
+ * @typedef {import('readable-stream').Transform & FeedParserState} FeedParserInstance
+ */
+
exports = module.exports = FeedParser;
diff --git a/lib/utils.js b/lib/utils.js
index 86a8a10..5142716 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -20,8 +20,9 @@ var _get = require('lodash.get');
* // => 'baz'
*
* @param {Object} obj
- * @param {String} [subkey="#"] By default, use the '#' key, but you may pass any key you like
- * @return Returns the value of the selected key or 'null' if undefined.
+ * @param {string} [subkey="#"] By default, use the '#' key, but you may pass any key you like
+ * @param {*} [defaultValue=null]
+ * @returns {*} The value of the selected key, or null if undefined.
* @private
*/
function get(obj, subkey, defaultValue) {
@@ -44,6 +45,8 @@ exports.get = get;
/**
* Safely trim a value if it's a String
+ * @param {*} val
+ * @returns {*}
* @private
*/
function safeTrim (val) {
@@ -58,6 +61,9 @@ exports.safeTrim = safeTrim;
* Resolve a URL against a base URL, returning the original pathUrl if
* either parameter isn't provided or if the URL is not resolvable (e.g.
* tag: URIs and other non-http schemes that the URL constructor rejects).
+ * @param {string} baseUrl
+ * @param {string} pathUrl
+ * @returns {string}
* @private
*/
function resolve (baseUrl, pathUrl) {
@@ -72,7 +78,8 @@ exports.resolve = resolve;
/*
* Check whether a given uri is an absolute URL
- * @param {String} uri
+ * @param {string} uri
+ * @returns {boolean}
* @private
*/
function isAbsoluteUrl (uri) {
@@ -88,9 +95,9 @@ exports.isAbsoluteUrl = isAbsoluteUrl;
/*
* Check whether a given namespace URI matches the given default
*
- * @param {String} URI
- * @param {String} default, e.g., 'atom'
- * @return {Boolean}
+ * @param {string} uri
+ * @param {string} def - e.g., 'atom'
+ * @returns {boolean}
* @private
*/
function nslookup (uri, def) {
@@ -101,8 +108,8 @@ exports.nslookup = nslookup;
/*
* Return the "default" namespace prefix for a given namespace URI
*
- * @param {String} URI
- * @return {String}
+ * @param {string} uri
+ * @returns {string}
* @private
*/
function nsprefix (uri) {
@@ -114,8 +121,8 @@ exports.nsprefix = nsprefix;
* Walk a node and re-resolve the urls using the given baseurl
*
* @param {Object} node
- * @param {String} baseurl
- * @return {Object} modified node
+ * @param {string} baseurl
+ * @returns {Object|false} modified node, or false if no node or baseurl
* @private
*/
function reresolve (node, baseurl) {
@@ -173,7 +180,8 @@ exports.reresolve = reresolve;
* Pulled out of node-resanitize because it was all that was being used
* and it's way lighter...
*
- * @param {String} str
+ * @param {string} str
+ * @returns {string}
* @private
*/
function stripHtml (str) {
diff --git a/package-lock.json b/package-lock.json
index e8c5821..46a783a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,10 +23,14 @@
"feedparser": "bin/feedparser.js"
},
"devDependencies": {
+ "@types/node": "^25.5.0",
+ "@types/readable-stream": "^2.3.15",
+ "@types/sax": "^1.2.7",
"eslint": "^6.8.0",
"iconv-lite": "^0.5.1",
"mocha": "^7.1.2",
- "node-fetch": "^2.6.0"
+ "node-fetch": "^2.6.0",
+ "typescript": "^5.9.3"
},
"engines": {
"node": ">= 10.18.1"
@@ -64,6 +68,37 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
+ "node_modules/@types/node": {
+ "version": "25.5.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
+ "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/@types/readable-stream": {
+ "version": "2.3.15",
+ "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz",
+ "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "node_modules/@types/sax": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
+ "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/acorn": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
@@ -2230,6 +2265,27 @@
"node": ">=8"
}
},
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
diff --git a/package.json b/package.json
index f2c544b..0a242c8 100644
--- a/package.json
+++ b/package.json
@@ -26,8 +26,10 @@
"url": "http://github.com/danmactough/node-feedparser/issues"
},
"main": "index.js",
+ "types": "./index.d.ts",
"files": [
"index.js",
+ "index.d.ts",
"bin/*.js",
"lib"
],
@@ -46,14 +48,20 @@
"sax": ">=1.2.4 <1.4.4"
},
"devDependencies": {
+ "@types/node": "^25.5.0",
+ "@types/readable-stream": "^2.3.15",
+ "@types/sax": "^1.2.7",
"eslint": "^6.8.0",
"iconv-lite": "^0.5.1",
"mocha": "^7.1.2",
- "node-fetch": "^2.6.0"
+ "node-fetch": "^2.6.0",
+ "typescript": "^5.9.3"
},
"scripts": {
"lint": "eslint .",
- "pretest": "npm run lint",
+ "typecheck": "node -e \"process.exit(process.release.lts < 'Gallium' ? 0 : 1)\" || tsc",
+ "typecheck:src": "node -e \"process.exit(process.release.lts < 'Gallium' ? 0 : 1)\" || tsc --project tsconfig.src.json",
+ "pretest": "npm run lint && npm run typecheck && npm run typecheck:src",
"test": "mocha",
"version": "git changelog ; git add History.md"
},
diff --git a/test/types.ts b/test/types.ts
new file mode 100644
index 0000000..883ba4d
--- /dev/null
+++ b/test/types.ts
@@ -0,0 +1,77 @@
+import FeedParser = require('../index');
+
+// Constructor with no options
+const fp1 = new FeedParser();
+
+// Constructor with all options
+const fp2 = new FeedParser({
+ strict: false,
+ normalize: true,
+ addmeta: true,
+ feedurl: 'https://example.com/feed',
+ resume_saxerror: true,
+ MAX_BUFFER_LENGTH: 1024 * 1024,
+});
+
+// meta event
+fp2.on('meta', (meta: FeedParser.Meta) => {
+ const title: string = meta.title;
+ const description: string = meta.description;
+ const type: FeedParser.Type = meta['#type'];
+ const version: string = meta['#version'];
+ const ns: Array<{ [key: string]: string }> = meta['#ns'];
+ const attrs: { [key: string]: any } = meta['@'];
+ const date: Date | null = meta.date;
+ const pubdate: Date | null = meta.pubdate;
+ const link: string = meta.link;
+ const xmlurl: string = meta.xmlurl;
+ const author: string = meta.author;
+ const language: string = meta.language;
+ const image: FeedParser.Image = meta.image;
+ const imageUrl: string = image.url;
+ const favicon: string = meta.favicon;
+ const copyright: string = meta.copyright;
+ const generator: string = meta.generator;
+ const categories: string[] = meta.categories;
+ // namespace-prefixed properties via index signature
+ const itunesAuthor = meta['itunes:author'];
+});
+
+// error event
+fp2.on('error', (err: Error) => {
+ err.message;
+});
+
+// readable event + read()
+fp2.on('readable', () => {
+ let item: FeedParser.Item | null;
+ while ((item = fp2.read()) !== null) {
+ const title: string = item.title;
+ const description: string = item.description;
+ const summary: string = item.summary;
+ const date: Date | null = item.date;
+ const pubdate: Date | null = item.pubdate;
+ const link: string = item.link;
+ const origlink: string = item.origlink;
+ const author: string = item.author;
+ const guid: string = item.guid;
+ const comments: string = item.comments;
+ const image: { url: string } = item.image;
+ const categories: string[] = item.categories;
+ const source: FeedParser.Source = item.source;
+ const sourceTitle: string = source.title;
+ const sourceUrl: string = source.url;
+ const enclosures: FeedParser.Enclosure[] = item.enclosures;
+ const enc: FeedParser.Enclosure = enclosures[0];
+ const encUrl: string = enc.url;
+ const meta: FeedParser.Meta = item.meta;
+ // namespace-prefixed properties via index signature
+ const mediaContent = item['media:content'];
+ }
+});
+
+// once and addListener overloads
+fp2.once('meta', (_meta: FeedParser.Meta) => {});
+fp2.addListener('error', (_err: Error) => {});
+
+fp2.resumeSaxError();
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..292ba13
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "noEmit": true,
+ "strict": true,
+ "module": "commonjs",
+ "lib": ["es6"]
+ },
+ "files": [
+ "test/types.ts"
+ ]
+}
diff --git a/tsconfig.src.json b/tsconfig.src.json
new file mode 100644
index 0000000..960cc43
--- /dev/null
+++ b/tsconfig.src.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "noEmit": true,
+ "checkJs": true,
+ "allowJs": true,
+ "strict": false,
+ "noImplicitAny": false,
+ "noImplicitThis": false,
+ "strictNullChecks": false,
+ "module": "commonjs",
+ "lib": ["es6"],
+ "target": "es6"
+ },
+ "include": ["index.js", "lib/**/*.js"],
+ "exclude": ["node_modules", "test", "bin"]
+}