@@ -8,6 +8,11 @@ import {
88 InvalidPollingIntervalError ,
99 UnsupportedFeedFormatError ,
1010} from "./errors"
11+ import {
12+ detectSmartPollingInterval ,
13+ getContentHash ,
14+ parseFeedDate ,
15+ } from "./utils"
1116import type {
1217 CollectionConfig ,
1318 DeleteMutationFnParams ,
@@ -17,66 +22,10 @@ import type {
1722 UtilsRecord ,
1823} from "@tanstack/db"
1924import type { StandardSchemaV1 } from "@standard-schema/spec"
25+ import type { AtomItem , FeedItem , HTTPOptions , RSSItem } from "./types"
2026
2127const debug = DebugModule . debug ( `ts/db:rss` )
2228
23- /**
24- * Types for RSS feed items
25- */
26- export interface RSSItem {
27- title ?: string
28- description ?: string
29- link ?: string
30- guid ?: string
31- pubDate ?: string | Date
32- author ?: string
33- category ?: string | Array < string >
34- enclosure ?: {
35- url : string
36- type ?: string
37- length ?: string
38- }
39- [ key : string ] : any
40- }
41-
42- /**
43- * Types for Atom feed items
44- */
45- export interface AtomItem {
46- title ?: string | { $text ?: string ; type ?: string }
47- summary ?: string | { $text ?: string ; type ?: string }
48- content ?: string | { $text ?: string ; type ?: string }
49- link ?:
50- | string
51- | { href ?: string ; rel ?: string ; type ?: string }
52- | Array < { href ?: string ; rel ?: string ; type ?: string } >
53- id ?: string
54- updated ?: string | Date
55- published ?: string | Date
56- author ?: string | { name ?: string ; email ?: string ; uri ?: string }
57- category ?:
58- | string
59- | { term ?: string ; label ?: string }
60- | Array < { term ?: string ; label ?: string } >
61- [ key : string ] : any
62- }
63-
64- export type FeedItem = RSSItem | AtomItem
65-
66- /**
67- * Feed type detection
68- */
69- export type FeedType = `rss` | `atom` | `auto`
70-
71- /**
72- * HTTP options for fetching feeds
73- */
74- export interface HTTPOptions {
75- timeout ?: number
76- headers ?: Record < string , string >
77- userAgent ?: string
78- }
79-
8029/**
8130 * Base configuration interface for feed collection options
8231 */
@@ -305,7 +254,7 @@ function parseFeed(xmlContent: string, parserOptions: any = {}): ParsedFeed {
305254function defaultRSSTransform ( item : RSSItem ) : RSSItem {
306255 return {
307256 ...item ,
308- pubDate : item . pubDate ? new Date ( item . pubDate ) : undefined ,
257+ pubDate : item . pubDate ? parseFeedDate ( item . pubDate ) : undefined ,
309258 }
310259}
311260
@@ -340,10 +289,10 @@ function defaultAtomTransform(item: AtomItem): AtomItem {
340289
341290 // Handle dates
342291 if ( item . updated ) {
343- normalized . updated = new Date ( item . updated )
292+ normalized . updated = parseFeedDate ( item . updated )
344293 }
345294 if ( item . published ) {
346- normalized . published = new Date ( item . published )
295+ normalized . published = parseFeedDate ( item . published )
347296 }
348297
349298 // Handle author
@@ -447,7 +396,7 @@ function createFeedCollectionOptions<
447396) {
448397 const {
449398 feedUrl,
450- pollingInterval = 300000 , // 5 minutes default
399+ pollingInterval : userPollingInterval ,
451400 httpOptions = { } ,
452401 startPolling = true ,
453402 maxSeenItems = 1000 ,
@@ -461,6 +410,10 @@ function createFeedCollectionOptions<
461410 ...restConfig
462411 } = config
463412
413+ // Smart polling interval detection
414+ let pollingInterval =
415+ userPollingInterval !== undefined ? userPollingInterval : 300000 // Default 5 minutes
416+
464417 // Validation
465418 if ( ! feedUrl ) {
466419 throw new FeedURLRequiredError ( )
@@ -470,7 +423,10 @@ function createFeedCollectionOptions<
470423 }
471424
472425 // State management
473- let seenItems = new Map < string , { id : string ; lastSeen : number } > ( )
426+ let seenItems = new Map <
427+ string ,
428+ { id : string ; lastSeen : number ; contentHash : string }
429+ > ( )
474430 let syncParams :
475431 | Parameters <
476432 SyncConfig < ResolveType < TExplicit , TSchema , TFallback > , TKey > [ `sync`]
@@ -544,10 +500,22 @@ function createFeedCollectionOptions<
544500 throw new UnsupportedFeedFormatError ( feedUrl )
545501 }
546502
503+ // Detect smart polling interval on first fetch
504+ if ( ! userPollingInterval ) {
505+ const parser = new XMLParser ( parserOptions )
506+ const feedData = parser . parse ( xmlContent )
507+ const smartInterval = detectSmartPollingInterval ( feedData )
508+ if ( smartInterval !== pollingInterval ) {
509+ pollingInterval = smartInterval
510+ debug ( `Updated polling interval to ${ pollingInterval } ms` )
511+ }
512+ }
513+
547514 const { begin, write, commit } = params
548515 begin ( )
549516
550517 let newItemsCount = 0
518+ let updatedItemsCount = 0
551519 const currentTime = Date . now ( )
552520
553521 for ( const rawItem of parsedFeed . items ) {
@@ -572,22 +540,41 @@ function createFeedCollectionOptions<
572540
573541 // Generate unique ID for deduplication
574542 const itemId = getItemId ( rawItem , parsedFeed . type )
543+ const contentHash = getContentHash ( rawItem )
575544
576545 // Check if we've seen this item before
577546 const seen = seenItems . get ( itemId )
578547
579548 if ( ! seen ) {
580549 // New item
581- seenItems . set ( itemId , { id : itemId , lastSeen : currentTime } )
550+ seenItems . set ( itemId , {
551+ id : itemId ,
552+ lastSeen : currentTime ,
553+ contentHash,
554+ } )
582555
583556 write ( {
584557 type : `insert` ,
585558 value : transformedItem ,
586559 } )
587560
588561 newItemsCount ++
562+ } else if ( seen . contentHash !== contentHash ) {
563+ // Item exists but content has changed - treat as update
564+ seenItems . set ( itemId , {
565+ ...seen ,
566+ lastSeen : currentTime ,
567+ contentHash,
568+ } )
569+
570+ write ( {
571+ type : `update` ,
572+ value : transformedItem ,
573+ } )
574+
575+ updatedItemsCount ++
589576 } else {
590- // Update last seen time
577+ // Item exists and content hasn't changed - just update last seen time
591578 seenItems . set ( itemId , { ...seen , lastSeen : currentTime } )
592579 }
593580 }
@@ -597,6 +584,9 @@ function createFeedCollectionOptions<
597584 if ( newItemsCount > 0 ) {
598585 debug ( `Added ${ newItemsCount } new items from feed` )
599586 }
587+ if ( updatedItemsCount > 0 ) {
588+ debug ( `Updated ${ updatedItemsCount } existing items from feed` )
589+ }
600590
601591 // Clean up old items periodically
602592 cleanupSeenItems ( )
@@ -694,6 +684,7 @@ function createFeedCollectionOptions<
694684 getKey,
695685 sync,
696686 startSync : true ,
687+ rowUpdateMode : `full` ,
697688 onInsert,
698689 onUpdate,
699690 onDelete,
0 commit comments