@molecule/api-feed-rss-parser 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Normalize a value that may be a single item or an array into a definite array.
3
+ * `undefined` and `null` collapse to `[]` so callers can iterate unconditionally.
4
+ *
5
+ * @param value - The (possibly array, possibly nullish, possibly scalar) value.
6
+ * @returns A non-null array. Never returns `undefined`.
7
+ */
8
+ export declare function toArray<T>(value: T | T[] | undefined | null): T[];
9
+ /**
10
+ * Coerce a (possibly array-valued) fast-xml-parser node to its first usable
11
+ * text value. Mirrors {@link textOf} but transparently unwraps arrays — which
12
+ * the parser produces for any element listed in `isArray`.
13
+ *
14
+ * @param value - The fast-xml-parser node — string, primitive, object, array, or nullish.
15
+ * @returns Trimmed first text value, or `undefined` when no usable value exists.
16
+ */
17
+ export declare function firstText(value: unknown): string | undefined;
18
+ /**
19
+ * Coerce a fast-xml-parser node to a string.
20
+ *
21
+ * fast-xml-parser may emit a leaf as either:
22
+ * - a primitive (`string` / `number` / `boolean`)
23
+ * - an object with `#text` (when the element has attributes alongside content)
24
+ * - an object with attribute keys (when `textNodeName` differs)
25
+ *
26
+ * This helper handles all three. Returns `undefined` when no usable value exists.
27
+ *
28
+ * @param value - The fast-xml-parser leaf value.
29
+ * @returns Trimmed string or `undefined`.
30
+ */
31
+ export declare function textOf(value: unknown): string | undefined;
32
+ /**
33
+ * Extract an attribute value (e.g. `href`, `url`) from a fast-xml-parser node.
34
+ *
35
+ * Attributes are exposed under the `attributeNamePrefix` ('@_' by default).
36
+ * This helper is tolerant of missing attribute objects.
37
+ *
38
+ * @param value - The fast-xml-parser node (object, primitive, or undefined).
39
+ * @param name - Attribute name without the prefix (e.g. `'href'`).
40
+ * @returns Attribute value as a trimmed string, or `undefined`.
41
+ */
42
+ export declare function attrOf(value: unknown, name: string): string | undefined;
43
+ /**
44
+ * Parse a date-ish string into an ISO 8601 timestamp.
45
+ *
46
+ * Accepts RFC 822 (RSS `pubDate`), RFC 3339 (Atom / JSON Feed), and most
47
+ * common variants the JavaScript `Date` constructor handles natively.
48
+ *
49
+ * @param value - Possibly a date string, or `undefined`.
50
+ * @returns ISO 8601 string when parseable, else `undefined`.
51
+ */
52
+ export declare function toIsoDate(value: string | undefined): string | undefined;
53
+ /**
54
+ * Parse iTunes podcast `<itunes:duration>` formats into seconds.
55
+ *
56
+ * Accepted shapes:
57
+ * - bare integer seconds: `"3600"`
58
+ * - `MM:SS`: `"42:30"`
59
+ * - `HH:MM:SS`: `"01:42:30"`
60
+ *
61
+ * @param value - The duration string, or `undefined`.
62
+ * @returns Duration in seconds, or `undefined` when unparseable.
63
+ */
64
+ export declare function parseItunesDuration(value: string | undefined): number | undefined;
65
+ /**
66
+ * Compute a stable identifier when a feed item has neither a GUID nor a link.
67
+ *
68
+ * Uses SHA-1 over `title + '' + publishedAt` to give a deterministic,
69
+ * compact key. Collisions are tolerable here — this is a deduplication aid,
70
+ * not a security primitive.
71
+ *
72
+ * @param title - Item title (may be empty).
73
+ * @param publishedAt - Pre-parsed ISO 8601 publication date, if any.
74
+ * @returns Hex-encoded SHA-1 digest. Always 40 characters.
75
+ */
76
+ export declare function synthesizeItemId(title: string, publishedAt: string | undefined): string;
77
+ //# sourceMappingURL=utilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,GAAG,IAAI,GAAG,CAAC,EAAE,CAGjE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAS5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAuBzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAavE;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAejF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAMvF"}
@@ -0,0 +1,162 @@
1
+ import { createHash } from 'node:crypto';
2
+ /**
3
+ * Normalize a value that may be a single item or an array into a definite array.
4
+ * `undefined` and `null` collapse to `[]` so callers can iterate unconditionally.
5
+ *
6
+ * @param value - The (possibly array, possibly nullish, possibly scalar) value.
7
+ * @returns A non-null array. Never returns `undefined`.
8
+ */
9
+ export function toArray(value) {
10
+ if (value == null)
11
+ return [];
12
+ return Array.isArray(value) ? value : [value];
13
+ }
14
+ /**
15
+ * Coerce a (possibly array-valued) fast-xml-parser node to its first usable
16
+ * text value. Mirrors {@link textOf} but transparently unwraps arrays — which
17
+ * the parser produces for any element listed in `isArray`.
18
+ *
19
+ * @param value - The fast-xml-parser node — string, primitive, object, array, or nullish.
20
+ * @returns Trimmed first text value, or `undefined` when no usable value exists.
21
+ */
22
+ export function firstText(value) {
23
+ if (Array.isArray(value)) {
24
+ for (const candidate of value) {
25
+ const text = textOf(candidate);
26
+ if (text)
27
+ return text;
28
+ }
29
+ return undefined;
30
+ }
31
+ return textOf(value);
32
+ }
33
+ /**
34
+ * Coerce a fast-xml-parser node to a string.
35
+ *
36
+ * fast-xml-parser may emit a leaf as either:
37
+ * - a primitive (`string` / `number` / `boolean`)
38
+ * - an object with `#text` (when the element has attributes alongside content)
39
+ * - an object with attribute keys (when `textNodeName` differs)
40
+ *
41
+ * This helper handles all three. Returns `undefined` when no usable value exists.
42
+ *
43
+ * @param value - The fast-xml-parser leaf value.
44
+ * @returns Trimmed string or `undefined`.
45
+ */
46
+ export function textOf(value) {
47
+ if (value == null)
48
+ return undefined;
49
+ if (typeof value === 'string') {
50
+ const trimmed = value.trim();
51
+ return trimmed === '' ? undefined : trimmed;
52
+ }
53
+ if (typeof value === 'number' || typeof value === 'boolean') {
54
+ return String(value);
55
+ }
56
+ if (typeof value === 'object') {
57
+ const obj = value;
58
+ if ('#text' in obj) {
59
+ const text = obj['#text'];
60
+ if (typeof text === 'string') {
61
+ const trimmed = text.trim();
62
+ return trimmed === '' ? undefined : trimmed;
63
+ }
64
+ if (typeof text === 'number' || typeof text === 'boolean') {
65
+ return String(text);
66
+ }
67
+ }
68
+ }
69
+ return undefined;
70
+ }
71
+ /**
72
+ * Extract an attribute value (e.g. `href`, `url`) from a fast-xml-parser node.
73
+ *
74
+ * Attributes are exposed under the `attributeNamePrefix` ('@_' by default).
75
+ * This helper is tolerant of missing attribute objects.
76
+ *
77
+ * @param value - The fast-xml-parser node (object, primitive, or undefined).
78
+ * @param name - Attribute name without the prefix (e.g. `'href'`).
79
+ * @returns Attribute value as a trimmed string, or `undefined`.
80
+ */
81
+ export function attrOf(value, name) {
82
+ if (value == null || typeof value !== 'object')
83
+ return undefined;
84
+ const obj = value;
85
+ const raw = obj[`@_${name}`];
86
+ if (raw == null)
87
+ return undefined;
88
+ if (typeof raw === 'string') {
89
+ const trimmed = raw.trim();
90
+ return trimmed === '' ? undefined : trimmed;
91
+ }
92
+ if (typeof raw === 'number' || typeof raw === 'boolean') {
93
+ return String(raw);
94
+ }
95
+ return undefined;
96
+ }
97
+ /**
98
+ * Parse a date-ish string into an ISO 8601 timestamp.
99
+ *
100
+ * Accepts RFC 822 (RSS `pubDate`), RFC 3339 (Atom / JSON Feed), and most
101
+ * common variants the JavaScript `Date` constructor handles natively.
102
+ *
103
+ * @param value - Possibly a date string, or `undefined`.
104
+ * @returns ISO 8601 string when parseable, else `undefined`.
105
+ */
106
+ export function toIsoDate(value) {
107
+ if (!value)
108
+ return undefined;
109
+ const ts = Date.parse(value);
110
+ if (Number.isNaN(ts))
111
+ return undefined;
112
+ return new Date(ts).toISOString();
113
+ }
114
+ /**
115
+ * Parse iTunes podcast `<itunes:duration>` formats into seconds.
116
+ *
117
+ * Accepted shapes:
118
+ * - bare integer seconds: `"3600"`
119
+ * - `MM:SS`: `"42:30"`
120
+ * - `HH:MM:SS`: `"01:42:30"`
121
+ *
122
+ * @param value - The duration string, or `undefined`.
123
+ * @returns Duration in seconds, or `undefined` when unparseable.
124
+ */
125
+ export function parseItunesDuration(value) {
126
+ if (!value)
127
+ return undefined;
128
+ const trimmed = value.trim();
129
+ if (trimmed === '')
130
+ return undefined;
131
+ if (/^\d+$/.test(trimmed))
132
+ return Number(trimmed);
133
+ const parts = trimmed.split(':').map((p) => p.trim());
134
+ if (parts.length === 2 || parts.length === 3) {
135
+ const nums = parts.map((p) => Number(p));
136
+ if (nums.some((n) => Number.isNaN(n)))
137
+ return undefined;
138
+ if (parts.length === 3) {
139
+ return nums[0] * 3600 + nums[1] * 60 + nums[2];
140
+ }
141
+ return nums[0] * 60 + nums[1];
142
+ }
143
+ return undefined;
144
+ }
145
+ /**
146
+ * Compute a stable identifier when a feed item has neither a GUID nor a link.
147
+ *
148
+ * Uses SHA-1 over `title + '' + publishedAt` to give a deterministic,
149
+ * compact key. Collisions are tolerable here — this is a deduplication aid,
150
+ * not a security primitive.
151
+ *
152
+ * @param title - Item title (may be empty).
153
+ * @param publishedAt - Pre-parsed ISO 8601 publication date, if any.
154
+ * @returns Hex-encoded SHA-1 digest. Always 40 characters.
155
+ */
156
+ export function synthesizeItemId(title, publishedAt) {
157
+ const hash = createHash('sha1');
158
+ hash.update(title);
159
+ hash.update('');
160
+ hash.update(publishedAt ?? '');
161
+ return hash.digest('hex');
162
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@molecule/api-feed-rss-parser",
3
+ "version": "1.0.0",
4
+ "description": "RSS/Atom/JSON Feed parser — normalizes RSS 2.0, Atom 1.0, RDF 1.0, and JSON Feed into a uniform { feed, items[] } shape.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "test": "vitest run",
11
+ "test:watch": "vitest"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "keywords": [
23
+ "molecule",
24
+ "feed",
25
+ "rss",
26
+ "atom",
27
+ "json-feed",
28
+ "podcast",
29
+ "parser"
30
+ ],
31
+ "license": "Apache-2.0",
32
+ "dependencies": {
33
+ "fast-xml-parser": "5.10.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "26.1.2",
37
+ "typescript": "6.0.3",
38
+ "vitest": "4.1.10"
39
+ }
40
+ }