@scalemule/news-publication 0.0.20 → 0.0.21

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.
package/dist/index.cjs CHANGED
@@ -1836,6 +1836,57 @@ function readerSources(input) {
1836
1836
  return output;
1837
1837
  }
1838
1838
 
1839
+ // src/listening.ts
1840
+ function publicationListeningTrack(article, publicationId, publicationName, origin) {
1841
+ const articleUrl2 = new URL(`/news/${encodeURIComponent(article.slug)}`, origin).href;
1842
+ let storyId = articleUrl2;
1843
+ if (article.canonical_url) {
1844
+ try {
1845
+ const canonical = new URL(article.canonical_url);
1846
+ if (canonical.protocol === "https:" && !canonical.username && !canonical.password && /^\/news\/[^/]+\/?$/.test(canonical.pathname) && localPublications.some((item) => new URL(item.url).hostname === canonical.hostname.replace(/^www\./, ""))) {
1847
+ canonical.search = "";
1848
+ canonical.hash = "";
1849
+ canonical.hostname = canonical.hostname.replace(/^www\./, "");
1850
+ canonical.pathname = canonical.pathname.replace(/\/$/, "");
1851
+ storyId = canonical.href;
1852
+ }
1853
+ } catch {
1854
+ }
1855
+ }
1856
+ const revision = article.editorial?.modified_at;
1857
+ return {
1858
+ id: article.id,
1859
+ publicationId,
1860
+ publicationName,
1861
+ title: article.title,
1862
+ articleUrl: articleUrl2,
1863
+ storyId,
1864
+ ...revision && Number.isFinite(Date.parse(revision)) ? { revision } : {},
1865
+ ...article.audio?.duration_ms && article.audio.duration_ms > 0 ? { durationSeconds: article.audio.duration_ms / 1e3 } : {},
1866
+ ...article.section ? { section: article.section } : {},
1867
+ ...article.published_at ? { publishedAt: article.published_at } : {}
1868
+ };
1869
+ }
1870
+ async function publicationListeningCandidates(items, read, publicationId, publicationName, origin) {
1871
+ const output = [];
1872
+ const recent = items.filter((item) => !item.editorial?.duplicate_url).slice(0, 30);
1873
+ let failures = 0;
1874
+ for (let i = 0; i < recent.length; i += 4) {
1875
+ const batch = await Promise.allSettled(recent.slice(i, i + 4).map((item) => read(item.slug)));
1876
+ for (const result of batch) {
1877
+ if (result.status === "rejected") {
1878
+ failures++;
1879
+ continue;
1880
+ }
1881
+ const article = result.value;
1882
+ if (!article.editorial?.duplicate_url && article.audio?.url && article.audio.duration_ms && article.audio.duration_ms > 0)
1883
+ output.push(publicationListeningTrack(article, publicationId, publicationName, origin));
1884
+ }
1885
+ }
1886
+ if (!output.length && failures) throw new Error("Audio inventory could not be loaded");
1887
+ return output.sort((a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? ""));
1888
+ }
1889
+
1839
1890
  Object.defineProperty(exports, "COOKIE_CONSENT_BACKUP_KEY", {
1840
1891
  enumerable: true,
1841
1892
  get: function () { return compliance.COOKIE_CONSENT_BACKUP_KEY; }
@@ -2007,6 +2058,8 @@ exports.publicJobsPath = publicJobsPath;
2007
2058
  exports.publicUrl = publicUrl;
2008
2059
  exports.publicationFromSnapshot = publicationFromSnapshot;
2009
2060
  exports.publicationJsonLd = publicationJsonLd;
2061
+ exports.publicationListeningCandidates = publicationListeningCandidates;
2062
+ exports.publicationListeningTrack = publicationListeningTrack;
2010
2063
  exports.publicationMeta = publicationMeta;
2011
2064
  exports.publicationRecord = publicationRecord;
2012
2065
  exports.readerSources = readerSources;
package/dist/index.d.cts CHANGED
@@ -311,4 +311,47 @@ declare function readerSources(input: unknown): {
311
311
  publisher?: string;
312
312
  }[];
313
313
 
314
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
314
+ interface ListeningArticle {
315
+ id: string;
316
+ slug: string;
317
+ title: string;
318
+ canonical_url?: string | null;
319
+ section?: string | null;
320
+ published_at?: string;
321
+ editorial?: {
322
+ duplicate_url?: string;
323
+ modified_at?: string;
324
+ } | null;
325
+ audio?: {
326
+ url?: string | null;
327
+ duration_ms?: number | null;
328
+ };
329
+ }
330
+ /** Public metadata only. Never cache signed recording URLs in reader history. */
331
+ declare function publicationListeningTrack(article: ListeningArticle, publicationId: string, publicationName: string, origin: string): {
332
+ publishedAt?: string | undefined;
333
+ section?: string | undefined;
334
+ durationSeconds?: number | undefined;
335
+ revision?: string | undefined;
336
+ id: string;
337
+ publicationId: string;
338
+ publicationName: string;
339
+ title: string;
340
+ articleUrl: string;
341
+ storyId: string;
342
+ };
343
+ /** Bounded public read fan-out, initiated only when a reader requests a catch-up queue. */
344
+ declare function publicationListeningCandidates<T extends ListeningArticle>(items: T[], read: (slug: string) => Promise<ListeningArticle>, publicationId: string, publicationName: string, origin: string): Promise<{
345
+ publishedAt?: string | undefined;
346
+ section?: string | undefined;
347
+ durationSeconds?: number | undefined;
348
+ revision?: string | undefined;
349
+ id: string;
350
+ publicationId: string;
351
+ publicationName: string;
352
+ title: string;
353
+ articleUrl: string;
354
+ storyId: string;
355
+ }[]>;
356
+
357
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, type ListeningArticle, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.d.ts CHANGED
@@ -311,4 +311,47 @@ declare function readerSources(input: unknown): {
311
311
  publisher?: string;
312
312
  }[];
313
313
 
314
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
314
+ interface ListeningArticle {
315
+ id: string;
316
+ slug: string;
317
+ title: string;
318
+ canonical_url?: string | null;
319
+ section?: string | null;
320
+ published_at?: string;
321
+ editorial?: {
322
+ duplicate_url?: string;
323
+ modified_at?: string;
324
+ } | null;
325
+ audio?: {
326
+ url?: string | null;
327
+ duration_ms?: number | null;
328
+ };
329
+ }
330
+ /** Public metadata only. Never cache signed recording URLs in reader history. */
331
+ declare function publicationListeningTrack(article: ListeningArticle, publicationId: string, publicationName: string, origin: string): {
332
+ publishedAt?: string | undefined;
333
+ section?: string | undefined;
334
+ durationSeconds?: number | undefined;
335
+ revision?: string | undefined;
336
+ id: string;
337
+ publicationId: string;
338
+ publicationName: string;
339
+ title: string;
340
+ articleUrl: string;
341
+ storyId: string;
342
+ };
343
+ /** Bounded public read fan-out, initiated only when a reader requests a catch-up queue. */
344
+ declare function publicationListeningCandidates<T extends ListeningArticle>(items: T[], read: (slug: string) => Promise<ListeningArticle>, publicationId: string, publicationName: string, origin: string): Promise<{
345
+ publishedAt?: string | undefined;
346
+ section?: string | undefined;
347
+ durationSeconds?: number | undefined;
348
+ revision?: string | undefined;
349
+ id: string;
350
+ publicationId: string;
351
+ publicationName: string;
352
+ title: string;
353
+ articleUrl: string;
354
+ storyId: string;
355
+ }[]>;
356
+
357
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, type ListeningArticle, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedP
6
6
  export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-GB36YM56.js';
7
7
  export { CookieConsentBanner } from './chunk-HTMIFWNA.js';
8
8
  export { CookieSettingsView } from './chunk-3MFKPHO3.js';
9
+ import { localPublications } from './chunk-2IYYEFLD.js';
9
10
  export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, decorateNetworkUrl, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, hasConsented, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from './chunk-2IYYEFLD.js';
10
11
  import { jsxs, jsx } from 'react/jsx-runtime';
11
12
 
@@ -517,4 +518,55 @@ function readerSources(input) {
517
518
  return output;
518
519
  }
519
520
 
520
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, EDITORIAL_SECTION_ORDER, EVENTS, NEWS_CACHE, NewsError, SESSION_IDLE_MS, UTM_FIELDS, UUID, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, composeEdition, defaultRevalidateTargets, displayHeadline, edgeCacheControl, editorialSections, feedCacheKey, feedCacheOptions, filterNews, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationMeta, readerSources, referralCode, resolveCoupon, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
521
+ // src/listening.ts
522
+ function publicationListeningTrack(article, publicationId, publicationName, origin) {
523
+ const articleUrl2 = new URL(`/news/${encodeURIComponent(article.slug)}`, origin).href;
524
+ let storyId = articleUrl2;
525
+ if (article.canonical_url) {
526
+ try {
527
+ const canonical = new URL(article.canonical_url);
528
+ if (canonical.protocol === "https:" && !canonical.username && !canonical.password && /^\/news\/[^/]+\/?$/.test(canonical.pathname) && localPublications.some((item) => new URL(item.url).hostname === canonical.hostname.replace(/^www\./, ""))) {
529
+ canonical.search = "";
530
+ canonical.hash = "";
531
+ canonical.hostname = canonical.hostname.replace(/^www\./, "");
532
+ canonical.pathname = canonical.pathname.replace(/\/$/, "");
533
+ storyId = canonical.href;
534
+ }
535
+ } catch {
536
+ }
537
+ }
538
+ const revision = article.editorial?.modified_at;
539
+ return {
540
+ id: article.id,
541
+ publicationId,
542
+ publicationName,
543
+ title: article.title,
544
+ articleUrl: articleUrl2,
545
+ storyId,
546
+ ...revision && Number.isFinite(Date.parse(revision)) ? { revision } : {},
547
+ ...article.audio?.duration_ms && article.audio.duration_ms > 0 ? { durationSeconds: article.audio.duration_ms / 1e3 } : {},
548
+ ...article.section ? { section: article.section } : {},
549
+ ...article.published_at ? { publishedAt: article.published_at } : {}
550
+ };
551
+ }
552
+ async function publicationListeningCandidates(items, read, publicationId, publicationName, origin) {
553
+ const output = [];
554
+ const recent = items.filter((item) => !item.editorial?.duplicate_url).slice(0, 30);
555
+ let failures = 0;
556
+ for (let i = 0; i < recent.length; i += 4) {
557
+ const batch = await Promise.allSettled(recent.slice(i, i + 4).map((item) => read(item.slug)));
558
+ for (const result of batch) {
559
+ if (result.status === "rejected") {
560
+ failures++;
561
+ continue;
562
+ }
563
+ const article = result.value;
564
+ if (!article.editorial?.duplicate_url && article.audio?.url && article.audio.duration_ms && article.audio.duration_ms > 0)
565
+ output.push(publicationListeningTrack(article, publicationId, publicationName, origin));
566
+ }
567
+ }
568
+ if (!output.length && failures) throw new Error("Audio inventory could not be loaded");
569
+ return output.sort((a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? ""));
570
+ }
571
+
572
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, EDITORIAL_SECTION_ORDER, EVENTS, NEWS_CACHE, NewsError, SESSION_IDLE_MS, UTM_FIELDS, UUID, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, composeEdition, defaultRevalidateTargets, displayHeadline, edgeCacheControl, editorialSections, feedCacheKey, feedCacheOptions, filterNews, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, referralCode, resolveCoupon, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",