Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | 8x 8x 8x 8x 8x 8x 8x 8x 8x 11x 3x 3x 3x 11x 11x 11x 4x 4x 4x 7x 7x 7x 7x 7x 4x 4x 4x 3x 3x 1x 1x 1x | /**
* @module scripts/fetch-calendar/orchestrator
* @description Primary→fallback orchestrator for the Riksdag calendar fetch.
*
* 1. **MCP primary**: call `get_calendar_events` on riksdag-regering.
* Retries up to `maxRetries` times on transient failures (network/json/tool).
* Breaks early on `html` kind — when the endpoint serves an HTML error
* page there is no point retrying.
* 2. **Web fallback**: scrape `riksdagen.se/sv/kalendarium/` instead.
*
* Decision lives in **one** function ≤ 90 lines, per the refactor brief.
*
* @author Hack23 AB
* @license Apache-2.0
*/
import {
callMcpCalendarEvents,
DEFAULT_MAX_RETRIES,
DEFAULT_MCP_URL,
DEFAULT_TIMEOUT,
RETRY_BASE_DELAY_MS,
} from './mcp/client.js';
import { CalendarMcpError } from './mcp/errors.js';
import { normalizeMcpCalendarEvent } from './mcp/normaliser.js';
import { DEFAULT_WEB_BASE_URL, fetchWebCalendar } from './scraper/parse.js';
import type { CalendarFetchConfig, CalendarFetchResult } from './types.js';
function defaultSleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Fetch Riksdag calendar events for the given date range using a
* primary→fallback resilience chain.
*
* @param from ISO 8601 date string (inclusive start, e.g. "2026-04-28").
* @param to ISO 8601 date string (inclusive end, e.g. "2026-05-04").
* @param config Optional overrides for URLs, timeout, retries, and fetch mock.
*/
export async function fetchCalendarWithFallback(
from: string,
to: string,
config: CalendarFetchConfig = {},
): Promise<CalendarFetchResult> {
const mcpUrl = config.mcpUrl ?? DEFAULT_MCP_URL;
const webBaseUrl = config.webBaseUrl ?? DEFAULT_WEB_BASE_URL;
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
const fetchFn = config.fetchFn ?? globalThis.fetch;
const sleepFn = config.sleepFn ?? defaultSleep;
const fetchedAt = new Date().toISOString();
const resolved = { mcpUrl, webBaseUrl, timeout, fetchFn, sleepFn };
let primaryError: string | undefined;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
if (attempt > 0) {
const delay = Math.min(RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1), 30_000);
console.warn(` ⚠️ MCP calendar retry ${attempt}/${maxRetries} after ${delay} ms…`);
await sleepFn(delay);
}
try {
console.error(
` 🔄 [fetch-calendar] MCP primary attempt ${attempt + 1}/${maxRetries + 1}…`,
);
const raw = await callMcpCalendarEvents(from, to, resolved);
const events = raw.map(normalizeMcpCalendarEvent);
console.error(` ✅ [fetch-calendar] MCP primary succeeded — ${events.length} events`);
return {
events,
manifest: {
date: from,
dateTo: to,
path: 'mcp-primary',
eventCount: events.length,
fetchedAt,
},
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
primaryError = msg;
const kind = err instanceof CalendarMcpError ? err.kind : 'unknown';
console.warn(
` ⚠️ [fetch-calendar] MCP attempt ${attempt + 1} failed (${kind}): ${msg.slice(0, 120)}`,
);
if (err instanceof CalendarMcpError && err.kind === 'html') break;
}
}
console.error(` 🔄 [fetch-calendar] Falling back to riksdagen.se/sv/kalendarium/…`);
let fallbackError: string | undefined;
try {
const events = await fetchWebCalendar(from, to, resolved);
console.error(` ✅ [fetch-calendar] Web fallback succeeded — ${events.length} events`);
return {
events,
manifest: {
date: from,
dateTo: to,
path: 'web-fallback',
eventCount: events.length,
primaryError,
fetchedAt,
},
};
} catch (err) {
fallbackError = err instanceof Error ? err.message : String(err);
console.error(` ❌ [fetch-calendar] Web fallback also failed: ${fallbackError}`);
}
return {
events: [],
manifest: {
date: from,
dateTo: to,
path: 'none',
eventCount: 0,
primaryError,
fallbackError,
fetchedAt,
},
};
}
|