polite-crawler 0.1.4 → 0.2.1
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/README.md +28 -121
- package/build/{browser-CKRNbIDT.mjs → browser-56dBBLec.mjs} +7 -3
- package/build/browser-56dBBLec.mjs.map +1 -0
- package/build/classify.d.mts +50 -2
- package/build/{cli-daemon-ZYRdy6EW.mjs → cli-daemon-40TxewuH.mjs} +2 -2
- package/build/{cli-daemon-ZYRdy6EW.mjs.map → cli-daemon-40TxewuH.mjs.map} +1 -1
- package/build/{cli-recipes-Bci6HREA.mjs → cli-recipes-COyH6wo4.mjs} +2 -2
- package/build/{cli-recipes-Bci6HREA.mjs.map → cli-recipes-COyH6wo4.mjs.map} +1 -1
- package/build/cli.mjs +41 -16
- package/build/cli.mjs.map +1 -1
- package/build/{daemon-DK6U51_v.mjs → daemon-Cm6Mb8EE.mjs} +11 -7
- package/build/daemon-Cm6Mb8EE.mjs.map +1 -0
- package/build/{diff-BUM91Rev.mjs → diff-ByOuHmq-.mjs} +7 -3
- package/build/{diff-BUM91Rev.mjs.map → diff-ByOuHmq-.mjs.map} +1 -1
- package/build/{extract-CefB5Vfn.mjs → extract-Ccgs1ERp.mjs} +12 -4
- package/build/extract-Ccgs1ERp.mjs.map +1 -0
- package/build/html-DkQUAE_d.mjs.map +1 -1
- package/build/{html-BACljlkf.d.mts → html-o5bDHjNi.d.mts} +16 -2
- package/build/html.d.mts +2 -3
- package/build/index.d.mts +4258 -45
- package/build/index.mjs +17 -17
- package/build/{log-Dx-wXjJl.mjs → log-CmBWcyQm.mjs} +5 -1
- package/build/log-CmBWcyQm.mjs.map +1 -0
- package/build/mcp-package-Y0bREnOS.mjs +40 -0
- package/build/mcp-package-Y0bREnOS.mjs.map +1 -0
- package/build/{ollama-BJxkjny4.mjs → ollama-D1xRh3hY.mjs} +7 -3
- package/build/ollama-D1xRh3hY.mjs.map +1 -0
- package/build/{open-CbpSCEZc.mjs → open-9oUXhGCK.mjs} +78 -18
- package/build/open-9oUXhGCK.mjs.map +1 -0
- package/build/politeness.d.mts +366 -2
- package/build/{recipe-GiFQojmo.mjs → recipe-BODo4plI.mjs} +10 -4
- package/build/recipe-BODo4plI.mjs.map +1 -0
- package/build/{recipe-product-Dellqudu.mjs → recipe-product-BrDKmDXb.mjs} +29 -7
- package/build/recipe-product-BrDKmDXb.mjs.map +1 -0
- package/build/{render-C1CUraWF.mjs → render-CKE2LILH.mjs} +10 -3
- package/build/render-CKE2LILH.mjs.map +1 -0
- package/build/robots.d.mts +143 -2
- package/build/robots.mjs +1 -1
- package/build/robots.mjs.map +1 -1
- package/build/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
- package/build/{runtime-D_r2SYge.mjs → runtime-z6x0pWSN.mjs} +84 -18
- package/build/runtime-z6x0pWSN.mjs.map +1 -0
- package/build/signals.d.mts +45 -2
- package/build/{transformers-BQMlaGj4.mjs → transformers-BNiuiLxT.mjs} +3 -3
- package/build/transformers-BNiuiLxT.mjs.map +1 -0
- package/build/{version-BPem-XZd.mjs → version-cPnAph6k.mjs} +7 -2
- package/build/version-cPnAph6k.mjs.map +1 -0
- package/build/{wacz-QyzLp4mC.mjs → wacz-Cog-hnVy.mjs} +4 -4
- package/build/{wacz-QyzLp4mC.mjs.map → wacz-Cog-hnVy.mjs.map} +1 -1
- package/build/{warc-BHcaRKHw.mjs → warc-FLgjirrM.mjs} +4 -4
- package/build/{warc-BHcaRKHw.mjs.map → warc-FLgjirrM.mjs.map} +1 -1
- package/build/{webbotauth-CjJdTAfE.mjs → webbotauth-Dq_dA_u3.mjs} +2 -2
- package/build/webbotauth-Dq_dA_u3.mjs.map +1 -0
- package/package.json +5 -11
- package/build/browser-CKRNbIDT.mjs.map +0 -1
- package/build/classify-CrfyXcwo.d.mts +0 -51
- package/build/daemon-DK6U51_v.mjs.map +0 -1
- package/build/daemon-wwIWsUmU.d.mts +0 -3777
- package/build/extract-CefB5Vfn.mjs.map +0 -1
- package/build/log-Dx-wXjJl.mjs.map +0 -1
- package/build/loopback-C_QYowSf.mjs +0 -22
- package/build/loopback-C_QYowSf.mjs.map +0 -1
- package/build/mcp.d.mts +0 -199
- package/build/mcp.mjs +0 -1555
- package/build/mcp.mjs.map +0 -1
- package/build/ollama-BJxkjny4.mjs.map +0 -1
- package/build/open-CbpSCEZc.mjs.map +0 -1
- package/build/politeness-yI3xBEx9.d.mts +0 -367
- package/build/recipe-GiFQojmo.mjs.map +0 -1
- package/build/recipe-product-Dellqudu.mjs.map +0 -1
- package/build/render-C1CUraWF.mjs.map +0 -1
- package/build/robots-R-qaEBOb.d.mts +0 -144
- package/build/runtime-D_r2SYge.mjs.map +0 -1
- package/build/signals-BHpy2xb2.d.mts +0 -46
- package/build/transformers-BQMlaGj4.mjs.map +0 -1
- package/build/urls-BTDSgDLC.d.mts +0 -16
- package/build/version-BPem-XZd.mjs.map +0 -1
- package/build/webbotauth-CjJdTAfE.mjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
2
|
-
import { t as browserMissing } from "./browser-
|
|
3
|
-
import { i as durationMs, r as VERSION } from "./version-
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
|
+
import { t as browserMissing } from "./browser-56dBBLec.mjs";
|
|
3
|
+
import { i as durationMs, r as VERSION } from "./version-cPnAph6k.mjs";
|
|
4
4
|
import { realClock } from "./politeness.mjs";
|
|
5
|
-
import { i as openCrawler } from "./open-
|
|
5
|
+
import { i as openCrawler } from "./open-9oUXhGCK.mjs";
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { appendFileSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -307,9 +307,13 @@ const changeCommand = (command, options = {}) => {
|
|
|
307
307
|
child.stdin.end(`${JSON.stringify(event)}\n`);
|
|
308
308
|
});
|
|
309
309
|
};
|
|
310
|
-
/**
|
|
310
|
+
/**
|
|
311
|
+
* The default MCP host is `mcpHttpHost` of the package polite-crawler-mcp. The daemon loads the
|
|
312
|
+
* package only when it serves MCP, and an error tells where the MCP server is when it is not there.
|
|
313
|
+
*/
|
|
311
314
|
const defaultHost = { async start(crawler, options, hooks) {
|
|
312
|
-
const {
|
|
315
|
+
const { loadMcpPackage } = await import("./mcp-package-Y0bREnOS.mjs");
|
|
316
|
+
const { mcpHttpHost } = await loadMcpPackage();
|
|
313
317
|
return mcpHttpHost.start(crawler, options, hooks);
|
|
314
318
|
} };
|
|
315
319
|
/** This function gives the writer of the change events. */
|
|
@@ -512,4 +516,4 @@ const startDaemon = async (options) => {
|
|
|
512
516
|
//#endregion
|
|
513
517
|
export { daemon_exports as a, startDaemon as c, daemonStateFile as i, DAEMON_STATE_SCHEMA as n, readDaemonState as o, changeCommand as r, scheduleWatches as s, DAEMON_DEFAULTS as t };
|
|
514
518
|
|
|
515
|
-
//# sourceMappingURL=daemon-
|
|
519
|
+
//# sourceMappingURL=daemon-Cm6Mb8EE.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-Cm6Mb8EE.mjs","names":[],"sources":["../src/daemon.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n appendFileSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { browserMissing } from \"./browser.ts\";\nimport type { Duration } from \"./duration.ts\";\nimport { durationMs } from \"./duration.ts\";\nimport type { OpenOptions, OpenedCrawler } from \"./open.ts\";\nimport { openCrawler } from \"./open.ts\";\nimport type { Clock } from \"./politeness.ts\";\nimport { realClock } from \"./politeness.ts\";\nimport { VERSION } from \"./version.ts\";\nimport type { WatchEntry, WatchEvent, WatchList, WatchSummary } from \"./watch.ts\";\n\n/*\n * The daemon is a local process that runs for a long time. It does these jobs:\n *\n * - It checks the watch list on a schedule (`scheduleWatches`). Each entry that is due gets its\n * check at its time, with each polite rule of `watch.run`.\n * - It writes each change as one JSON line, and it can start a command of the operator for it.\n * - It can serve the MCP tools over HTTP, with the long tools as MCP tasks. This part loads the MCP\n * SDK only when the daemon serves MCP, through a small seam (`DaemonMcpHost`).\n * - It writes a small state file, which `polite-crawler daemon status` reads.\n */\n\n/** These are the defaults of the daemon. */\nexport const DAEMON_DEFAULTS = {\n /** The longest wait between two ticks: 1 minute. A tick comes sooner when an entry is due sooner. */\n tickMs: 60_000,\n /** The most random time that the daemon adds to each wait: 10 seconds. */\n jitterMs: 10_000,\n /** The budget of the checks of one tick: 10 minutes. No request starts after it. */\n budgetMs: 600_000,\n /** The most checks of one tick. */\n maxPages: 100,\n /** At a stop, the checks in progress get this time to end: 10 seconds. Then they abort. */\n graceMs: 10_000,\n /** The shortest wait between two ticks: 1 second. */\n minGapMs: 1_000,\n /** The shortest tick: 1 second. */\n minTickMs: 1_000,\n /** The status lists this number of the next due entries. */\n dueShown: 10,\n /** An on-change command that runs longer than this time stops: 1 minute. */\n hookTimeoutMs: 60_000,\n} as const;\n\n/** This value is the version of the format of the state file. */\nexport const DAEMON_STATE_SCHEMA = 1;\n\n/** This is a watch entry that is due soon, as the status shows it. */\nexport interface DaemonDue {\n readonly url: string;\n /** This time (ISO 8601) is the time when the entry is due. */\n readonly next_at: string;\n readonly every_ms: number;\n /** This value is the kind of the last check, or null before the first check. */\n readonly last_kind: string | null;\n}\n\n/** This is one tick of the schedule: one run of the watch list. */\nexport interface DaemonTick {\n /** This time (ISO 8601) is the start of the run. */\n readonly started_at: string;\n /** This time (ISO 8601) is the end of the run. */\n readonly ended_at: string;\n /** This value is the summary of the run, or null when the run failed. */\n readonly summary: WatchSummary | null;\n /** This text tells why the run failed, or null. */\n readonly error: string | null;\n /** This time (ISO 8601) is the time of the next tick, or null when the schedule stops. */\n readonly next_at: string | null;\n /** These entries are the next due entries after the run, the first due first. */\n readonly due: readonly DaemonDue[];\n}\n\n/** These options control the schedule of the watch list. */\nexport interface ScheduleOptions {\n /** This value is the longest wait between two ticks. The default is `1m`, and the floor is `1s`. */\n readonly tick?: Duration | undefined;\n /** This value is the most random time that the schedule adds to each wait. The default is `10s`. */\n readonly jitter?: Duration | undefined;\n /** This value is the budget of the checks of one tick. The default is `10m`. */\n readonly budget?: Duration | undefined;\n /** This value is the most checks of one tick. The default is 100. */\n readonly maxPages?: number | undefined;\n /** This clock gives the time and the waits. Give the clock of the crawler. The default is the real clock. */\n readonly clock?: Clock | undefined;\n /** This function gives the random numbers of the jitter. The default is `Math.random`. */\n readonly random?: (() => number) | undefined;\n /** This function gets each event of each check. */\n readonly onEvent?: ((event: WatchEvent) => void) | undefined;\n /** This function gets each tick when its run ends. */\n readonly onTick?: ((tick: DaemonTick) => void) | undefined;\n /** This function gets the error of a run that failed. The schedule goes on at the next tick. */\n readonly onError?: ((error: Error) => void) | undefined;\n}\n\n/** This is the result of a schedule: the counts so far, and the last tick. */\nexport interface ScheduleResult {\n readonly ticks: number;\n /** This value counts the checks that gave an event. */\n readonly checked: number;\n /** This value counts the confirmed changes. */\n readonly changes: number;\n readonly last: DaemonTick | null;\n}\n\n/** A schedule that runs. `stop` ends it, and `done` resolves when it ends. */\nexport interface WatchSchedule {\n /** This promise resolves when the schedule ends. It does not reject. */\n readonly done: Promise<ScheduleResult>;\n /**\n * This method stops the schedule. The run in progress starts no new check. The checks in\n * progress get `grace` (10 seconds by default) to end and write their state. Then they abort,\n * and the run releases their leases. A second call with a shorter grace shortens it.\n */\n stop(grace?: Duration): Promise<ScheduleResult>;\n /** This method gives the counts so far. */\n result(): ScheduleResult;\n}\n\nconst iso = (ms: number): string => new Date(ms).toISOString();\n\n/** This function reads a duration of an option, with a floor. A bad value throws a `RangeError`. */\nconst msOf = (name: string, value: Duration, floor: number): number => {\n let ms: number;\n try {\n ms = durationMs(value);\n } catch (error) {\n throw new RangeError(`${name}: ${(error as Error).message}`, { cause: error });\n }\n if (ms < floor)\n throw new RangeError(`${name} must be ${String(floor)} ms or more, not ${String(ms)}`);\n return ms;\n};\n\nconst pagesOf = (value: number | undefined): number => {\n const pages = value ?? DAEMON_DEFAULTS.maxPages;\n if (!Number.isInteger(pages) || pages < 1)\n throw new RangeError(`maxPages must be an integer of 1 or more, not ${String(pages)}`);\n return pages;\n};\n\nconst errorOf = (error: unknown): Error =>\n error instanceof Error ? error : new Error(String(error));\n\n/** This function gives the next due entries of a list, as the status shows them. */\nconst dueOf = (entries: readonly WatchEntry[]): DaemonDue[] =>\n [...entries]\n .sort((a, b) => Date.parse(a.next_at) - Date.parse(b.next_at) || a.url.localeCompare(b.url))\n .slice(0, DAEMON_DEFAULTS.dueShown)\n .map((entry) => ({\n url: entry.url,\n next_at: entry.next_at,\n every_ms: entry.every_ms,\n last_kind: entry.last_kind,\n }));\n\n/**\n * This function gives the time of the next tick. It is the time when the first entry is due, or\n * the end of its lease. It is not later than one tick, and not sooner than `minGapMs`. A random\n * jitter from 0 to `jitterMs` comes after it, so a check does not come before its time.\n *\n * When the budget or `maxPages` of a run left due entries, they wait for the next tick, so one\n * tick does not exceed its budget. A run that failed also waits one tick.\n */\nexport const nextTickAt = (\n entries: readonly WatchEntry[],\n summary: WatchSummary | null,\n now: number,\n options: { readonly tickMs: number; readonly jitterMs: number; readonly random: () => number },\n): number => {\n let next = now + options.tickMs;\n const leftover = summary === null || summary.stopped !== \"done\";\n for (const entry of entries) {\n const lease = entry.leased_until === null ? 0 : Date.parse(entry.leased_until);\n const due = Math.max(Date.parse(entry.next_at), lease);\n if (due <= now && leftover) continue;\n next = Math.min(next, Math.max(due, now + DAEMON_DEFAULTS.minGapMs));\n }\n const random = Math.min(Math.max(options.random(), 0), 1);\n return next + Math.min(options.jitterMs, Math.floor(random * options.jitterMs));\n};\n\n/**\n * This function checks a watch list on a schedule. The first tick starts at once. Each tick is one\n * `watch.run` with the budget and `maxPages` of a tick. Thus each polite rule of the run applies:\n * one request at a time to each host, the stops of a host, `Retry-After` and robots.txt.\n *\n * After a run, the schedule waits for the next tick (`nextTickAt`). An error of a run goes to\n * `onError`, and the schedule goes on.\n */\nexport const scheduleWatches = (watch: WatchList, options: ScheduleOptions = {}): WatchSchedule => {\n const tickMs = msOf(\"tick\", options.tick ?? DAEMON_DEFAULTS.tickMs, DAEMON_DEFAULTS.minTickMs);\n const jitterMs = msOf(\"jitter\", options.jitter ?? DAEMON_DEFAULTS.jitterMs, 0);\n const budgetMs = msOf(\"budget\", options.budget ?? DAEMON_DEFAULTS.budgetMs, 0);\n const maxPages = pagesOf(options.maxPages);\n const clock = options.clock ?? realClock;\n const random = options.random ?? Math.random;\n // the drain starts no new check, the cancel aborts the checks in progress, the wake ends a wait\n const drain = new AbortController();\n const cancel = new AbortController();\n const wake = new AbortController();\n let stopping = false;\n const counts = { ticks: 0, checked: 0, changes: 0, last: null as DaemonTick | null };\n const result = (): ScheduleResult => ({ ...counts });\n const report = (error: unknown): void => {\n try {\n options.onError?.(errorOf(error));\n } catch {\n // an error of the error handler stops nothing\n }\n };\n const safely = (work: () => void): void => {\n try {\n work();\n } catch (error) {\n report(error);\n }\n };\n\n const loop = async (): Promise<void> => {\n while (!stopping) {\n const started = clock.now();\n let summary: WatchSummary | null = null;\n let failure: string | null = null;\n try {\n const run = watch.run({\n budgetMs,\n maxPages,\n signal: cancel.signal,\n drain: drain.signal,\n });\n for await (const event of run) {\n counts.checked += 1;\n if (event.confirmed) counts.changes += 1;\n safely(() => options.onEvent?.(event));\n }\n summary = await run.summary;\n } catch (error) {\n failure = errorOf(error).message;\n report(error);\n }\n counts.ticks += 1;\n // the list gives the next due entries to the status, also at the last tick\n let entries: WatchEntry[] = [];\n try {\n entries = await watch.list();\n } catch (error) {\n report(error);\n }\n const next = stopping\n ? null\n : nextTickAt(entries, summary, clock.now(), { tickMs, jitterMs, random });\n const tick: DaemonTick = {\n started_at: iso(started),\n ended_at: iso(clock.now()),\n summary,\n error: failure,\n next_at: next === null ? null : iso(next),\n due: dueOf(entries),\n };\n counts.last = tick;\n safely(() => options.onTick?.(tick));\n if (next === null || stopping) break;\n await clock.sleep(Math.max(0, next - clock.now()), wake.signal);\n }\n };\n\n const done = loop().then(result, (error: unknown) => {\n report(error);\n return result();\n });\n return {\n done,\n result,\n stop(grace = DAEMON_DEFAULTS.graceMs) {\n const graceMs = msOf(\"grace\", grace, 0);\n stopping = true;\n drain.abort(new Error(\"the schedule stops\"));\n wake.abort(new Error(\"the schedule stops\"));\n if (graceMs === 0) cancel.abort(new Error(\"the schedule stopped at once\"));\n else {\n // a real timer: the grace is time of the process, not of the clock of the crawler\n const timer = setTimeout(\n () => cancel.abort(new Error(\"the grace time of the stop ended\")),\n graceMs,\n );\n void done.finally(() => clearTimeout(timer));\n }\n return done;\n },\n };\n};\n\n// ── the state file ──────────────────────────────────────────────────────────\n\n/** This is a task of the MCP server, as the status shows it. */\nexport interface DaemonTask {\n readonly id: string;\n /** This value is the name of the tool, for example `crawl_site`. */\n readonly tool: string;\n /** This value is `working`, `input_required`, `completed`, `failed` or `cancelled`. */\n readonly status: string;\n /** This text tells the progress, for example `3 of 10 done`, or null. */\n readonly message: string | null;\n readonly created_at: string;\n readonly updated_at: string;\n}\n\n/** This is the status of a daemon. The state file (`<store>/daemon/state.json`) holds the same JSON. */\nexport interface DaemonStatus {\n /** This value is the version of the format (`DAEMON_STATE_SCHEMA`). */\n readonly schema: number;\n /** This value is the version of polite-crawler. */\n readonly version: string;\n /** This value is the id of the process of the daemon. */\n readonly pid: number;\n readonly store: string;\n /** This value is false after the daemon stopped. */\n readonly running: boolean;\n readonly started_at: string;\n readonly updated_at: string;\n readonly stopped_at: string | null;\n readonly tick_ms: number;\n readonly jitter_ms: number;\n readonly budget_ms: number;\n readonly max_pages: number;\n /** This value counts the ticks. */\n readonly ticks: number;\n /** This value counts the checks. */\n readonly checked: number;\n /** This value counts the confirmed changes. */\n readonly changes: number;\n readonly last_tick: DaemonTick | null;\n /** This time (ISO 8601) is the time of the next tick, or null. */\n readonly next_tick_at: string | null;\n /** These entries are the next due entries at the last tick, the first due first. */\n readonly next_due: readonly DaemonDue[];\n /** These tasks of the MCP server did not end. */\n readonly tasks: readonly DaemonTask[];\n /** This value is the MCP endpoint and the file of its token, or null with no MCP server. */\n readonly mcp: { readonly url: string; readonly token_file: string | null } | null;\n /** This value is the last error: of a tick, of the event log, of an on-change hook or of the MCP server. */\n readonly last_error: { readonly at: string; readonly message: string } | null;\n}\n\n/** This is a status that `readDaemonState` read from a state file. */\nexport interface DaemonState extends DaemonStatus {\n /** This value is true when the daemon runs: the state says so, and its process is alive. */\n readonly alive: boolean;\n}\n\n/** These state files belong to a daemon that runs in this process. */\nconst RUNNING = new Set<string>();\n\n/** This function tells if a process is alive. A process of a different user is alive too. */\nconst processAlive = (pid: number): boolean => {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as { code?: unknown }).code === \"EPERM\";\n }\n};\n\n/** This function gives the path of the state file of a store. */\nexport const daemonStateFile = (dir: string): string => join(dir, \"daemon\", \"state.json\");\n\n/**\n * This function reads the state file of a daemon. It gives undefined when the file is not there,\n * or when it is not a state file. `alive` tells if the daemon runs.\n */\nexport const readDaemonState = (\n dir: string,\n file: string = daemonStateFile(dir),\n): DaemonState | undefined => {\n let state: DaemonStatus;\n try {\n state = JSON.parse(readFileSync(file, \"utf8\")) as DaemonStatus;\n } catch {\n return undefined;\n }\n if (typeof state !== \"object\" || state === null || typeof state.pid !== \"number\")\n return undefined;\n const alive =\n state.running === true &&\n (state.pid === process.pid ? RUNNING.has(file) : processAlive(state.pid));\n return { ...state, alive };\n};\n\n/** This function writes a state file for the owner only. A rename makes the change atomic where the system can. */\nconst writeState = (file: string, state: DaemonStatus): void => {\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 });\n const text = `${JSON.stringify(state, null, 2)}\\n`;\n const temp = `${file}.${String(process.pid)}.tmp`;\n writeFileSync(temp, text, { mode: 0o600 });\n try {\n renameSync(temp, file);\n } catch {\n // Windows can refuse the rename while a reader has the file open\n writeFileSync(file, text, { mode: 0o600 });\n try {\n unlinkSync(temp);\n } catch {\n // the next write replaces it\n }\n }\n};\n\n/** This function writes a new random token to a file for the owner only, and gives the token. */\nconst writeToken = (file: string): string => {\n const token = randomBytes(32).toString(\"base64url\");\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 });\n try {\n // a new file gets the mode: an old file can have a wider mode\n unlinkSync(file);\n } catch {\n // no old file\n }\n writeFileSync(file, `${token}\\n`, { mode: 0o600, flag: \"wx\" });\n return token;\n};\n\n// ── the on-change command ───────────────────────────────────────────────────\n\n/** These options start an on-change command. */\nexport interface ChangeCommandOptions {\n /** A command that runs longer than this time stops. The default is `1m`. */\n readonly timeout?: Duration | undefined;\n /** This folder is the working folder of the command. The default is the working folder of the daemon. */\n readonly cwd?: string | undefined;\n}\n\n/**\n * This function gives an `onChange` hook that starts a command of the operator for each change.\n * The command operates in the shell of the system, as the user of the daemon. It gets the event as\n * one JSON line on its standard input, not in its command line. Thus no text of a page goes into\n * the command line. The output of the command goes nowhere. A command that exits with a code that\n * is not 0, or that runs too long, rejects with the end of its standard error.\n *\n * The command is the decision of the operator. Do not take it from a page or from an assistant.\n */\nexport const changeCommand = (\n command: string,\n options: ChangeCommandOptions = {},\n): ((event: WatchEvent) => Promise<void>) => {\n if (command.trim() === \"\") throw new RangeError(\"the on-change command is empty\");\n const timeoutMs = msOf(\"timeout\", options.timeout ?? DAEMON_DEFAULTS.hookTimeoutMs, 1);\n return (event) =>\n new Promise<void>((resolve, reject) => {\n const child = spawn(command, {\n shell: true,\n stdio: [\"pipe\", \"ignore\", \"pipe\"],\n windowsHide: true,\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n });\n let tail = \"\";\n let late = false;\n const timer = setTimeout(() => {\n late = true;\n child.kill();\n }, timeoutMs);\n child.stderr.setEncoding(\"utf8\");\n child.stderr.on(\"data\", (text: string) => {\n tail = (tail + text).slice(-2000);\n });\n // a command that does not read its input closes the pipe early\n child.stdin.on(\"error\", () => undefined);\n child.on(\"error\", (error) => {\n clearTimeout(timer);\n reject(\n new Error(`the on-change command did not start: ${error.message}`, { cause: error }),\n );\n });\n child.on(\"close\", (code, signal) => {\n clearTimeout(timer);\n const said = tail.trim() === \"\" ? \"\" : `: ${tail.trim().slice(-300)}`;\n if (late) {\n reject(\n new Error(\n `the on-change command ran for more than ${String(timeoutMs)} ms, so the daemon stopped it`,\n ),\n );\n } else if (code === 0) resolve();\n else\n reject(\n new Error(\n `the on-change command exited with ${code === null ? String(signal) : `code ${String(code)}`}${said}`,\n ),\n );\n });\n child.stdin.end(`${JSON.stringify(event)}\\n`);\n });\n};\n\n// ── the daemon ──────────────────────────────────────────────────────────────\n\n/**\n * These options serve the MCP tools over HTTP.\n *\n * @beta\n */\nexport interface DaemonMcpOptions {\n /** This value is the port. The default is 9307. The value 0 takes a free port. */\n readonly port?: number | undefined;\n /** This value is the host to bind. The default is `127.0.0.1`. A host that is not loopback needs `allowRemote`. */\n readonly host?: string | undefined;\n /** The value true permits a host that is not loopback. Read the warning in the README first. */\n readonly allowRemote?: boolean | undefined;\n /** This value is the bearer token. The default makes a new token at each start, in `<store>/daemon/mcp-token`. */\n readonly token?: string | undefined;\n /** This file holds the bearer token of the operator. The daemon reads it and writes nothing. */\n readonly tokenFile?: string | undefined;\n /** These tokens get their robots.txt rules applied to each fetch of a tool. The default is `[\"Claude-User\"]`. */\n readonly onBehalfOf?: readonly string[] | undefined;\n /** The value true lets `user_provided` release held-back text when the client cannot ask the user. */\n readonly allowUserProvided?: boolean | undefined;\n /** This text tells why the server renders no page. */\n readonly renderOff?: string | undefined;\n}\n\n/**\n * These functions connect an MCP host to the daemon.\n *\n * @beta\n */\nexport interface DaemonMcpHooks {\n /** This function gives the status of the daemon, for the status route. */\n readonly status: () => DaemonStatus;\n /** This function operates when a task starts, changes or ends. */\n readonly onTasks: () => void;\n /** This function writes a message of the server. */\n readonly log: (line: string) => void;\n}\n\n/**\n * This is the MCP server of the daemon, as the MCP host gives it.\n *\n * @beta\n */\nexport interface DaemonMcpService {\n /** This URL is the MCP endpoint. */\n readonly url: string;\n /** This method gives the tasks that did not end. */\n tasks(): readonly DaemonTask[];\n /** This method stops the server and cancels its tasks. */\n close(): Promise<void>;\n}\n\n/**\n * This is the seam between the daemon and its MCP server. The default host (`mcpHttpHost` of the\n * package polite-crawler-mcp) serves the tools over Streamable HTTP. A different MCP host can\n * replace it.\n *\n * @beta\n */\nexport interface DaemonMcpHost {\n start(\n crawler: OpenedCrawler,\n options: DaemonMcpOptions,\n hooks: DaemonMcpHooks,\n ): Promise<DaemonMcpService>;\n}\n\n/** These options start a daemon on a store. Only `dir` is necessary. */\nexport interface DaemonOptions extends OpenOptions {\n /** This value is the longest wait between two ticks. The default is `1m`, and the floor is `1s`. */\n readonly tick?: Duration | undefined;\n /** This value is the most random time that the daemon adds to each wait. The default is `10s`. */\n readonly jitter?: Duration | undefined;\n /** This value is the budget of the checks of one tick. The default is `10m`. */\n readonly budget?: Duration | undefined;\n /** This value is the most checks of one tick. The default is 100. */\n readonly maxPages?: number | undefined;\n /** At a stop, the checks in progress get this time to end. The default is `10s`. */\n readonly grace?: Duration | undefined;\n /**\n * The daemon writes each change event as one JSON line here: a file, a function, or the standard\n * output (the default). The value false writes no line. A change is an event with `confirmed: true`.\n */\n readonly events?: string | ((line: string) => void) | false | undefined;\n /** This function gets each change event. The daemon starts it in order, one at a time, and logs its error. */\n readonly onChange?: ((event: WatchEvent) => void | Promise<void>) | undefined;\n /**\n * These options serve the MCP tools over HTTP. Without them, the daemon serves no MCP.\n *\n * @beta\n */\n readonly mcp?: DaemonMcpOptions | undefined;\n /**\n * This host serves the MCP tools. The default serves them over Streamable HTTP.\n *\n * @beta\n */\n readonly mcpHost?: DaemonMcpHost | undefined;\n /** The value false installs no handler of `SIGINT` and `SIGTERM`. The default is true. */\n readonly signals?: boolean | undefined;\n /** This file keeps the state of the daemon. The default is `<store>/daemon/state.json`. */\n readonly stateFile?: string | undefined;\n /** This function writes the messages of the daemon. The default writes them to the standard error. */\n readonly log?: ((line: string) => void) | undefined;\n}\n\n/** This is a daemon that runs. */\nexport interface Daemon {\n /** This crawler is the crawler of the daemon. The daemon closes it when it stops. */\n readonly crawler: OpenedCrawler;\n /** This file keeps the state of the daemon. */\n readonly stateFile: string;\n /**\n * This value is the MCP server, or undefined.\n *\n * @beta\n */\n readonly mcp: DaemonMcpService | undefined;\n /** This method gives the status at this time. */\n status(): DaemonStatus;\n /**\n * This method stops the daemon. The checks in progress get the grace time to end, then they\n * abort and the run releases their leases. The MCP server stops and cancels its tasks. A second\n * call with `0` aborts the checks at once.\n */\n stop(grace?: Duration): Promise<DaemonStatus>;\n /** This promise resolves with the last status when the daemon stopped. */\n readonly done: Promise<DaemonStatus>;\n}\n\n/**\n * The default MCP host is `mcpHttpHost` of the package polite-crawler-mcp. The daemon loads the\n * package only when it serves MCP, and an error tells where the MCP server is when it is not there.\n */\nconst defaultHost: DaemonMcpHost = {\n async start(crawler, options, hooks) {\n const { loadMcpPackage } = await import(\"./mcp-package.ts\");\n const { mcpHttpHost } = await loadMcpPackage();\n return mcpHttpHost.start(crawler, options, hooks);\n },\n};\n\n/** This function gives the writer of the change events. */\nconst eventWriter = (events: DaemonOptions[\"events\"]): ((line: string) => void) | undefined => {\n if (events === false) return undefined;\n if (typeof events === \"function\") return events;\n if (events === undefined) return (line) => void process.stdout.write(`${line}\\n`);\n mkdirSync(dirname(events), { recursive: true });\n return (line) => appendFileSync(events, `${line}\\n`, { mode: 0o600 });\n};\n\n/**\n * This function starts a daemon on a store. It opens the crawler, serves MCP when `mcp` is given,\n * and checks the watch list on a schedule (`scheduleWatches`). It writes each change event as one\n * JSON line, and it gives each change to `onChange`. The state file tells `daemon status` what runs.\n *\n * Only one daemon operates on a store: a second start rejects. At `SIGINT` or `SIGTERM`, the daemon\n * stops cleanly (`stop`). A second signal aborts the checks at once.\n */\nexport const startDaemon = async (options: DaemonOptions): Promise<Daemon> => {\n const {\n tick,\n jitter,\n budget,\n maxPages,\n grace,\n events,\n onChange,\n mcp,\n mcpHost,\n signals = true,\n stateFile: givenState,\n log = (line: string) => void process.stderr.write(`${line}\\n`),\n ...open\n } = options;\n const tickMs = msOf(\"tick\", tick ?? DAEMON_DEFAULTS.tickMs, DAEMON_DEFAULTS.minTickMs);\n const jitterMs = msOf(\"jitter\", jitter ?? DAEMON_DEFAULTS.jitterMs, 0);\n const budgetMs = msOf(\"budget\", budget ?? DAEMON_DEFAULTS.budgetMs, 0);\n const pages = pagesOf(maxPages);\n const graceMs = msOf(\"grace\", grace ?? DAEMON_DEFAULTS.graceMs, 0);\n const stateFile = givenState ?? daemonStateFile(open.dir);\n const before = readDaemonState(open.dir, stateFile);\n if (before?.alive === true) {\n throw new Error(\n `a daemon runs on the store ${open.dir} already (process ${String(before.pid)}): stop it first, or remove ${stateFile} when no daemon runs`,\n );\n }\n const writeEvent = eventWriter(events);\n // a missing browser turns rendering off one time, at start\n const renderOff = open.renderer === undefined ? await browserMissing() : undefined;\n const crawler = openCrawler({\n ...open,\n ...(renderOff === undefined ? {} : { renderer: false as const }),\n });\n const clock = open.clock ?? realClock;\n const startedAt = iso(clock.now());\n let lastError: DaemonStatus[\"last_error\"] = null;\n let service: DaemonMcpService | undefined;\n let tokenFile: string | null = null;\n let schedule: WatchSchedule | undefined;\n let nextDue: readonly DaemonDue[] = [];\n let stoppedAt: string | null = null;\n\n const status = (): DaemonStatus => {\n const counts = schedule?.result() ?? { ticks: 0, checked: 0, changes: 0, last: null };\n return {\n schema: DAEMON_STATE_SCHEMA,\n version: VERSION,\n pid: process.pid,\n store: open.dir,\n running: stoppedAt === null,\n started_at: startedAt,\n updated_at: iso(clock.now()),\n stopped_at: stoppedAt,\n tick_ms: tickMs,\n jitter_ms: jitterMs,\n budget_ms: budgetMs,\n max_pages: pages,\n ticks: counts.ticks,\n checked: counts.checked,\n changes: counts.changes,\n last_tick: counts.last,\n next_tick_at: stoppedAt === null ? (counts.last?.next_at ?? startedAt) : null,\n next_due: nextDue,\n tasks: service?.tasks() ?? [],\n mcp: service === undefined ? null : { url: service.url, token_file: tokenFile },\n last_error: lastError,\n };\n };\n const save = (): void => {\n try {\n writeState(stateFile, status());\n } catch (error) {\n log(`polite-crawler: the state file ${stateFile} does not write: ${errorOf(error).message}`);\n }\n };\n const fail = (error: unknown, where: string): void => {\n const message = `${where}: ${errorOf(error).message}`;\n lastError = { at: iso(clock.now()), message };\n log(`polite-crawler: ${message}`);\n };\n\n if (mcp !== undefined) {\n let given: DaemonMcpOptions = { ...mcp, renderOff: mcp.renderOff ?? renderOff };\n try {\n if (mcp.token === undefined && mcp.tokenFile === undefined) {\n // a new token at each start: a token that leaked ends at the next start\n tokenFile = join(dirname(stateFile), \"mcp-token\");\n given = { ...given, token: writeToken(tokenFile) };\n } else tokenFile = mcp.tokenFile ?? null;\n service = await (mcpHost ?? defaultHost).start(crawler, given, {\n status,\n onTasks: save,\n log,\n });\n } catch (error) {\n await crawler.close();\n throw error;\n }\n }\n\n // the hooks run in order, one at a time, so a slow hook does not start many processes\n let hooks: Promise<void> = Promise.resolve();\n const changed = (event: WatchEvent): void => {\n if (writeEvent !== undefined) {\n try {\n writeEvent(JSON.stringify(event));\n } catch (error) {\n fail(error, \"the event log\");\n }\n }\n if (onChange !== undefined) {\n hooks = hooks\n .then(() => onChange(event))\n .catch((error: unknown) => fail(error, \"the on-change hook\"));\n }\n };\n\n RUNNING.add(stateFile);\n const running = scheduleWatches(crawler.watch, {\n tick: tickMs,\n jitter: jitterMs,\n budget: budgetMs,\n maxPages: pages,\n clock,\n random: open.random,\n onEvent: (event) => {\n if (event.confirmed) changed(event);\n },\n onTick: (done) => {\n nextDue = done.due;\n // a run that failed went to onError\n if (done.summary !== null && done.summary.checked > 0) {\n const summary = done.summary;\n log(\n `polite-crawler: ${String(summary.checked)} checked: ${String(summary.confirmed)} changes, ${String(summary.suspected)} suspected, ${String(summary.refused)} refused, ${String(summary.error)} errors. Next tick at ${done.next_at ?? \"the stop\"}.`,\n );\n }\n save();\n },\n onError: (error) => fail(error, \"a tick\"),\n });\n schedule = running;\n save();\n log(\n `polite-crawler ${VERSION}: the daemon runs on the store ${open.dir}, a tick each ${String(tickMs)} ms${service === undefined ? \"\" : `, MCP at ${service.url}${tokenFile === null ? \"\" : ` (the token is in ${tokenFile})`}`}`,\n );\n if (renderOff !== undefined) log(`polite-crawler: rendering is off: ${renderOff}`);\n\n let resolveDone: (final: DaemonStatus) => void = () => undefined;\n const done = new Promise<DaemonStatus>((resolve) => {\n resolveDone = resolve;\n });\n let stopping: Promise<DaemonStatus> | undefined;\n let signalled = 0;\n const onSignal = (signal: NodeJS.Signals): void => {\n signalled += 1;\n log(\n signalled > 1\n ? `polite-crawler: ${signal} again: the checks in progress abort now`\n : `polite-crawler: ${signal}: the daemon stops, and the checks in progress get ${String(graceMs)} ms`,\n );\n void stop(signalled > 1 ? 0 : graceMs);\n };\n const stop = (stopGrace: Duration = graceMs): Promise<DaemonStatus> => {\n const ms = msOf(\"grace\", stopGrace, 0);\n void running.stop(ms);\n stopping ??= (async () => {\n const closing = service?.close().catch((error: unknown) => fail(error, \"the MCP server\"));\n await running.done;\n // the hooks in the queue get the grace too\n let timer: ReturnType<typeof setTimeout> | undefined;\n await Promise.race([\n hooks,\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, ms);\n }),\n ]);\n clearTimeout(timer);\n await closing;\n stoppedAt = iso(clock.now());\n save();\n RUNNING.delete(stateFile);\n await crawler.close();\n if (signals) {\n process.removeListener(\"SIGINT\", onSignal);\n process.removeListener(\"SIGTERM\", onSignal);\n }\n log(\"polite-crawler: the daemon stopped\");\n const final = status();\n resolveDone(final);\n return final;\n })();\n return stopping;\n };\n if (signals) {\n process.on(\"SIGINT\", onSignal);\n process.on(\"SIGTERM\", onSignal);\n }\n return { crawler, stateFile, mcp: service, status, stop, done };\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
2
|
-
import {
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
|
+
import { P as stableStringify, c as isHeldBack, l as isoTime, m as versionAtTime, n as HELD_BACK, s as heldBackMeta } from "./runtime-z6x0pWSN.mjs";
|
|
3
3
|
//#region src/diff.ts
|
|
4
4
|
var diff_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
DIFF_LIMITS: () => DIFF_LIMITS,
|
|
@@ -553,6 +553,8 @@ const sideOf = (version, at, held) => ({
|
|
|
553
553
|
* This function compares two versions of a stored document, and writes nothing. It applies the
|
|
554
554
|
* rule for a held-back document. When the document or a version is held back, the diff compares
|
|
555
555
|
* only the structural metadata, and it shows no text and no title.
|
|
556
|
+
*
|
|
557
|
+
* @beta
|
|
556
558
|
*/
|
|
557
559
|
const diffDocument = async (runtime, id, options = {}) => {
|
|
558
560
|
await Promise.resolve();
|
|
@@ -627,6 +629,8 @@ const wordsText = (line) => line.words === null ? `[-${line.before}-]{+${line.af
|
|
|
627
629
|
* This function gives the text of a diff for a person or an assistant. The text has the counts, the
|
|
628
630
|
* hunks as a unified diff, and the changes of the metadata. A changed line marks its words with `[-removed-]`
|
|
629
631
|
* and `{+added+}`. The text holds the text of the pages, so the MCP server puts it in an envelope.
|
|
632
|
+
*
|
|
633
|
+
* @beta
|
|
630
634
|
*/
|
|
631
635
|
const formatDiff = (diff) => {
|
|
632
636
|
const { summary } = diff;
|
|
@@ -645,4 +649,4 @@ const formatDiff = (diff) => {
|
|
|
645
649
|
//#endregion
|
|
646
650
|
export { formatDiff as a, diff_exports as i, diffDocument as n, diffTexts as r, DIFF_LIMITS as t };
|
|
647
651
|
|
|
648
|
-
//# sourceMappingURL=diff-
|
|
652
|
+
//# sourceMappingURL=diff-ByOuHmq-.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-BUM91Rev.mjs","names":[],"sources":["../src/diff.ts"],"sourcesContent":["import type { CrawlRuntime } from \"./runtime.ts\";\nimport type { VersionText } from \"./docs.ts\";\nimport { HELD_BACK, heldBackMeta, isHeldBack, isoTime, versionAtTime } from \"./runtime.ts\";\nimport { stableStringify } from \"./record.ts\";\n\n/**\n * A diff compares two versions of a stored document. It gives the changes of the text as a line\n * diff, with a word diff inside each changed line. It gives the changes of the title, the URL and\n * the metadata (the JSON-LD too) as changes at a path.\n *\n * The line diff is the algorithm of Myers (1986). Its time is linear in the length of the texts\n * when the change is small. A budget of steps (`DIFF_LIMITS`) bounds the time of a large change.\n * After the budget, the diff gives the rest of the change as one removal and one addition. Then\n * the result says that it is not minimal. A huge text gives its first part, and the result says so.\n *\n * A held-back version gives no text and no title to a diff, and only its structural metadata.\n */\n\n/** These are the limits of a diff. Each limit bounds the time or the size of a result. */\nexport const DIFF_LIMITS = {\n /** The characters of each text that a diff compares. A longer text gives its first part. */\n maxChars: 1_000_000,\n /** The lines of each text that a diff compares. A longer text gives its first lines. */\n maxLines: 50_000,\n /** The steps of the line diff. After them, the rest of the change is one removal and one addition. */\n maxSteps: 1_000_000,\n /** The steps of the word diffs of all the changed lines together. */\n maxWordSteps: 1_000_000,\n /** The words of one line that a word diff compares. A longer line gets no word diff. */\n maxLineWords: 5_000,\n /** The lines of context before and after each change. */\n context: 2,\n /** The lines that the hunks of a result show. The summary counts each line. */\n maxHunkLines: 2_000,\n /** The changes of the metadata that a result shows. The summary counts each change. */\n maxMetaChanges: 200,\n /** The depth of the metadata that a diff compares. A deeper change is a change of the whole value. */\n maxMetaDepth: 16,\n /** The characters of the JSON of each value of a change of the metadata. */\n maxValueChars: 300,\n} as const;\n\n/** These are the limits of a diff, one by one. `DIFF_LIMITS` gives the defaults. */\nexport type DiffLimits = { readonly [K in keyof typeof DIFF_LIMITS]: number };\n\n/** This is a part of a changed line: words that stay, words that the new text adds, or words that it removes. */\nexport interface WordPart {\n readonly op: \"same\" | \"added\" | \"removed\";\n readonly text: string;\n}\n\n/**\n * This is one line of a hunk. A `same` line is context. A `changed` line pairs a removed line with\n * an added line, and `words` gives the word diff, or null when the word budget ended.\n */\nexport type DiffLine =\n | { readonly op: \"same\" | \"added\" | \"removed\"; readonly text: string }\n | {\n readonly op: \"changed\";\n readonly before: string;\n readonly after: string;\n readonly words: readonly WordPart[] | null;\n };\n\n/** This is a part of the diff of the text: the changed lines with some context, as in a unified diff. */\nexport interface DiffHunk {\n /** This value is the number (from 1) of the first line of the hunk in the old text. */\n readonly from_line: number;\n /** This value is the number of lines of the old text in the hunk. */\n readonly from_count: number;\n /** This value is the number (from 1) of the first line of the hunk in the new text. */\n readonly to_line: number;\n /** This value is the number of lines of the new text in the hunk. */\n readonly to_count: number;\n readonly lines: readonly DiffLine[];\n}\n\n/**\n * This is one change of the title, the URL or the metadata. The path names the value, for\n * example `meta.jsonld[0].openingHours`. `before` and `after` are the JSON of the values, cut to\n * `DIFF_LIMITS.maxValueChars`.\n */\nexport interface MetaChange {\n readonly path: string;\n readonly change: \"added\" | \"removed\" | \"changed\";\n /** This text is the JSON of the old value, or null when the new version adds the value. */\n readonly before: string | null;\n /** This text is the JSON of the new value, or null when the new version removes the value. */\n readonly after: string | null;\n}\n\n/** These are the counts of a diff. */\nexport interface DiffSummary {\n /** This value counts the lines that only the new text has. */\n readonly lines_added: number;\n /** This value counts the lines that only the old text has. */\n readonly lines_removed: number;\n /** This value counts the pairs of a removed line and an added line that the result shows as one changed line. */\n readonly lines_changed: number;\n /** This value counts the words that the new text adds, in the added lines and in the changed lines. */\n readonly words_added: number;\n /** This value counts the words that the new text removes, in the removed lines and in the changed lines. */\n readonly words_removed: number;\n /** This value counts the changes of the title, the URL and the metadata. */\n readonly meta_changes: number;\n}\n\n/** This is the input of `diffTexts`: the parts of a version that a diff compares. */\nexport interface DiffInput {\n readonly text: string;\n readonly fields: Readonly<Record<string, unknown>>;\n}\n\n/** This is the result of `diffTexts`. */\nexport interface TextDiff {\n readonly summary: DiffSummary;\n readonly hunks: readonly DiffHunk[];\n readonly meta: readonly MetaChange[];\n /** This value is true when the text and the fields are the same. */\n readonly same: boolean;\n /** This value is true when the line diff is minimal and each changed line has its word diff. */\n readonly exact: boolean;\n /** This value is true when the result leaves out a part. It can leave out the end of a long text, lines of the hunks or changes of the metadata. */\n readonly truncated: boolean;\n /** This value counts the steps of the line diff and of the word diffs. A test reads it: a step count does not change with the computer. */\n readonly steps: number;\n readonly notes: readonly string[];\n}\n\n/** A run of the edit script. `0` keeps lines of the two texts. `1` removes lines of the old text, and `2` adds lines of the new text. */\ninterface Run {\n readonly op: 0 | 1 | 2;\n readonly count: number;\n}\n\n/** A budget of steps. The diffs add to `steps`, and stop when it passes `max`. */\ninterface Budget {\n steps: number;\n max: number;\n}\n\n/**\n * This function gives an edit script from `a` to `b`, with the algorithm of Myers. It first\n * removes the common start and the common end. When the budget ends, it keeps the furthest path\n * that it found, and the rest is one removal and one addition. `exact` is then false.\n */\nexport const editScript = (\n a: ArrayLike<number>,\n b: ArrayLike<number>,\n budget: Budget,\n): { readonly runs: Run[]; readonly exact: boolean } => {\n const n = a.length;\n const m = b.length;\n let head = 0;\n while (head < n && head < m && a[head] === b[head]) head += 1;\n let endA = n;\n let endB = m;\n while (endA > head && endB > head && a[endA - 1] === b[endB - 1]) {\n endA -= 1;\n endB -= 1;\n }\n budget.steps += head + (n - endA) + 1;\n const reversed: Array<[0 | 1 | 2, number]> = [];\n const width = endA - head;\n const height = endB - head;\n let exact = true;\n reversed.push([0, n - endA]);\n if (width === 0 || height === 0) {\n reversed.push([2, height], [1, width]);\n } else {\n const at = (x: number): number => a[head + x]!;\n const bt = (y: number): number => b[head + y]!;\n const max = width + height;\n const offset = max + 1;\n const v = new Int32Array(2 * max + 3);\n // trace[d] is the band of v after the round d: the index k + d holds the furthest x on the diagonal k\n const trace: Int32Array[] = [];\n let end: { readonly x: number; readonly y: number; readonly d: number } | undefined;\n rounds: for (let d = 0; d <= max; d += 1) {\n for (let k = -d; k <= d; k += 2) {\n budget.steps += 1;\n if (budget.steps > budget.max) break rounds;\n let x =\n k === -d || (k !== d && v[offset + k - 1]! < v[offset + k + 1]!)\n ? v[offset + k + 1]!\n : v[offset + k - 1]! + 1;\n let y = x - k;\n while (x < width && y < height && at(x) === bt(y)) {\n x += 1;\n y += 1;\n budget.steps += 1;\n }\n v[offset + k] = x;\n if (x >= width && y >= height) {\n end = { x, y, d };\n break rounds;\n }\n }\n trace.push(v.slice(offset - d, offset + d + 1));\n }\n if (end === undefined) {\n // the budget ended: the furthest point of the last full round, and the rest as one change\n exact = false;\n const d = trace.length - 1;\n let x = 0;\n let y = 0;\n if (d >= 0) {\n const band = trace[d]!;\n for (let k = -d; k <= d; k += 2) {\n const fx = band[k + d]!;\n const fy = fx - k;\n if (fx <= width && fy >= 0 && fy <= height && fx + fy > x + y) {\n x = fx;\n y = fy;\n }\n }\n }\n reversed.push([2, height - y], [1, width - x]);\n end = { x, y, d: Math.max(0, d) };\n }\n let { x, y } = end;\n for (let d = end.d; d > 0; d -= 1) {\n const band = trace[d - 1]!;\n const k = x - y;\n const before = (kk: number): number => band[kk + d - 1]!;\n const down = k === -d || (k !== d && before(k - 1) < before(k + 1));\n const fromK = down ? k + 1 : k - 1;\n const fromX = before(fromK);\n const fromY = fromX - fromK;\n const moved = down ? fromX : fromX + 1;\n reversed.push([0, x - moved], down ? [2, 1] : [1, 1]);\n x = fromX;\n y = fromY;\n }\n reversed.push([0, x]);\n }\n reversed.push([0, head]);\n const runs: Run[] = [];\n for (let at = reversed.length - 1; at >= 0; at -= 1) {\n const [op, count] = reversed[at]!;\n if (count <= 0) continue;\n const last = runs.at(-1);\n if (last !== undefined && last.op === op)\n runs[runs.length - 1] = { op, count: last.count + count };\n else runs.push({ op, count });\n }\n return { runs, exact };\n};\n\n/** This function gives a number for each value, equal numbers for equal values. */\nconst numberer = (): ((value: string) => number) => {\n const ids = new Map<string, number>();\n return (value) => {\n let id = ids.get(value);\n if (id === undefined) {\n id = ids.size;\n ids.set(value, id);\n }\n return id;\n };\n};\n\n/** A token is a word, a run of spaces, or one sign. */\nconst TOKEN = /\\s+|[\\p{L}\\p{N}\\p{M}_]+|[^\\s\\p{L}\\p{N}\\p{M}_]/gu;\n\nconst tokensOf = (line: string): string[] => line.match(TOKEN) ?? [];\n\n/** This function counts the words of a text: each token that is not a space. */\nconst wordsOf = (text: string): number => {\n let count = 0;\n for (const token of tokensOf(text)) if (!/^\\s+$/.test(token)) count += 1;\n return count;\n};\n\n/** This function gives the word diff of a changed line, or null when the line is too long or the budget ended. */\nconst wordDiff = (\n before: string,\n after: string,\n budget: Budget,\n limits: DiffLimits,\n): { readonly parts: WordPart[]; readonly exact: boolean } | null => {\n if (budget.steps >= budget.max) return null;\n const a = tokensOf(before);\n const b = tokensOf(after);\n if (a.length > limits.maxLineWords || b.length > limits.maxLineWords) return null;\n const id = numberer();\n const script = editScript(\n a.map((token) => id(token)),\n b.map((token) => id(token)),\n budget,\n );\n const parts: WordPart[] = [];\n const push = (op: WordPart[\"op\"], text: string): void => {\n const last = parts.at(-1);\n if (last !== undefined && last.op === op)\n parts[parts.length - 1] = { op, text: last.text + text };\n else parts.push({ op, text });\n };\n let i = 0;\n let j = 0;\n for (const run of script.runs) {\n if (run.op === 0) {\n push(\"same\", a.slice(i, i + run.count).join(\"\"));\n i += run.count;\n j += run.count;\n } else if (run.op === 1) {\n push(\"removed\", a.slice(i, i + run.count).join(\"\"));\n i += run.count;\n } else {\n push(\"added\", b.slice(j, j + run.count).join(\"\"));\n j += run.count;\n }\n }\n return { parts, exact: script.exact };\n};\n\n/** One line of the full diff, before the hunks: its kind and its positions in the old and the new text. */\ntype Entry =\n | { readonly op: \"same\"; readonly a: number; readonly b: number }\n | { readonly op: \"removed\"; readonly a: number }\n | { readonly op: \"added\"; readonly b: number }\n | {\n readonly op: \"changed\";\n readonly a: number;\n readonly b: number;\n readonly words: readonly WordPart[] | null;\n };\n\nconst IDENTIFIER = /^[A-Za-z_$][\\w$]*$/;\n\nconst keyPath = (path: string, key: string): string =>\n IDENTIFIER.test(key) ? (path === \"\" ? key : `${path}.${key}`) : `${path}[${JSON.stringify(key)}]`;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/** This function gives the changes at paths between two values, to the limits. */\nconst metaChanges = (\n before: unknown,\n after: unknown,\n limits: DiffLimits,\n): { readonly changes: MetaChange[]; readonly count: number } => {\n const changes: MetaChange[] = [];\n let count = 0;\n const show = (value: unknown): string => {\n const text = stableStringify(value);\n return text.length > limits.maxValueChars ? `${text.slice(0, limits.maxValueChars)}…` : text;\n };\n const note = (path: string, change: MetaChange[\"change\"], a: unknown, b: unknown): void => {\n count += 1;\n if (changes.length < limits.maxMetaChanges) {\n changes.push({\n path,\n change,\n before: change === \"added\" ? null : show(a),\n after: change === \"removed\" ? null : show(b),\n });\n }\n };\n // each array diff has a small budget of its own, so the metadata stays bounded too\n const walk = (a: unknown, b: unknown, path: string, depth: number): void => {\n if (a === b) return;\n if (depth >= limits.maxMetaDepth) {\n if (stableStringify(a) !== stableStringify(b)) note(path, \"changed\", a, b);\n return;\n }\n if (isRecord(a) && isRecord(b)) {\n const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();\n for (const key of keys) {\n const inA = Object.hasOwn(a, key) && a[key] !== undefined;\n const inB = Object.hasOwn(b, key) && b[key] !== undefined;\n const at = keyPath(path, key);\n if (inA && !inB) note(at, \"removed\", a[key], undefined);\n else if (!inA && inB) note(at, \"added\", undefined, b[key]);\n else if (inA && inB) walk(a[key], b[key], at, depth + 1);\n }\n return;\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n const id = numberer();\n const script = editScript(\n a.map((item) => id(stableStringify(item))),\n b.map((item) => id(stableStringify(item))),\n { steps: 0, max: 100_000 },\n );\n let i = 0;\n let j = 0;\n const removed: number[] = [];\n const added: number[] = [];\n const flush = (): void => {\n const pairs = Math.min(removed.length, added.length);\n for (let at = 0; at < pairs; at += 1) {\n walk(a[removed[at]!], b[added[at]!], `${path}[${String(added[at])}]`, depth + 1);\n }\n for (const at of removed.slice(pairs))\n note(`${path}[${String(at)}]`, \"removed\", a[at], undefined);\n for (const at of added.slice(pairs))\n note(`${path}[${String(at)}]`, \"added\", undefined, b[at]);\n removed.length = 0;\n added.length = 0;\n };\n for (const run of script.runs) {\n if (run.op === 0) {\n flush();\n i += run.count;\n j += run.count;\n } else if (run.op === 1) {\n for (let at = 0; at < run.count; at += 1) removed.push(i + at);\n i += run.count;\n } else {\n for (let at = 0; at < run.count; at += 1) added.push(j + at);\n j += run.count;\n }\n }\n flush();\n return;\n }\n if (stableStringify(a) !== stableStringify(b)) note(path, \"changed\", a, b);\n };\n walk(before, after, \"\", 0);\n return { changes, count };\n};\n\n/** This function gives the limits of a diff, with the defaults of `DIFF_LIMITS` for the limits that it does not get. */\nconst limitsOf = (given: Partial<DiffLimits>): DiffLimits => {\n const limits: Record<string, number> = { ...DIFF_LIMITS };\n for (const [name, value] of Object.entries(given)) {\n if (value === undefined) continue;\n if (!Number.isInteger(value) || value < 0)\n throw new RangeError(`the diff limit ${name} must be an integer of 0 or more`);\n limits[name] = value;\n }\n return limits as DiffLimits;\n};\n\n/**\n * This function compares two versions: the lines of the text, the words of each changed line, and\n * the fields (the title, the URL and the metadata) at their paths. It writes nothing, and its time\n * and its result stay inside the limits.\n */\nexport const diffTexts = (\n before: DiffInput,\n after: DiffInput,\n given: Partial<DiffLimits> = {},\n): TextDiff => {\n const limits = limitsOf(given);\n const notes: string[] = [];\n let truncated = false;\n const cut = (text: string, side: string): string[] => {\n let kept = text;\n if (kept.length > limits.maxChars) {\n kept = kept.slice(0, limits.maxChars);\n truncated = true;\n notes.push(\n `the ${side} text is longer than ${String(limits.maxChars)} characters, so the diff compares its first part`,\n );\n }\n const lines = kept.length === 0 ? [] : kept.split(\"\\n\");\n if (lines.length > limits.maxLines) {\n truncated = true;\n notes.push(\n `the ${side} text has more than ${String(limits.maxLines)} lines, so the diff compares its first lines`,\n );\n return lines.slice(0, limits.maxLines);\n }\n return lines;\n };\n const a = cut(before.text, \"old\");\n const b = cut(after.text, \"new\");\n const id = numberer();\n const lineBudget: Budget = { steps: 0, max: limits.maxSteps };\n const script = editScript(\n a.map((line) => id(line)),\n b.map((line) => id(line)),\n lineBudget,\n );\n let exact = script.exact;\n if (!script.exact) {\n notes.push(\n `the change is too large for a minimal diff in ${String(limits.maxSteps)} steps, so the diff shows its last part as one removal and one addition`,\n );\n }\n const wordBudget: Budget = { steps: 0, max: limits.maxWordSteps };\n const entries: Entry[] = [];\n let linesAdded = 0;\n let linesRemoved = 0;\n let linesChanged = 0;\n let wordsAdded = 0;\n let wordsRemoved = 0;\n let wordsCut = false;\n let i = 0;\n let j = 0;\n const removed: number[] = [];\n const added: number[] = [];\n const flush = (): void => {\n const pairs = Math.min(removed.length, added.length);\n for (let at = 0; at < pairs; at += 1) {\n const from = removed[at]!;\n const to = added[at]!;\n const words = wordDiff(a[from]!, b[to]!, wordBudget, limits);\n if (words === null) {\n wordsCut = true;\n wordsRemoved += wordsOf(a[from]!);\n wordsAdded += wordsOf(b[to]!);\n } else {\n if (!words.exact) exact = false;\n for (const part of words.parts) {\n if (part.op === \"added\") wordsAdded += wordsOf(part.text);\n else if (part.op === \"removed\") wordsRemoved += wordsOf(part.text);\n }\n }\n entries.push({ op: \"changed\", a: from, b: to, words: words?.parts ?? null });\n }\n for (const from of removed.slice(pairs)) {\n wordsRemoved += wordsOf(a[from]!);\n entries.push({ op: \"removed\", a: from });\n }\n for (const to of added.slice(pairs)) {\n wordsAdded += wordsOf(b[to]!);\n entries.push({ op: \"added\", b: to });\n }\n linesChanged += pairs;\n removed.length = 0;\n added.length = 0;\n };\n for (const run of script.runs) {\n if (run.op === 0) {\n flush();\n for (let at = 0; at < run.count; at += 1) entries.push({ op: \"same\", a: i + at, b: j + at });\n i += run.count;\n j += run.count;\n } else if (run.op === 1) {\n for (let at = 0; at < run.count; at += 1) removed.push(i + at);\n linesRemoved += run.count;\n i += run.count;\n } else {\n for (let at = 0; at < run.count; at += 1) added.push(j + at);\n linesAdded += run.count;\n j += run.count;\n }\n }\n flush();\n if (wordsCut) {\n exact = false;\n notes.push(\"some changed lines have no word diff: they are too long, or the word budget ended\");\n }\n\n // the hunks: each change with its context, and the hunks that touch become one\n const hunks: DiffHunk[] = [];\n let shown = 0;\n let current:\n { lines: DiffLine[]; from: number; to: number; a: number; b: number; end: number } | undefined;\n const lineOf = (entry: Entry): DiffLine => {\n switch (entry.op) {\n case \"same\":\n return { op: \"same\", text: a[entry.a]! };\n case \"removed\":\n return { op: \"removed\", text: a[entry.a]! };\n case \"added\":\n return { op: \"added\", text: b[entry.b]! };\n default:\n return { op: \"changed\", before: a[entry.a]!, after: b[entry.b]!, words: entry.words };\n }\n };\n const close = (): void => {\n if (current === undefined) return;\n hunks.push({\n from_line: current.from + 1,\n from_count: current.a,\n to_line: current.to + 1,\n to_count: current.b,\n lines: current.lines,\n });\n current = undefined;\n };\n // the position in the old and the new text of each entry\n let posA = 0;\n let posB = 0;\n const positions: Array<readonly [number, number]> = [];\n for (const entry of entries) {\n positions.push([posA, posB]);\n if (entry.op !== \"added\") posA += 1;\n if (entry.op !== \"removed\") posB += 1;\n }\n const changeAt: number[] = [];\n for (const [at, entry] of entries.entries()) if (entry.op !== \"same\") changeAt.push(at);\n let hunkCut = false;\n for (const at of changeAt) {\n if (shown >= limits.maxHunkLines) {\n hunkCut = true;\n break;\n }\n const start = Math.max(0, at - limits.context);\n const stop = Math.min(entries.length - 1, at + limits.context);\n if (current !== undefined && start > current.end + 1) close();\n if (current === undefined) {\n const [fromA, fromB] = positions[start]!;\n current = { lines: [], from: fromA, to: fromB, a: 0, b: 0, end: start - 1 };\n }\n for (let line = current.end + 1; line <= stop; line += 1) {\n if (shown >= limits.maxHunkLines) {\n hunkCut = true;\n break;\n }\n const entry = entries[line]!;\n current.lines.push(lineOf(entry));\n if (entry.op !== \"added\") current.a += 1;\n if (entry.op !== \"removed\") current.b += 1;\n current.end = line;\n shown += 1;\n }\n }\n close();\n if (hunkCut) {\n truncated = true;\n notes.push(\n `the hunks show ${String(limits.maxHunkLines)} lines, and the summary counts each line`,\n );\n }\n\n const fields = metaChanges(before.fields, after.fields, limits);\n if (fields.count > fields.changes.length) {\n truncated = true;\n notes.push(\n `the result shows ${String(fields.changes.length)} of ${String(fields.count)} changes of the metadata`,\n );\n }\n return {\n summary: {\n lines_added: linesAdded,\n lines_removed: linesRemoved,\n lines_changed: linesChanged,\n words_added: wordsAdded,\n words_removed: wordsRemoved,\n meta_changes: fields.count,\n },\n hunks,\n meta: fields.changes,\n same:\n linesAdded === 0 && linesRemoved === 0 && fields.count === 0 && before.text === after.text,\n exact,\n truncated,\n steps: lineBudget.steps + wordBudget.steps,\n notes,\n };\n};\n\n// ── the diff of a stored document ───────────────────────────────────────────\n\n/** A version of a document for a diff: a capture id, or the version that was current at a time. */\nexport type VersionChoice = string | { readonly at: string | Date };\n\n/** These options choose the two versions of a diff. */\nexport interface DiffOptions {\n /**\n * This version is the old side: a capture id, or `{ at }`. The default is the version before\n * `to` with the same recipe and the same kind of capture (plain or rendered).\n */\n readonly from?: VersionChoice | undefined;\n /** This version is the new side: a capture id, or `{ at }`. The default is the newest version. */\n readonly to?: VersionChoice | undefined;\n /** These values change the limits of the diff (`DIFF_LIMITS`). */\n readonly limits?: Partial<DiffLimits> | undefined;\n}\n\n/** This is one side of a diff: the version that it compares. */\nexport interface DiffSide {\n /** This id is the capture of the version. A citation names it. */\n readonly capture: string;\n /** This time (ISO 8601) is the fetch time of the version. */\n readonly fetched_at: string;\n /** This value is the length of the text of the version. */\n readonly chars: number;\n readonly rendered: boolean;\n /** This value is true when the version is held back, so the diff does not show its text. */\n readonly held_back: boolean;\n /** This time (ISO 8601) is the `at` that chose the version, or null for a capture id or a default. */\n readonly at: string | null;\n}\n\n/** The result of a diff of two versions of a stored document. */\nexport interface DiffResult {\n readonly doc: string;\n readonly url: string | null;\n readonly from: DiffSide;\n readonly to: DiffSide;\n /**\n * This value is true when the two versions have the same text, title, URL and metadata. It is\n * null when a side is held back, because then the diff does not compare the texts.\n */\n readonly same: boolean | null;\n readonly summary: DiffSummary;\n readonly hunks: readonly DiffHunk[];\n readonly meta: readonly MetaChange[];\n /** This value is true when a side is held back. Then the result has no text and no title, and only the structural metadata. */\n readonly held_back: boolean;\n /** This value is true when the line diff is minimal and each changed line has its word diff. */\n readonly exact: boolean;\n /** This value is true when the result leaves out a part. The note tells which part. */\n readonly truncated: boolean;\n /** This value counts the steps of the diff. */\n readonly steps: number;\n readonly note: string | null;\n}\n\n/** This function gives the version of a choice, or throws an error that names the problem. */\nconst chosen = (\n runtime: CrawlRuntime,\n id: string,\n choice: VersionChoice,\n): { readonly version: VersionText; readonly at: string | null } => {\n if (typeof choice === \"string\") {\n const version = runtime.index.versionText(id, choice);\n if (version === undefined)\n throw new Error(`the document ${id} has no version from the capture ${choice}`);\n return { version, at: null };\n }\n if (!isRecord(choice) || choice.at === undefined)\n throw new RangeError(\"a version is a capture id, or an object with the time at\");\n const at = isoTime(\"at\", choice.at);\n const found = versionAtTime(runtime, id, at);\n if (found.version === undefined) {\n throw new Error(\n `the document ${id} has no version at ${at} or before${found.first === undefined ? \"\" : `: its first capture is from ${found.first}`}`,\n );\n }\n return { version: found.version, at };\n};\n\nconst sideOf = (version: VersionText, at: string | null, held: boolean): DiffSide => ({\n capture: version.capture,\n fetched_at: version.fetched_at,\n chars: version.chars,\n rendered: version.rendered,\n held_back: held,\n at,\n});\n\n/**\n * This function compares two versions of a stored document, and writes nothing. It applies the\n * rule for a held-back document. When the document or a version is held back, the diff compares\n * only the structural metadata, and it shows no text and no title.\n */\nexport const diffDocument = async (\n runtime: CrawlRuntime,\n id: string,\n options: DiffOptions = {},\n): Promise<DiffResult> => {\n await Promise.resolve();\n const doc = runtime.index.get(id);\n if (doc === undefined)\n throw new Error(`no stored document ${id}: find one with find(), or fetch its page`);\n let to: { readonly version: VersionText; readonly at: string | null };\n if (options.to === undefined) {\n const newest = runtime.index.versions(id, { limit: 1 })[0];\n const version =\n newest === undefined ? undefined : runtime.index.versionText(id, newest.capture);\n if (version === undefined) throw new Error(`the document ${id} has no version`);\n to = { version, at: null };\n } else to = chosen(runtime, id, options.to);\n let from: { readonly version: VersionText; readonly at: string | null } | undefined;\n if (options.from === undefined) {\n // the version before, of the same recipe and kind: a render and a plain body are not a change\n const earlier = runtime.index\n .versions(id, { limit: 1000 })\n .find(\n (version) =>\n version.capture !== to.version.capture &&\n version.recipe === to.version.recipe &&\n version.rendered === to.version.rendered &&\n version.fetched_at <= to.version.fetched_at,\n );\n const version =\n earlier === undefined ? undefined : runtime.index.versionText(id, earlier.capture);\n if (version !== undefined) from = { version, at: null };\n } else from = chosen(runtime, id, options.from);\n const notes: string[] = [];\n if (from === undefined) {\n from = to;\n notes.push(`the document ${id} has no earlier version of the same kind to compare`);\n }\n const docHeld = isHeldBack(doc);\n const fromHeld = docHeld || isHeldBack(from.version);\n const toHeld = docHeld || isHeldBack(to.version);\n const held = fromHeld || toHeld;\n const fields = (version: VersionText): Record<string, unknown> =>\n held\n ? { kind: version.kind, url: version.url, meta: heldBackMeta(version.meta) }\n : { title: version.title, kind: version.kind, url: version.url, meta: version.meta };\n const diff = diffTexts(\n { text: held ? \"\" : from.version.text, fields: fields(from.version) },\n { text: held ? \"\" : to.version.text, fields: fields(to.version) },\n options.limits ?? {},\n );\n if (held) notes.unshift(HELD_BACK);\n notes.push(...diff.notes);\n return {\n doc: doc.doc,\n url: to.version.url ?? doc.url,\n from: sideOf(from.version, from.at, fromHeld),\n to: sideOf(to.version, to.at, toHeld),\n same: held ? null : diff.same,\n summary: diff.summary,\n hunks: diff.hunks,\n meta: diff.meta,\n held_back: held,\n exact: diff.exact,\n truncated: diff.truncated,\n steps: diff.steps,\n note: notes.length > 0 ? notes.join(\"; \") : null,\n };\n};\n\n/** This function gives the text of the word diff of a changed line: `[-removed-]` and `{+added+}` mark the words. */\nconst wordsText = (line: Extract<DiffLine, { op: \"changed\" }>): string =>\n line.words === null\n ? `[-${line.before}-]{+${line.after}+}`\n : line.words\n .map((part) =>\n part.op === \"same\"\n ? part.text\n : part.op === \"added\"\n ? `{+${part.text}+}`\n : `[-${part.text}-]`,\n )\n .join(\"\");\n\n/**\n * This function gives the text of a diff for a person or an assistant. The text has the counts, the\n * hunks as a unified diff, and the changes of the metadata. A changed line marks its words with `[-removed-]`\n * and `{+added+}`. The text holds the text of the pages, so the MCP server puts it in an envelope.\n */\nexport const formatDiff = (diff: Pick<DiffResult, \"summary\" | \"hunks\" | \"meta\">): string => {\n const { summary } = diff;\n const lines: string[] = [\n `the text: ${String(summary.lines_added)} lines added, ${String(summary.lines_removed)} removed (${String(summary.lines_changed)} changed), ${String(summary.words_added)} words added, ${String(summary.words_removed)} removed`,\n ];\n for (const hunk of diff.hunks) {\n lines.push(\n `@@ -${String(hunk.from_line)},${String(hunk.from_count)} +${String(hunk.to_line)},${String(hunk.to_count)} @@`,\n );\n for (const line of hunk.lines) {\n if (line.op === \"changed\") lines.push(`~ ${wordsText(line)}`);\n else lines.push(`${line.op === \"same\" ? \" \" : line.op === \"added\" ? \"+\" : \"-\"} ${line.text}`);\n }\n }\n lines.push(`the metadata: ${String(summary.meta_changes)} changes`);\n for (const change of diff.meta) {\n if (change.change === \"added\") lines.push(`+ ${change.path}: ${change.after ?? \"\"}`);\n else if (change.change === \"removed\") lines.push(`- ${change.path}: ${change.before ?? \"\"}`);\n else lines.push(`~ ${change.path}: ${change.before ?? \"\"} -> ${change.after ?? \"\"}`);\n }\n return lines.join(\"\\n\");\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"diff-ByOuHmq-.mjs","names":[],"sources":["../src/diff.ts"],"sourcesContent":["import type { CrawlRuntime } from \"./runtime.ts\";\nimport type { VersionText } from \"./docs.ts\";\nimport { HELD_BACK, heldBackMeta, isHeldBack, isoTime, versionAtTime } from \"./runtime.ts\";\nimport { stableStringify } from \"./record.ts\";\n\n/**\n * A diff compares two versions of a stored document. It gives the changes of the text as a line\n * diff, with a word diff inside each changed line. It gives the changes of the title, the URL and\n * the metadata (the JSON-LD too) as changes at a path.\n *\n * The line diff is the algorithm of Myers (1986). Its time is linear in the length of the texts\n * when the change is small. A budget of steps (`DIFF_LIMITS`) bounds the time of a large change.\n * After the budget, the diff gives the rest of the change as one removal and one addition. Then\n * the result says that it is not minimal. A huge text gives its first part, and the result says so.\n *\n * A held-back version gives no text and no title to a diff, and only its structural metadata.\n */\n\n/** These are the limits of a diff. Each limit bounds the time or the size of a result. */\nexport const DIFF_LIMITS = {\n /** The characters of each text that a diff compares. A longer text gives its first part. */\n maxChars: 1_000_000,\n /** The lines of each text that a diff compares. A longer text gives its first lines. */\n maxLines: 50_000,\n /** The steps of the line diff. After them, the rest of the change is one removal and one addition. */\n maxSteps: 1_000_000,\n /** The steps of the word diffs of all the changed lines together. */\n maxWordSteps: 1_000_000,\n /** The words of one line that a word diff compares. A longer line gets no word diff. */\n maxLineWords: 5_000,\n /** The lines of context before and after each change. */\n context: 2,\n /** The lines that the hunks of a result show. The summary counts each line. */\n maxHunkLines: 2_000,\n /** The changes of the metadata that a result shows. The summary counts each change. */\n maxMetaChanges: 200,\n /** The depth of the metadata that a diff compares. A deeper change is a change of the whole value. */\n maxMetaDepth: 16,\n /** The characters of the JSON of each value of a change of the metadata. */\n maxValueChars: 300,\n} as const;\n\n/** These are the limits of a diff, one by one. `DIFF_LIMITS` gives the defaults. */\nexport type DiffLimits = { readonly [K in keyof typeof DIFF_LIMITS]: number };\n\n/** This is a part of a changed line: words that stay, words that the new text adds, or words that it removes. */\nexport interface WordPart {\n readonly op: \"same\" | \"added\" | \"removed\";\n readonly text: string;\n}\n\n/**\n * This is one line of a hunk. A `same` line is context. A `changed` line pairs a removed line with\n * an added line, and `words` gives the word diff, or null when the word budget ended.\n */\nexport type DiffLine =\n | { readonly op: \"same\" | \"added\" | \"removed\"; readonly text: string }\n | {\n readonly op: \"changed\";\n readonly before: string;\n readonly after: string;\n readonly words: readonly WordPart[] | null;\n };\n\n/** This is a part of the diff of the text: the changed lines with some context, as in a unified diff. */\nexport interface DiffHunk {\n /** This value is the number (from 1) of the first line of the hunk in the old text. */\n readonly from_line: number;\n /** This value is the number of lines of the old text in the hunk. */\n readonly from_count: number;\n /** This value is the number (from 1) of the first line of the hunk in the new text. */\n readonly to_line: number;\n /** This value is the number of lines of the new text in the hunk. */\n readonly to_count: number;\n readonly lines: readonly DiffLine[];\n}\n\n/**\n * This is one change of the title, the URL or the metadata. The path names the value, for\n * example `meta.jsonld[0].openingHours`. `before` and `after` are the JSON of the values, cut to\n * `DIFF_LIMITS.maxValueChars`.\n */\nexport interface MetaChange {\n readonly path: string;\n readonly change: \"added\" | \"removed\" | \"changed\";\n /** This text is the JSON of the old value, or null when the new version adds the value. */\n readonly before: string | null;\n /** This text is the JSON of the new value, or null when the new version removes the value. */\n readonly after: string | null;\n}\n\n/** These are the counts of a diff. */\nexport interface DiffSummary {\n /** This value counts the lines that only the new text has. */\n readonly lines_added: number;\n /** This value counts the lines that only the old text has. */\n readonly lines_removed: number;\n /** This value counts the pairs of a removed line and an added line that the result shows as one changed line. */\n readonly lines_changed: number;\n /** This value counts the words that the new text adds, in the added lines and in the changed lines. */\n readonly words_added: number;\n /** This value counts the words that the new text removes, in the removed lines and in the changed lines. */\n readonly words_removed: number;\n /** This value counts the changes of the title, the URL and the metadata. */\n readonly meta_changes: number;\n}\n\n/** This is the input of `diffTexts`: the parts of a version that a diff compares. */\nexport interface DiffInput {\n readonly text: string;\n readonly fields: Readonly<Record<string, unknown>>;\n}\n\n/** This is the result of `diffTexts`. */\nexport interface TextDiff {\n readonly summary: DiffSummary;\n readonly hunks: readonly DiffHunk[];\n readonly meta: readonly MetaChange[];\n /** This value is true when the text and the fields are the same. */\n readonly same: boolean;\n /** This value is true when the line diff is minimal and each changed line has its word diff. */\n readonly exact: boolean;\n /** This value is true when the result leaves out a part. It can leave out the end of a long text, lines of the hunks or changes of the metadata. */\n readonly truncated: boolean;\n /** This value counts the steps of the line diff and of the word diffs. A test reads it: a step count does not change with the computer. */\n readonly steps: number;\n readonly notes: readonly string[];\n}\n\n/** A run of the edit script. `0` keeps lines of the two texts. `1` removes lines of the old text, and `2` adds lines of the new text. */\ninterface Run {\n readonly op: 0 | 1 | 2;\n readonly count: number;\n}\n\n/** A budget of steps. The diffs add to `steps`, and stop when it passes `max`. */\ninterface Budget {\n steps: number;\n max: number;\n}\n\n/**\n * This function gives an edit script from `a` to `b`, with the algorithm of Myers. It first\n * removes the common start and the common end. When the budget ends, it keeps the furthest path\n * that it found, and the rest is one removal and one addition. `exact` is then false.\n */\nexport const editScript = (\n a: ArrayLike<number>,\n b: ArrayLike<number>,\n budget: Budget,\n): { readonly runs: Run[]; readonly exact: boolean } => {\n const n = a.length;\n const m = b.length;\n let head = 0;\n while (head < n && head < m && a[head] === b[head]) head += 1;\n let endA = n;\n let endB = m;\n while (endA > head && endB > head && a[endA - 1] === b[endB - 1]) {\n endA -= 1;\n endB -= 1;\n }\n budget.steps += head + (n - endA) + 1;\n const reversed: Array<[0 | 1 | 2, number]> = [];\n const width = endA - head;\n const height = endB - head;\n let exact = true;\n reversed.push([0, n - endA]);\n if (width === 0 || height === 0) {\n reversed.push([2, height], [1, width]);\n } else {\n const at = (x: number): number => a[head + x]!;\n const bt = (y: number): number => b[head + y]!;\n const max = width + height;\n const offset = max + 1;\n const v = new Int32Array(2 * max + 3);\n // trace[d] is the band of v after the round d: the index k + d holds the furthest x on the diagonal k\n const trace: Int32Array[] = [];\n let end: { readonly x: number; readonly y: number; readonly d: number } | undefined;\n rounds: for (let d = 0; d <= max; d += 1) {\n for (let k = -d; k <= d; k += 2) {\n budget.steps += 1;\n if (budget.steps > budget.max) break rounds;\n let x =\n k === -d || (k !== d && v[offset + k - 1]! < v[offset + k + 1]!)\n ? v[offset + k + 1]!\n : v[offset + k - 1]! + 1;\n let y = x - k;\n while (x < width && y < height && at(x) === bt(y)) {\n x += 1;\n y += 1;\n budget.steps += 1;\n }\n v[offset + k] = x;\n if (x >= width && y >= height) {\n end = { x, y, d };\n break rounds;\n }\n }\n trace.push(v.slice(offset - d, offset + d + 1));\n }\n if (end === undefined) {\n // the budget ended: the furthest point of the last full round, and the rest as one change\n exact = false;\n const d = trace.length - 1;\n let x = 0;\n let y = 0;\n if (d >= 0) {\n const band = trace[d]!;\n for (let k = -d; k <= d; k += 2) {\n const fx = band[k + d]!;\n const fy = fx - k;\n if (fx <= width && fy >= 0 && fy <= height && fx + fy > x + y) {\n x = fx;\n y = fy;\n }\n }\n }\n reversed.push([2, height - y], [1, width - x]);\n end = { x, y, d: Math.max(0, d) };\n }\n let { x, y } = end;\n for (let d = end.d; d > 0; d -= 1) {\n const band = trace[d - 1]!;\n const k = x - y;\n const before = (kk: number): number => band[kk + d - 1]!;\n const down = k === -d || (k !== d && before(k - 1) < before(k + 1));\n const fromK = down ? k + 1 : k - 1;\n const fromX = before(fromK);\n const fromY = fromX - fromK;\n const moved = down ? fromX : fromX + 1;\n reversed.push([0, x - moved], down ? [2, 1] : [1, 1]);\n x = fromX;\n y = fromY;\n }\n reversed.push([0, x]);\n }\n reversed.push([0, head]);\n const runs: Run[] = [];\n for (let at = reversed.length - 1; at >= 0; at -= 1) {\n const [op, count] = reversed[at]!;\n if (count <= 0) continue;\n const last = runs.at(-1);\n if (last !== undefined && last.op === op)\n runs[runs.length - 1] = { op, count: last.count + count };\n else runs.push({ op, count });\n }\n return { runs, exact };\n};\n\n/** This function gives a number for each value, equal numbers for equal values. */\nconst numberer = (): ((value: string) => number) => {\n const ids = new Map<string, number>();\n return (value) => {\n let id = ids.get(value);\n if (id === undefined) {\n id = ids.size;\n ids.set(value, id);\n }\n return id;\n };\n};\n\n/** A token is a word, a run of spaces, or one sign. */\nconst TOKEN = /\\s+|[\\p{L}\\p{N}\\p{M}_]+|[^\\s\\p{L}\\p{N}\\p{M}_]/gu;\n\nconst tokensOf = (line: string): string[] => line.match(TOKEN) ?? [];\n\n/** This function counts the words of a text: each token that is not a space. */\nconst wordsOf = (text: string): number => {\n let count = 0;\n for (const token of tokensOf(text)) if (!/^\\s+$/.test(token)) count += 1;\n return count;\n};\n\n/** This function gives the word diff of a changed line, or null when the line is too long or the budget ended. */\nconst wordDiff = (\n before: string,\n after: string,\n budget: Budget,\n limits: DiffLimits,\n): { readonly parts: WordPart[]; readonly exact: boolean } | null => {\n if (budget.steps >= budget.max) return null;\n const a = tokensOf(before);\n const b = tokensOf(after);\n if (a.length > limits.maxLineWords || b.length > limits.maxLineWords) return null;\n const id = numberer();\n const script = editScript(\n a.map((token) => id(token)),\n b.map((token) => id(token)),\n budget,\n );\n const parts: WordPart[] = [];\n const push = (op: WordPart[\"op\"], text: string): void => {\n const last = parts.at(-1);\n if (last !== undefined && last.op === op)\n parts[parts.length - 1] = { op, text: last.text + text };\n else parts.push({ op, text });\n };\n let i = 0;\n let j = 0;\n for (const run of script.runs) {\n if (run.op === 0) {\n push(\"same\", a.slice(i, i + run.count).join(\"\"));\n i += run.count;\n j += run.count;\n } else if (run.op === 1) {\n push(\"removed\", a.slice(i, i + run.count).join(\"\"));\n i += run.count;\n } else {\n push(\"added\", b.slice(j, j + run.count).join(\"\"));\n j += run.count;\n }\n }\n return { parts, exact: script.exact };\n};\n\n/** One line of the full diff, before the hunks: its kind and its positions in the old and the new text. */\ntype Entry =\n | { readonly op: \"same\"; readonly a: number; readonly b: number }\n | { readonly op: \"removed\"; readonly a: number }\n | { readonly op: \"added\"; readonly b: number }\n | {\n readonly op: \"changed\";\n readonly a: number;\n readonly b: number;\n readonly words: readonly WordPart[] | null;\n };\n\nconst IDENTIFIER = /^[A-Za-z_$][\\w$]*$/;\n\nconst keyPath = (path: string, key: string): string =>\n IDENTIFIER.test(key) ? (path === \"\" ? key : `${path}.${key}`) : `${path}[${JSON.stringify(key)}]`;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/** This function gives the changes at paths between two values, to the limits. */\nconst metaChanges = (\n before: unknown,\n after: unknown,\n limits: DiffLimits,\n): { readonly changes: MetaChange[]; readonly count: number } => {\n const changes: MetaChange[] = [];\n let count = 0;\n const show = (value: unknown): string => {\n const text = stableStringify(value);\n return text.length > limits.maxValueChars ? `${text.slice(0, limits.maxValueChars)}…` : text;\n };\n const note = (path: string, change: MetaChange[\"change\"], a: unknown, b: unknown): void => {\n count += 1;\n if (changes.length < limits.maxMetaChanges) {\n changes.push({\n path,\n change,\n before: change === \"added\" ? null : show(a),\n after: change === \"removed\" ? null : show(b),\n });\n }\n };\n // each array diff has a small budget of its own, so the metadata stays bounded too\n const walk = (a: unknown, b: unknown, path: string, depth: number): void => {\n if (a === b) return;\n if (depth >= limits.maxMetaDepth) {\n if (stableStringify(a) !== stableStringify(b)) note(path, \"changed\", a, b);\n return;\n }\n if (isRecord(a) && isRecord(b)) {\n const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();\n for (const key of keys) {\n const inA = Object.hasOwn(a, key) && a[key] !== undefined;\n const inB = Object.hasOwn(b, key) && b[key] !== undefined;\n const at = keyPath(path, key);\n if (inA && !inB) note(at, \"removed\", a[key], undefined);\n else if (!inA && inB) note(at, \"added\", undefined, b[key]);\n else if (inA && inB) walk(a[key], b[key], at, depth + 1);\n }\n return;\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n const id = numberer();\n const script = editScript(\n a.map((item) => id(stableStringify(item))),\n b.map((item) => id(stableStringify(item))),\n { steps: 0, max: 100_000 },\n );\n let i = 0;\n let j = 0;\n const removed: number[] = [];\n const added: number[] = [];\n const flush = (): void => {\n const pairs = Math.min(removed.length, added.length);\n for (let at = 0; at < pairs; at += 1) {\n walk(a[removed[at]!], b[added[at]!], `${path}[${String(added[at])}]`, depth + 1);\n }\n for (const at of removed.slice(pairs))\n note(`${path}[${String(at)}]`, \"removed\", a[at], undefined);\n for (const at of added.slice(pairs))\n note(`${path}[${String(at)}]`, \"added\", undefined, b[at]);\n removed.length = 0;\n added.length = 0;\n };\n for (const run of script.runs) {\n if (run.op === 0) {\n flush();\n i += run.count;\n j += run.count;\n } else if (run.op === 1) {\n for (let at = 0; at < run.count; at += 1) removed.push(i + at);\n i += run.count;\n } else {\n for (let at = 0; at < run.count; at += 1) added.push(j + at);\n j += run.count;\n }\n }\n flush();\n return;\n }\n if (stableStringify(a) !== stableStringify(b)) note(path, \"changed\", a, b);\n };\n walk(before, after, \"\", 0);\n return { changes, count };\n};\n\n/** This function gives the limits of a diff, with the defaults of `DIFF_LIMITS` for the limits that it does not get. */\nconst limitsOf = (given: Partial<DiffLimits>): DiffLimits => {\n const limits: Record<string, number> = { ...DIFF_LIMITS };\n for (const [name, value] of Object.entries(given)) {\n if (value === undefined) continue;\n if (!Number.isInteger(value) || value < 0)\n throw new RangeError(`the diff limit ${name} must be an integer of 0 or more`);\n limits[name] = value;\n }\n return limits as DiffLimits;\n};\n\n/**\n * This function compares two versions: the lines of the text, the words of each changed line, and\n * the fields (the title, the URL and the metadata) at their paths. It writes nothing, and its time\n * and its result stay inside the limits.\n */\nexport const diffTexts = (\n before: DiffInput,\n after: DiffInput,\n given: Partial<DiffLimits> = {},\n): TextDiff => {\n const limits = limitsOf(given);\n const notes: string[] = [];\n let truncated = false;\n const cut = (text: string, side: string): string[] => {\n let kept = text;\n if (kept.length > limits.maxChars) {\n kept = kept.slice(0, limits.maxChars);\n truncated = true;\n notes.push(\n `the ${side} text is longer than ${String(limits.maxChars)} characters, so the diff compares its first part`,\n );\n }\n const lines = kept.length === 0 ? [] : kept.split(\"\\n\");\n if (lines.length > limits.maxLines) {\n truncated = true;\n notes.push(\n `the ${side} text has more than ${String(limits.maxLines)} lines, so the diff compares its first lines`,\n );\n return lines.slice(0, limits.maxLines);\n }\n return lines;\n };\n const a = cut(before.text, \"old\");\n const b = cut(after.text, \"new\");\n const id = numberer();\n const lineBudget: Budget = { steps: 0, max: limits.maxSteps };\n const script = editScript(\n a.map((line) => id(line)),\n b.map((line) => id(line)),\n lineBudget,\n );\n let exact = script.exact;\n if (!script.exact) {\n notes.push(\n `the change is too large for a minimal diff in ${String(limits.maxSteps)} steps, so the diff shows its last part as one removal and one addition`,\n );\n }\n const wordBudget: Budget = { steps: 0, max: limits.maxWordSteps };\n const entries: Entry[] = [];\n let linesAdded = 0;\n let linesRemoved = 0;\n let linesChanged = 0;\n let wordsAdded = 0;\n let wordsRemoved = 0;\n let wordsCut = false;\n let i = 0;\n let j = 0;\n const removed: number[] = [];\n const added: number[] = [];\n const flush = (): void => {\n const pairs = Math.min(removed.length, added.length);\n for (let at = 0; at < pairs; at += 1) {\n const from = removed[at]!;\n const to = added[at]!;\n const words = wordDiff(a[from]!, b[to]!, wordBudget, limits);\n if (words === null) {\n wordsCut = true;\n wordsRemoved += wordsOf(a[from]!);\n wordsAdded += wordsOf(b[to]!);\n } else {\n if (!words.exact) exact = false;\n for (const part of words.parts) {\n if (part.op === \"added\") wordsAdded += wordsOf(part.text);\n else if (part.op === \"removed\") wordsRemoved += wordsOf(part.text);\n }\n }\n entries.push({ op: \"changed\", a: from, b: to, words: words?.parts ?? null });\n }\n for (const from of removed.slice(pairs)) {\n wordsRemoved += wordsOf(a[from]!);\n entries.push({ op: \"removed\", a: from });\n }\n for (const to of added.slice(pairs)) {\n wordsAdded += wordsOf(b[to]!);\n entries.push({ op: \"added\", b: to });\n }\n linesChanged += pairs;\n removed.length = 0;\n added.length = 0;\n };\n for (const run of script.runs) {\n if (run.op === 0) {\n flush();\n for (let at = 0; at < run.count; at += 1) entries.push({ op: \"same\", a: i + at, b: j + at });\n i += run.count;\n j += run.count;\n } else if (run.op === 1) {\n for (let at = 0; at < run.count; at += 1) removed.push(i + at);\n linesRemoved += run.count;\n i += run.count;\n } else {\n for (let at = 0; at < run.count; at += 1) added.push(j + at);\n linesAdded += run.count;\n j += run.count;\n }\n }\n flush();\n if (wordsCut) {\n exact = false;\n notes.push(\"some changed lines have no word diff: they are too long, or the word budget ended\");\n }\n\n // the hunks: each change with its context, and the hunks that touch become one\n const hunks: DiffHunk[] = [];\n let shown = 0;\n let current:\n { lines: DiffLine[]; from: number; to: number; a: number; b: number; end: number } | undefined;\n const lineOf = (entry: Entry): DiffLine => {\n switch (entry.op) {\n case \"same\":\n return { op: \"same\", text: a[entry.a]! };\n case \"removed\":\n return { op: \"removed\", text: a[entry.a]! };\n case \"added\":\n return { op: \"added\", text: b[entry.b]! };\n default:\n return { op: \"changed\", before: a[entry.a]!, after: b[entry.b]!, words: entry.words };\n }\n };\n const close = (): void => {\n if (current === undefined) return;\n hunks.push({\n from_line: current.from + 1,\n from_count: current.a,\n to_line: current.to + 1,\n to_count: current.b,\n lines: current.lines,\n });\n current = undefined;\n };\n // the position in the old and the new text of each entry\n let posA = 0;\n let posB = 0;\n const positions: Array<readonly [number, number]> = [];\n for (const entry of entries) {\n positions.push([posA, posB]);\n if (entry.op !== \"added\") posA += 1;\n if (entry.op !== \"removed\") posB += 1;\n }\n const changeAt: number[] = [];\n for (const [at, entry] of entries.entries()) if (entry.op !== \"same\") changeAt.push(at);\n let hunkCut = false;\n for (const at of changeAt) {\n if (shown >= limits.maxHunkLines) {\n hunkCut = true;\n break;\n }\n const start = Math.max(0, at - limits.context);\n const stop = Math.min(entries.length - 1, at + limits.context);\n if (current !== undefined && start > current.end + 1) close();\n if (current === undefined) {\n const [fromA, fromB] = positions[start]!;\n current = { lines: [], from: fromA, to: fromB, a: 0, b: 0, end: start - 1 };\n }\n for (let line = current.end + 1; line <= stop; line += 1) {\n if (shown >= limits.maxHunkLines) {\n hunkCut = true;\n break;\n }\n const entry = entries[line]!;\n current.lines.push(lineOf(entry));\n if (entry.op !== \"added\") current.a += 1;\n if (entry.op !== \"removed\") current.b += 1;\n current.end = line;\n shown += 1;\n }\n }\n close();\n if (hunkCut) {\n truncated = true;\n notes.push(\n `the hunks show ${String(limits.maxHunkLines)} lines, and the summary counts each line`,\n );\n }\n\n const fields = metaChanges(before.fields, after.fields, limits);\n if (fields.count > fields.changes.length) {\n truncated = true;\n notes.push(\n `the result shows ${String(fields.changes.length)} of ${String(fields.count)} changes of the metadata`,\n );\n }\n return {\n summary: {\n lines_added: linesAdded,\n lines_removed: linesRemoved,\n lines_changed: linesChanged,\n words_added: wordsAdded,\n words_removed: wordsRemoved,\n meta_changes: fields.count,\n },\n hunks,\n meta: fields.changes,\n same:\n linesAdded === 0 && linesRemoved === 0 && fields.count === 0 && before.text === after.text,\n exact,\n truncated,\n steps: lineBudget.steps + wordBudget.steps,\n notes,\n };\n};\n\n// ── the diff of a stored document ───────────────────────────────────────────\n\n/** A version of a document for a diff: a capture id, or the version that was current at a time. */\nexport type VersionChoice = string | { readonly at: string | Date };\n\n/** These options choose the two versions of a diff. */\nexport interface DiffOptions {\n /**\n * This version is the old side: a capture id, or `{ at }`. The default is the version before\n * `to` with the same recipe and the same kind of capture (plain or rendered).\n */\n readonly from?: VersionChoice | undefined;\n /** This version is the new side: a capture id, or `{ at }`. The default is the newest version. */\n readonly to?: VersionChoice | undefined;\n /** These values change the limits of the diff (`DIFF_LIMITS`). */\n readonly limits?: Partial<DiffLimits> | undefined;\n}\n\n/** This is one side of a diff: the version that it compares. */\nexport interface DiffSide {\n /** This id is the capture of the version. A citation names it. */\n readonly capture: string;\n /** This time (ISO 8601) is the fetch time of the version. */\n readonly fetched_at: string;\n /** This value is the length of the text of the version. */\n readonly chars: number;\n readonly rendered: boolean;\n /** This value is true when the version is held back, so the diff does not show its text. */\n readonly held_back: boolean;\n /** This time (ISO 8601) is the `at` that chose the version, or null for a capture id or a default. */\n readonly at: string | null;\n}\n\n/** The result of a diff of two versions of a stored document. */\nexport interface DiffResult {\n readonly doc: string;\n readonly url: string | null;\n readonly from: DiffSide;\n readonly to: DiffSide;\n /**\n * This value is true when the two versions have the same text, title, URL and metadata. It is\n * null when a side is held back, because then the diff does not compare the texts.\n */\n readonly same: boolean | null;\n readonly summary: DiffSummary;\n readonly hunks: readonly DiffHunk[];\n readonly meta: readonly MetaChange[];\n /** This value is true when a side is held back. Then the result has no text and no title, and only the structural metadata. */\n readonly held_back: boolean;\n /** This value is true when the line diff is minimal and each changed line has its word diff. */\n readonly exact: boolean;\n /** This value is true when the result leaves out a part. The note tells which part. */\n readonly truncated: boolean;\n /** This value counts the steps of the diff. */\n readonly steps: number;\n readonly note: string | null;\n}\n\n/** This function gives the version of a choice, or throws an error that names the problem. */\nconst chosen = (\n runtime: CrawlRuntime,\n id: string,\n choice: VersionChoice,\n): { readonly version: VersionText; readonly at: string | null } => {\n if (typeof choice === \"string\") {\n const version = runtime.index.versionText(id, choice);\n if (version === undefined)\n throw new Error(`the document ${id} has no version from the capture ${choice}`);\n return { version, at: null };\n }\n if (!isRecord(choice) || choice.at === undefined)\n throw new RangeError(\"a version is a capture id, or an object with the time at\");\n const at = isoTime(\"at\", choice.at);\n const found = versionAtTime(runtime, id, at);\n if (found.version === undefined) {\n throw new Error(\n `the document ${id} has no version at ${at} or before${found.first === undefined ? \"\" : `: its first capture is from ${found.first}`}`,\n );\n }\n return { version: found.version, at };\n};\n\nconst sideOf = (version: VersionText, at: string | null, held: boolean): DiffSide => ({\n capture: version.capture,\n fetched_at: version.fetched_at,\n chars: version.chars,\n rendered: version.rendered,\n held_back: held,\n at,\n});\n\n/**\n * This function compares two versions of a stored document, and writes nothing. It applies the\n * rule for a held-back document. When the document or a version is held back, the diff compares\n * only the structural metadata, and it shows no text and no title.\n *\n * @beta\n */\nexport const diffDocument = async (\n runtime: CrawlRuntime,\n id: string,\n options: DiffOptions = {},\n): Promise<DiffResult> => {\n await Promise.resolve();\n const doc = runtime.index.get(id);\n if (doc === undefined)\n throw new Error(`no stored document ${id}: find one with find(), or fetch its page`);\n let to: { readonly version: VersionText; readonly at: string | null };\n if (options.to === undefined) {\n const newest = runtime.index.versions(id, { limit: 1 })[0];\n const version =\n newest === undefined ? undefined : runtime.index.versionText(id, newest.capture);\n if (version === undefined) throw new Error(`the document ${id} has no version`);\n to = { version, at: null };\n } else to = chosen(runtime, id, options.to);\n let from: { readonly version: VersionText; readonly at: string | null } | undefined;\n if (options.from === undefined) {\n // the version before, of the same recipe and kind: a render and a plain body are not a change\n const earlier = runtime.index\n .versions(id, { limit: 1000 })\n .find(\n (version) =>\n version.capture !== to.version.capture &&\n version.recipe === to.version.recipe &&\n version.rendered === to.version.rendered &&\n version.fetched_at <= to.version.fetched_at,\n );\n const version =\n earlier === undefined ? undefined : runtime.index.versionText(id, earlier.capture);\n if (version !== undefined) from = { version, at: null };\n } else from = chosen(runtime, id, options.from);\n const notes: string[] = [];\n if (from === undefined) {\n from = to;\n notes.push(`the document ${id} has no earlier version of the same kind to compare`);\n }\n const docHeld = isHeldBack(doc);\n const fromHeld = docHeld || isHeldBack(from.version);\n const toHeld = docHeld || isHeldBack(to.version);\n const held = fromHeld || toHeld;\n const fields = (version: VersionText): Record<string, unknown> =>\n held\n ? { kind: version.kind, url: version.url, meta: heldBackMeta(version.meta) }\n : { title: version.title, kind: version.kind, url: version.url, meta: version.meta };\n const diff = diffTexts(\n { text: held ? \"\" : from.version.text, fields: fields(from.version) },\n { text: held ? \"\" : to.version.text, fields: fields(to.version) },\n options.limits ?? {},\n );\n if (held) notes.unshift(HELD_BACK);\n notes.push(...diff.notes);\n return {\n doc: doc.doc,\n url: to.version.url ?? doc.url,\n from: sideOf(from.version, from.at, fromHeld),\n to: sideOf(to.version, to.at, toHeld),\n same: held ? null : diff.same,\n summary: diff.summary,\n hunks: diff.hunks,\n meta: diff.meta,\n held_back: held,\n exact: diff.exact,\n truncated: diff.truncated,\n steps: diff.steps,\n note: notes.length > 0 ? notes.join(\"; \") : null,\n };\n};\n\n/** This function gives the text of the word diff of a changed line: `[-removed-]` and `{+added+}` mark the words. */\nconst wordsText = (line: Extract<DiffLine, { op: \"changed\" }>): string =>\n line.words === null\n ? `[-${line.before}-]{+${line.after}+}`\n : line.words\n .map((part) =>\n part.op === \"same\"\n ? part.text\n : part.op === \"added\"\n ? `{+${part.text}+}`\n : `[-${part.text}-]`,\n )\n .join(\"\");\n\n/**\n * This function gives the text of a diff for a person or an assistant. The text has the counts, the\n * hunks as a unified diff, and the changes of the metadata. A changed line marks its words with `[-removed-]`\n * and `{+added+}`. The text holds the text of the pages, so the MCP server puts it in an envelope.\n *\n * @beta\n */\nexport const formatDiff = (diff: Pick<DiffResult, \"summary\" | \"hunks\" | \"meta\">): string => {\n const { summary } = diff;\n const lines: string[] = [\n `the text: ${String(summary.lines_added)} lines added, ${String(summary.lines_removed)} removed (${String(summary.lines_changed)} changed), ${String(summary.words_added)} words added, ${String(summary.words_removed)} removed`,\n ];\n for (const hunk of diff.hunks) {\n lines.push(\n `@@ -${String(hunk.from_line)},${String(hunk.from_count)} +${String(hunk.to_line)},${String(hunk.to_count)} @@`,\n );\n for (const line of hunk.lines) {\n if (line.op === \"changed\") lines.push(`~ ${wordsText(line)}`);\n else lines.push(`${line.op === \"same\" ? \" \" : line.op === \"added\" ? \"+\" : \"-\"} ${line.text}`);\n }\n }\n lines.push(`the metadata: ${String(summary.meta_changes)} changes`);\n for (const change of diff.meta) {\n if (change.change === \"added\") lines.push(`+ ${change.path}: ${change.after ?? \"\"}`);\n else if (change.change === \"removed\") lines.push(`- ${change.path}: ${change.before ?? \"\"}`);\n else lines.push(`~ ${change.path}: ${change.before ?? \"\"} -> ${change.after ?? \"\"}`);\n }\n return lines.join(\"\\n\");\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
2
|
-
import {
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
|
+
import { C as fold, c as isHeldBack, d as recordOverride, l as isoTime, m as versionAtTime, n as HELD_BACK, v as archivedNote, w as foldText, y as archivedOf } from "./runtime-z6x0pWSN.mjs";
|
|
3
3
|
import { n as canonicalUrl } from "./urls-BHFeSyEK.mjs";
|
|
4
4
|
import { r as typesOf } from "./html-DkQUAE_d.mjs";
|
|
5
5
|
//#region src/extract.ts
|
|
@@ -8,7 +8,11 @@ var extract_exports = /* @__PURE__ */ __exportAll({
|
|
|
8
8
|
checkFields: () => checkFields,
|
|
9
9
|
extractDocument: () => extractDocument
|
|
10
10
|
});
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* These kinds read the text of the page. The kind `json-ld:<path>` reads the JSON-LD.
|
|
13
|
+
*
|
|
14
|
+
* @beta
|
|
15
|
+
*/
|
|
12
16
|
const FIELD_KINDS = [
|
|
13
17
|
"text",
|
|
14
18
|
"number",
|
|
@@ -1681,6 +1685,8 @@ const summaryOf = (statuses) => {
|
|
|
1681
1685
|
* This function checks the proposed values of fields against the text and the metadata of a
|
|
1682
1686
|
* document. It sends no request and writes nothing. It does not make values. A field that is not
|
|
1683
1687
|
* valid throws a `RangeError`. The work grows in proportion to the length of the text.
|
|
1688
|
+
*
|
|
1689
|
+
* @beta
|
|
1684
1690
|
*/
|
|
1685
1691
|
const checkFields = (source, fields) => {
|
|
1686
1692
|
const claims = claimsOf(fields);
|
|
@@ -1719,6 +1725,8 @@ const sameForEach = (claims, status, note) => Object.fromEntries(claims.map(([na
|
|
|
1719
1725
|
* - A held-back document gives `held-back` for each field, with no position and no capture, unless
|
|
1720
1726
|
* `userProvided`. The crawler then logs an override.
|
|
1721
1727
|
* - A field that is not valid throws a `RangeError`, and an unknown document throws an error.
|
|
1728
|
+
*
|
|
1729
|
+
* @beta
|
|
1722
1730
|
*/
|
|
1723
1731
|
const extractDocument = async (runtime, id, fields, options = {}) => {
|
|
1724
1732
|
const claims = claimsOf(fields);
|
|
@@ -1790,4 +1798,4 @@ const extractDocument = async (runtime, id, fields, options = {}) => {
|
|
|
1790
1798
|
//#endregion
|
|
1791
1799
|
export { extract_exports as i, checkFields as n, extractDocument as r, FIELD_KINDS as t };
|
|
1792
1800
|
|
|
1793
|
-
//# sourceMappingURL=extract-
|
|
1801
|
+
//# sourceMappingURL=extract-Ccgs1ERp.mjs.map
|