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.
Files changed (78) hide show
  1. package/README.md +28 -121
  2. package/build/{browser-CKRNbIDT.mjs → browser-56dBBLec.mjs} +7 -3
  3. package/build/browser-56dBBLec.mjs.map +1 -0
  4. package/build/classify.d.mts +50 -2
  5. package/build/{cli-daemon-ZYRdy6EW.mjs → cli-daemon-40TxewuH.mjs} +2 -2
  6. package/build/{cli-daemon-ZYRdy6EW.mjs.map → cli-daemon-40TxewuH.mjs.map} +1 -1
  7. package/build/{cli-recipes-Bci6HREA.mjs → cli-recipes-COyH6wo4.mjs} +2 -2
  8. package/build/{cli-recipes-Bci6HREA.mjs.map → cli-recipes-COyH6wo4.mjs.map} +1 -1
  9. package/build/cli.mjs +41 -16
  10. package/build/cli.mjs.map +1 -1
  11. package/build/{daemon-DK6U51_v.mjs → daemon-Cm6Mb8EE.mjs} +11 -7
  12. package/build/daemon-Cm6Mb8EE.mjs.map +1 -0
  13. package/build/{diff-BUM91Rev.mjs → diff-ByOuHmq-.mjs} +7 -3
  14. package/build/{diff-BUM91Rev.mjs.map → diff-ByOuHmq-.mjs.map} +1 -1
  15. package/build/{extract-CefB5Vfn.mjs → extract-Ccgs1ERp.mjs} +12 -4
  16. package/build/extract-Ccgs1ERp.mjs.map +1 -0
  17. package/build/html-DkQUAE_d.mjs.map +1 -1
  18. package/build/{html-BACljlkf.d.mts → html-o5bDHjNi.d.mts} +16 -2
  19. package/build/html.d.mts +2 -3
  20. package/build/index.d.mts +4258 -45
  21. package/build/index.mjs +17 -17
  22. package/build/{log-Dx-wXjJl.mjs → log-CmBWcyQm.mjs} +5 -1
  23. package/build/log-CmBWcyQm.mjs.map +1 -0
  24. package/build/mcp-package-Y0bREnOS.mjs +40 -0
  25. package/build/mcp-package-Y0bREnOS.mjs.map +1 -0
  26. package/build/{ollama-BJxkjny4.mjs → ollama-D1xRh3hY.mjs} +7 -3
  27. package/build/ollama-D1xRh3hY.mjs.map +1 -0
  28. package/build/{open-CbpSCEZc.mjs → open-9oUXhGCK.mjs} +78 -18
  29. package/build/open-9oUXhGCK.mjs.map +1 -0
  30. package/build/politeness.d.mts +366 -2
  31. package/build/{recipe-GiFQojmo.mjs → recipe-BODo4plI.mjs} +10 -4
  32. package/build/recipe-BODo4plI.mjs.map +1 -0
  33. package/build/{recipe-product-Dellqudu.mjs → recipe-product-BrDKmDXb.mjs} +29 -7
  34. package/build/recipe-product-BrDKmDXb.mjs.map +1 -0
  35. package/build/{render-C1CUraWF.mjs → render-CKE2LILH.mjs} +10 -3
  36. package/build/render-CKE2LILH.mjs.map +1 -0
  37. package/build/robots.d.mts +143 -2
  38. package/build/robots.mjs +1 -1
  39. package/build/robots.mjs.map +1 -1
  40. package/build/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
  41. package/build/{runtime-D_r2SYge.mjs → runtime-z6x0pWSN.mjs} +84 -18
  42. package/build/runtime-z6x0pWSN.mjs.map +1 -0
  43. package/build/signals.d.mts +45 -2
  44. package/build/{transformers-BQMlaGj4.mjs → transformers-BNiuiLxT.mjs} +3 -3
  45. package/build/transformers-BNiuiLxT.mjs.map +1 -0
  46. package/build/{version-BPem-XZd.mjs → version-cPnAph6k.mjs} +7 -2
  47. package/build/version-cPnAph6k.mjs.map +1 -0
  48. package/build/{wacz-QyzLp4mC.mjs → wacz-Cog-hnVy.mjs} +4 -4
  49. package/build/{wacz-QyzLp4mC.mjs.map → wacz-Cog-hnVy.mjs.map} +1 -1
  50. package/build/{warc-BHcaRKHw.mjs → warc-FLgjirrM.mjs} +4 -4
  51. package/build/{warc-BHcaRKHw.mjs.map → warc-FLgjirrM.mjs.map} +1 -1
  52. package/build/{webbotauth-CjJdTAfE.mjs → webbotauth-Dq_dA_u3.mjs} +2 -2
  53. package/build/webbotauth-Dq_dA_u3.mjs.map +1 -0
  54. package/package.json +5 -11
  55. package/build/browser-CKRNbIDT.mjs.map +0 -1
  56. package/build/classify-CrfyXcwo.d.mts +0 -51
  57. package/build/daemon-DK6U51_v.mjs.map +0 -1
  58. package/build/daemon-wwIWsUmU.d.mts +0 -3777
  59. package/build/extract-CefB5Vfn.mjs.map +0 -1
  60. package/build/log-Dx-wXjJl.mjs.map +0 -1
  61. package/build/loopback-C_QYowSf.mjs +0 -22
  62. package/build/loopback-C_QYowSf.mjs.map +0 -1
  63. package/build/mcp.d.mts +0 -199
  64. package/build/mcp.mjs +0 -1555
  65. package/build/mcp.mjs.map +0 -1
  66. package/build/ollama-BJxkjny4.mjs.map +0 -1
  67. package/build/open-CbpSCEZc.mjs.map +0 -1
  68. package/build/politeness-yI3xBEx9.d.mts +0 -367
  69. package/build/recipe-GiFQojmo.mjs.map +0 -1
  70. package/build/recipe-product-Dellqudu.mjs.map +0 -1
  71. package/build/render-C1CUraWF.mjs.map +0 -1
  72. package/build/robots-R-qaEBOb.d.mts +0 -144
  73. package/build/runtime-D_r2SYge.mjs.map +0 -1
  74. package/build/signals-BHpy2xb2.d.mts +0 -46
  75. package/build/transformers-BQMlaGj4.mjs.map +0 -1
  76. package/build/urls-BTDSgDLC.d.mts +0 -16
  77. package/build/version-BPem-XZd.mjs.map +0 -1
  78. package/build/webbotauth-CjJdTAfE.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"extract-CefB5Vfn.mjs","names":[],"sources":["../src/extract.ts"],"sourcesContent":["import type { VersionText } from \"./docs.ts\";\nimport type { Archived } from \"./fallback.ts\";\nimport { archivedNote, archivedOf } from \"./fallback.ts\";\nimport type { Folded } from \"./fold.ts\";\nimport { fold, foldText } from \"./fold.ts\";\nimport { typesOf } from \"./html.ts\";\nimport type { CrawlRuntime } from \"./runtime.ts\";\n// the time travel of `read` and `verify` chooses the version, so the three agree\nimport { HELD_BACK, isHeldBack, isoTime, recordOverride, versionAtTime } from \"./runtime.ts\";\nimport type { AiInput } from \"./signals.ts\";\nimport { canonicalUrl } from \"./urls.ts\";\n\n/**\n * The field check takes the values that a caller proposes for the fields of a stored page. It finds\n * each value in the stored text or in the JSON-LD. A model or a person gives the values. The check\n * does not make values: it only checks and locates the values that it gets.\n *\n * Each field gets one status:\n *\n * - `verified`: the stored text holds the exact value. The result gives the span (or the JSON-LD\n * path) and the capture.\n * - `normalized`: the stored text holds the value under a stated normalization, for example a\n * different number format. The result names the normalization and gives the span of the stored text.\n * - `ambiguous`: the value matches, but the stored text does not give one meaning. The result gives\n * each span, and it does not choose one.\n * - `not-found`: the stored text does not hold the value.\n * - `held-back`: the page asked not to be AI input. The result tells nothing about the value.\n * - `invalid`: the value is not a value of its kind, so the check did not search for it.\n *\n * The time of the check is linear in the length of the text, for a fixed set of fields.\n */\n\n// ── the public types ────────────────────────────────────────────────────────\n\n/** The kinds of field that the check reads. `json-ld:<path>` reads a value at a path of the JSON-LD, for example `json-ld:Product.offers.price`. */\nexport type FieldKind =\n \"text\" | \"number\" | \"money\" | \"date\" | \"url\" | \"email\" | \"phone\" | `json-ld:${string}`;\n\n/** These kinds read the text of the page. The kind `json-ld:<path>` reads the JSON-LD. */\nexport const FIELD_KINDS: readonly string[] = [\n \"text\",\n \"number\",\n \"money\",\n \"date\",\n \"url\",\n \"email\",\n \"phone\",\n];\n\n/** This is the value that a caller proposes for one field. */\nexport interface FieldClaim {\n /** This value is the proposal. The check removes the spaces at its start and at its end. */\n readonly value: string | number;\n /** This value is the kind of the field. The default is `number` for a number and `text` for a text. */\n readonly kind?: FieldKind | undefined;\n}\n\n/** The status of one field. The module comment of `extract.ts` and the README tell what each status means. */\nexport type FieldStatus =\n \"verified\" | \"normalized\" | \"ambiguous\" | \"not-found\" | \"held-back\" | \"invalid\";\n\n/** These positions locate a value in the text of a version. The end is not part of the value. */\nexport interface FieldSpan {\n readonly start: number;\n readonly end: number;\n /** This value is `full_text` when the value is only in the whole text of a page that the reader cut. */\n readonly in: \"text\" | \"full_text\";\n}\n\n/** This is one place that an ambiguous field matches. */\nexport interface FieldCandidate {\n readonly span: FieldSpan | null;\n /** This path locates a value of the JSON-LD (`jsonld[0].offers[1].price`) or a stored link (`links[3]`). */\n readonly path: string | null;\n /** This value is the exact stored text, or the stored value of the JSON-LD. */\n readonly raw: string | number | boolean;\n readonly excerpt: string;\n /** These are the meanings that the stored text can have, for example two dates for `03/04/2026`. */\n readonly readings: readonly string[];\n}\n\n/** This is the check of one field, before the runtime adds the capture. */\nexport interface FieldCheck {\n readonly status: FieldStatus;\n /** This value is the kind that the check used. */\n readonly kind: string;\n readonly span: FieldSpan | null;\n readonly path: string | null;\n /** This value is the exact stored text of the match, or the stored value of the JSON-LD. */\n readonly raw: string | number | boolean | null;\n /** This text is the stored text around the match. */\n readonly excerpt: string | null;\n /** This value names the normalization of a `normalized` match, for example `thousands-separator`. Two or more names are joined with `+`. */\n readonly normalization: string | null;\n /** This value is the meaning of the stored text that matched, for example `1234.5` or `2026-03-04`. */\n readonly reading: string | null;\n /** This value is the number of places that match. */\n readonly count: number;\n /** These are the places of an ambiguous field, one for each meaning, 5 or fewer. */\n readonly candidates: readonly FieldCandidate[];\n readonly note: string | null;\n}\n\n/** This is the result of one field: the check, and the capture of the text that the check read. */\nexport interface FieldResult extends FieldCheck {\n readonly capture: string | null;\n /** This time (ISO 8601) is the fetch time of that capture. */\n readonly fetched_at: string | null;\n}\n\n/** These are the counts of the statuses of the fields. */\nexport interface ExtractSummary {\n readonly fields: number;\n readonly verified: number;\n readonly normalized: number;\n readonly ambiguous: number;\n readonly not_found: number;\n readonly held_back: number;\n readonly invalid: number;\n}\n\n/** This is the text of a document that `checkFields` reads, with its metadata. */\nexport interface ExtractSource {\n readonly text: string;\n /** This text is the whole text of a page that the reader cut. The check reads it when the value is not in `text`. */\n readonly full_text?: string | undefined;\n /** This URL is the address of the page. The check resolves a relative URL against it. */\n readonly url?: string | null | undefined;\n /** The check reads `jsonld`, `jsonld_dropped`, `links` and `truncated` of this metadata. */\n readonly meta?: Readonly<Record<string, unknown>> | undefined;\n}\n\n/** This is the result of `checkFields`. */\nexport interface FieldsChecked {\n readonly fields: Readonly<Record<string, FieldCheck>>;\n readonly summary: ExtractSummary;\n /** This value counts the units of work of the check. It grows in proportion to the length of the text. */\n readonly steps: number;\n}\n\n/** These options control a check of fields against a stored document. */\nexport interface ExtractOptions {\n /** This id checks the version of one capture, not the current version. */\n readonly capture?: string | undefined;\n /**\n * This time checks the version that was current at that time: an ISO 8601 text or a `Date`. A date\n * with no time is the start of that day in UTC. Give `capture` or `at`, not the two.\n */\n readonly at?: string | Date | undefined;\n /** This value is true when a person gave this URL directly. It is the only way to check a held-back document. The crawler logs it. */\n readonly userProvided?: boolean | undefined;\n}\n\n/** This is the result of a check of fields against a stored document. */\nexport interface ExtractResult {\n readonly doc: string;\n readonly url: string | null;\n readonly captures: readonly string[];\n /** This id is the capture of the text that the check read. A held-back document gives null. */\n readonly capture: string | null;\n /** This hash is the blob of that capture. */\n readonly blob: string | null;\n /** This time (ISO 8601) is the fetch time of that capture. */\n readonly fetched_at: string | null;\n readonly held_back: boolean;\n /** These are the results of the fields, with the names that the caller gave. */\n readonly fields: Readonly<Record<string, FieldResult>>;\n readonly summary: ExtractSummary;\n readonly note: string | null;\n /** This time (ISO 8601) is the `at` of the check. The result has it only when the check gave `at`. */\n readonly at?: string | undefined;\n /** This value is true when `at` is before the first capture of the document. Then no field is found. */\n readonly before_first?: boolean | undefined;\n /**\n * This value tells that the checked text is an archived copy, with the archive and the date of the\n * copy. The note says so too. A citation of a value names the archive and that date.\n */\n readonly archived?: Archived | undefined;\n}\n\n// ── the work counter and the stored text ────────────────────────────────────\n\ninterface Work {\n steps: number;\n}\n\n/** A text of the document, folded one time for each check, with the tokens of each kind. */\ninterface Doc {\n readonly name: \"text\" | \"full_text\";\n readonly text: string;\n readonly folded: Folded;\n readonly cache: Map<string, unknown>;\n}\n\nconst prepare = (name: \"text\" | \"full_text\", text: string, work: Work): Doc => {\n // foldText is linear: each unit of the text gives a constant number of units of work\n work.steps += text.length;\n return { name, text, folded: foldText(text, \"quote\"), cache: new Map() };\n};\n\nconst cached = <T>(doc: Doc, key: string, make: () => T): T => {\n if (doc.cache.has(key)) return doc.cache.get(key) as T;\n const value = make();\n doc.cache.set(key, value);\n return value;\n};\n\n/** This function gives the original span of the folded units from `i` to `j`. */\nconst spanOf = (doc: Doc, i: number, j: number): FieldSpan => ({\n start: doc.folded.start[i]!,\n end: doc.folded.end[j - 1]!,\n in: doc.name,\n});\n\nconst EXCERPT = 60;\n\n/** This function moves a position off the second half of a surrogate pair. */\nconst onBoundary = (text: string, position: number, forward: boolean): number => {\n const code = text.charCodeAt(position);\n if (position > 0 && position < text.length && code >= 0xdc00 && code <= 0xdfff) {\n return forward ? position + 1 : position - 1;\n }\n return position;\n};\n\nconst excerptOf = (text: string, start: number, end: number): string => {\n const from = onBoundary(text, Math.max(0, start - EXCERPT), false);\n const to = onBoundary(text, Math.min(text.length, end + EXCERPT), true);\n return `${from > 0 ? \"…\" : \"\"}${text.slice(from, to)}${to < text.length ? \"…\" : \"\"}`;\n};\n\nconst isDigitCode = (code: number): boolean => code >= 48 && code <= 57;\nconst isDigitAt = (text: string, at: number): boolean =>\n at >= 0 && at < text.length && isDigitCode(text.charCodeAt(at));\nconst WORD = /^[\\p{L}\\p{N}]$/u;\n/** This function tells if the unit at a position is a letter or a digit. */\nconst isWordAt = (text: string, at: number): boolean => {\n if (at < 0 || at >= text.length) return false;\n const code = text.charCodeAt(at);\n if (code < 0x80) {\n return isDigitCode(code) || (code >= 97 && code <= 122) || (code >= 65 && code <= 90);\n }\n return WORD.test(text[at]!);\n};\nconst isLetterAt = (text: string, at: number): boolean =>\n isWordAt(text, at) && !isDigitAt(text, at);\n\n// ── the decision ────────────────────────────────────────────────────────────\n\ninterface Where {\n readonly span: FieldSpan | null;\n readonly path: string | null;\n}\n\n/** A place of the stored data that can hold a value of a kind, with its meanings. */\ninterface Cand<R> {\n readonly where: Where;\n readonly raw: string | number | boolean;\n /** This text is the exact stored text, for the exact comparison. */\n readonly exact: string;\n readonly excerpt: () => string;\n readonly readings: readonly R[];\n /** This part of the place also matches exactly, for example the amount of a money value. */\n readonly alt?: { readonly where: Where; readonly exact: string } | undefined;\n}\n\n/** The comparison of one meaning of the value with one meaning of the stored text. */\ninterface Compared {\n /** This value is false when the stored text says less than the value, for example a date with no year. */\n readonly clean: boolean;\n readonly key: string;\n readonly reading: string;\n readonly labels: readonly string[];\n}\n\ninterface Comparator<C, R> {\n readonly compare: (claim: C, reading: R) => Compared | undefined;\n readonly display: (reading: R) => string;\n /** This function tells if two matches have one meaning. The default compares the keys. */\n readonly same?: (a: Compared, b: Compared) => boolean;\n}\n\nconst MAX_CANDIDATES = 5;\n\nconst LABEL_ORDER = [\n \"unicode\",\n \"whitespace\",\n \"case\",\n \"thousands-separator\",\n \"decimal-comma\",\n \"number-form\",\n \"currency-symbol\",\n \"currency-position\",\n \"amount-only\",\n \"date-iso\",\n \"date-ymd\",\n \"date-dmy\",\n \"date-mdy\",\n \"date-numeric\",\n \"date-written\",\n \"date-form\",\n \"date-partial\",\n \"url-resolved\",\n \"url-scheme\",\n \"url-form\",\n \"email-case\",\n \"phone-digits\",\n \"phone-suffix\",\n];\n\nconst labelText = (labels: Iterable<string>): string => {\n const set = new Set(labels);\n return LABEL_ORDER.filter((label) => set.has(label)).join(\"+\");\n};\n\nconst empty = (status: FieldStatus, kind: string, note: string | null = null): FieldCheck => ({\n status,\n kind,\n span: null,\n path: null,\n raw: null,\n excerpt: null,\n normalization: null,\n reading: null,\n count: 0,\n candidates: [],\n note,\n});\n\ninterface Matched<R> {\n readonly cand: Cand<R>;\n readonly compared: Compared;\n readonly clean: boolean;\n}\n\n/**\n * This function decides the status of a field from its places. An exact match wins. A normalized\n * match needs one meaning: two meanings, or a place that the text does not make clear, give\n * `ambiguous`. The cost is linear in the number of places.\n */\nconst decide = <C, R>(\n kind: string,\n claimText: string,\n claims: readonly C[],\n cands: readonly Cand<R>[],\n comparator: Comparator<C, R>,\n work: Work,\n): FieldCheck => {\n let exactCount = 0;\n let exactFirst:\n { readonly where: Where; readonly cand: Cand<R>; readonly raw: string } | undefined;\n for (const cand of cands) {\n work.steps += 1;\n if (cand.exact === claimText) {\n exactCount += 1;\n exactFirst ??= { where: cand.where, cand, raw: cand.exact };\n } else if (cand.alt !== undefined && cand.alt.exact === claimText) {\n exactCount += 1;\n exactFirst ??= { where: cand.alt.where, cand, raw: cand.alt.exact };\n }\n }\n if (exactFirst !== undefined) {\n const reading = exactFirst.cand.readings[0];\n return {\n ...empty(\"verified\", kind),\n span: exactFirst.where.span,\n path: exactFirst.where.path,\n raw: exactFirst.where === exactFirst.cand.where ? exactFirst.cand.raw : exactFirst.raw,\n excerpt: exactFirst.cand.excerpt(),\n reading:\n exactFirst.cand.readings.length === 1 && reading !== undefined\n ? comparator.display(reading)\n : null,\n count: exactCount,\n };\n }\n const matches: Matched<R>[] = [];\n for (const cand of cands) {\n let best: Compared | undefined;\n for (const reading of cand.readings) {\n for (const claim of claims) {\n work.steps += 1;\n const compared = comparator.compare(claim, reading);\n if (compared !== undefined && (best === undefined || (compared.clean && !best.clean)))\n best = compared;\n }\n }\n if (best === undefined) continue;\n matches.push({\n cand,\n compared: best,\n clean: best.clean && cand.readings.length === 1 && claims.length === 1,\n });\n }\n if (matches.length === 0) return empty(\"not-found\", kind);\n const same = comparator.same ?? ((a: Compared, b: Compared): boolean => a.key === b.key);\n const candidateOf = (match: Matched<R>): FieldCandidate => ({\n span: match.cand.where.span,\n path: match.cand.where.path,\n raw: match.cand.raw,\n excerpt: match.cand.excerpt(),\n readings: match.cand.readings.map(comparator.display),\n });\n const clean = matches.filter((match) => match.clean);\n if (clean.length > 0) {\n // one representative of each meaning; two are enough to know that the field is ambiguous\n const meanings: Matched<R>[] = [];\n for (const match of clean) {\n work.steps += 1;\n if (meanings.some((known) => same(known.compared, match.compared))) continue;\n if (meanings.length < MAX_CANDIDATES) meanings.push(match);\n else break;\n }\n const first = clean[0]!;\n if (meanings.length === 1) {\n return {\n ...empty(\"normalized\", kind),\n span: first.cand.where.span,\n path: first.cand.where.path,\n raw: first.cand.raw,\n excerpt: first.cand.excerpt(),\n normalization: labelText(first.compared.labels),\n reading: first.compared.reading,\n count: clean.length,\n };\n }\n return {\n ...empty(\n \"ambiguous\",\n kind,\n \"the value matches places with different meanings, so the check does not choose one\",\n ),\n count: clean.length,\n candidates: meanings.map(candidateOf),\n };\n }\n const note =\n claims.length > 1\n ? \"the value itself has more than one meaning: give it in a form with one meaning\"\n : \"the stored text matches, but it does not give one meaning, so the check does not choose one\";\n return {\n ...empty(\"ambiguous\", kind, note),\n count: matches.length,\n candidates: matches.slice(0, MAX_CANDIDATES).map(candidateOf),\n };\n};\n\n// ── text ────────────────────────────────────────────────────────────────────\n\n/** This function gives each start of a needle in a text, in linear time (Knuth, Morris and Pratt). */\nconst occurrences = (hay: string, needle: string, work: Work): number[] => {\n const m = needle.length;\n const fail = new Int32Array(m);\n for (let q = 0, i = 1; i < m; i += 1) {\n while (q > 0 && needle.charCodeAt(i) !== needle.charCodeAt(q)) {\n q = fail[q - 1]!;\n work.steps += 1;\n }\n if (needle.charCodeAt(i) === needle.charCodeAt(q)) q += 1;\n fail[i] = q;\n work.steps += 1;\n }\n const hits: number[] = [];\n for (let q = 0, i = 0; i < hay.length; i += 1) {\n while (q > 0 && hay.charCodeAt(i) !== needle.charCodeAt(q)) {\n q = fail[q - 1]!;\n work.steps += 1;\n }\n if (hay.charCodeAt(i) === needle.charCodeAt(q)) q += 1;\n work.steps += 1;\n if (q === m) {\n hits.push(i - m + 1);\n q = fail[q - 1]!;\n }\n }\n return hits;\n};\n\nconst IGNORABLE = /\\p{Default_Ignorable_Code_Point}/gu;\nconst QUOTES = /[‘’‚‛′]/g;\nconst DOUBLE_QUOTES = /[“”„‟″]/g;\nconst DASHES = /[‐‑‒–—―−﹘﹣]/g;\n\n/** This function applies the Unicode part of the fold: NFKC, no ignorable characters, and ASCII quotes and dashes. */\nconst unicodeForm = (text: string): string =>\n text\n .normalize(\"NFKC\")\n .replace(IGNORABLE, \"\")\n .replace(QUOTES, \"'\")\n .replace(DOUBLE_QUOTES, '\"')\n .replace(DASHES, \"-\");\nconst spaceForm = (text: string): string => text.replace(/\\s+/gu, \" \").trim();\n\nconst LAYER_SETS: ReadonlyArray<readonly (\"unicode\" | \"whitespace\" | \"case\")[]> = [\n [\"unicode\"],\n [\"whitespace\"],\n [\"case\"],\n [\"unicode\", \"whitespace\"],\n [\"unicode\", \"case\"],\n [\"whitespace\", \"case\"],\n];\n\n/** This function names the parts of the fold that make the stored text equal to the value. */\nconst textLabels = (stored: string, value: string): string[] => {\n const apply = (text: string, layers: readonly string[]): string => {\n let out = text;\n if (layers.includes(\"unicode\")) out = unicodeForm(out);\n if (layers.includes(\"whitespace\")) out = spaceForm(out);\n if (layers.includes(\"case\")) out = out.toLowerCase();\n return out;\n };\n for (const layers of LAYER_SETS) {\n if (apply(stored, layers) === apply(value, layers)) return [...layers];\n }\n return [\"unicode\", \"whitespace\", \"case\"];\n};\n\n/**\n * This function finds a text value in a text, with the fold of `verify`. A match must start and\n * end at the edge of a word. The positions are in the original text.\n */\nconst checkText = (doc: Doc, claim: string, work: Work): FieldCheck => {\n const needle = fold(claim, \"quote\");\n if (needle.length === 0) return empty(\"invalid\", \"text\", \"the value has no text\");\n const hay = doc.folded.text;\n const hits = occurrences(hay, needle, work);\n const wordStart = isWordAt(needle, 0);\n const wordEnd = isWordAt(needle, needle.length - 1);\n let count = 0;\n let next = 0;\n let firstExact: FieldSpan | undefined;\n let first: FieldSpan | undefined;\n for (const at of hits) {\n work.steps += 1;\n if (at < next) continue;\n const end = at + needle.length;\n if (wordStart && isWordAt(hay, at - 1)) continue;\n if (wordEnd && isWordAt(hay, end)) continue;\n next = end;\n count += 1;\n const span = spanOf(doc, at, end);\n first ??= span;\n if (firstExact === undefined && doc.text.slice(span.start, span.end) === claim)\n firstExact = span;\n }\n if (first === undefined) return empty(\"not-found\", \"text\");\n const span = firstExact ?? first;\n const stored = doc.text.slice(span.start, span.end);\n return {\n ...empty(firstExact !== undefined ? \"verified\" : \"normalized\", \"text\"),\n span,\n raw: stored,\n excerpt: excerptOf(doc.text, span.start, span.end),\n normalization: firstExact !== undefined ? null : labelText(textLabels(stored, claim)),\n count,\n };\n};\n\n// ── numbers ─────────────────────────────────────────────────────────────────\n\n/** One meaning of a number: its canonical value, and the form that the text gives it. */\ninterface NumReading {\n /** This text is the canonical decimal value, for example `1234.5`. */\n readonly value: string;\n /** This text is the value with its digits as written, for example `+01234.50`. */\n readonly plain: string;\n /** This character separates the thousands, or it is null. */\n readonly group: string | null;\n /** This character is the decimal point or the decimal comma, or it is null. */\n readonly point: string | null;\n}\n\nconst THOUSANDS: ReadonlySet<string> = new Set([\",\", \".\", \"'\", \" \"]);\nconst DECIMALS: ReadonlySet<string> = new Set([\".\", \",\"]);\n/** The no-break and thin spaces that separate the thousands in some languages. */\nconst GROUP_SPACES: ReadonlySet<number> = new Set([0xa0, 0x202f, 0x2009, 0x2007]);\n\nconst makeReading = (\n sign: string,\n int: string,\n frac: string,\n group: string | null,\n point: string | null,\n): NumReading => {\n const intPart = int.replace(/^0+(?=\\d)/, \"\");\n const fracPart = frac.replace(/0+$/, \"\");\n const zero = /^0+$/.test(intPart) && fracPart === \"\";\n return {\n value: `${sign === \"-\" && !zero ? \"-\" : \"\"}${intPart}${fracPart === \"\" ? \"\" : `.${fracPart}`}`,\n plain: `${sign}${int}${frac === \"\" ? \"\" : `.${frac}`}`,\n group,\n point,\n };\n};\n\n/**\n * This function gives each meaning of a number as written. A group of exactly three digits after\n * one separator gives two meanings (`1,234` is 1234 or 1.234), and the function gives the two.\n */\nconst numberReadings = (written: string): NumReading[] => {\n let sign = \"\";\n let rest = written;\n if (rest.startsWith(\"-\") || rest.startsWith(\"+\")) {\n sign = rest[0]!;\n rest = rest.slice(1);\n }\n const groups: string[] = [];\n const seps: string[] = [];\n let from = 0;\n for (let at = 0; at < rest.length; at += 1) {\n if (isDigitCode(rest.charCodeAt(at))) continue;\n groups.push(rest.slice(from, at));\n seps.push(rest[at]!);\n from = at + 1;\n }\n groups.push(rest.slice(from));\n if (groups.some((group) => group.length === 0)) return [];\n const found = new Map<string, NumReading>();\n const add = (int: string, frac: string, group: string | null, point: string | null): void => {\n const reading = makeReading(sign, int, frac, group, point);\n if (!found.has(reading.value)) found.set(reading.value, reading);\n };\n if (seps.length === 0) {\n add(groups[0]!, \"\", null, null);\n return [...found.values()];\n }\n const t = seps[0]!;\n const grouped = (count: number): boolean => {\n const head = groups[0]!;\n if (head.length > 3 || head.startsWith(\"0\")) return false;\n for (let k = 1; k <= count; k += 1) {\n if (groups[k]!.length !== 3 || seps[k - 1] !== t) return false;\n }\n return true;\n };\n if (THOUSANDS.has(t) && grouped(seps.length)) add(groups.join(\"\"), \"\", t, null);\n const d = seps[seps.length - 1]!;\n if (\n seps.length >= 2 &&\n DECIMALS.has(d) &&\n d !== t &&\n THOUSANDS.has(t) &&\n grouped(seps.length - 1)\n ) {\n add(groups.slice(0, -1).join(\"\"), groups[groups.length - 1]!, t, d);\n }\n if (seps.length === 1 && DECIMALS.has(d)) add(groups[0]!, groups[1]!, null, d);\n return [...found.values()];\n};\n\n/** This function writes a JavaScript `number` in decimal digits, with no exponent. */\nconst decimalOf = (value: number): string | undefined => {\n if (!Number.isFinite(value)) return undefined;\n const text = String(value);\n const match = /^(-?)(\\d+)(?:\\.(\\d+))?e([+-]\\d+)$/i.exec(text);\n if (match === null) return text;\n const digits = `${match[2]!}${match[3] ?? \"\"}`;\n const point = match[2]!.length + Number(match[4]);\n const body =\n point <= 0\n ? `0.${\"0\".repeat(-point)}${digits}`\n : point >= digits.length\n ? `${digits}${\"0\".repeat(point - digits.length)}`\n : `${digits.slice(0, point)}.${digits.slice(point)}`;\n return `${match[1]!}${body}`;\n};\n\n/** This function reads a proposed number. A JavaScript `number` has one meaning: its point is a decimal point. */\nconst claimNumber = (value: string | number): NumReading[] => {\n if (typeof value === \"number\") {\n const text = decimalOf(value);\n if (text === undefined) return [];\n const sign = text.startsWith(\"-\") ? \"-\" : \"\";\n const [int = \"\", frac = \"\"] = text.replace(/^-/, \"\").split(\".\");\n return [makeReading(sign, int, frac, null, frac === \"\" ? null : \".\")];\n }\n const text = fold(value, \"quote\").trim();\n if (!/^[+-]?\\d[\\d.,' ]*$/.test(text) || !/\\d$/.test(text)) return [];\n return numberReadings(text);\n};\n\ninterface NumToken {\n readonly i: number;\n readonly j: number;\n readonly readings: readonly NumReading[];\n}\n\n/**\n * This function finds the numbers of a folded text. A number is digits with separators. A letter\n * before it makes a name (`A320`), so it is not a number. Each unit is read a constant number of times.\n */\nconst numberTokens = (doc: Doc, work: Work): readonly NumToken[] =>\n cached(doc, \"number\", () => {\n const f = doc.folded.text;\n const n = f.length;\n const out: NumToken[] = [];\n const isSep = (at: number): boolean => {\n const char = f[at];\n if (char === \",\" || char === \".\" || char === \"'\") return true;\n if (char !== \" \") return false;\n const from = doc.folded.start[at]!;\n return (\n doc.folded.end[at]! - from === 1 &&\n GROUP_SPACES.has(doc.text.charCodeAt(from)) &&\n (at + 1 >= n || doc.folded.start[at + 1] === from + 1)\n );\n };\n let i = 0;\n while (i < n) {\n work.steps += 1;\n if (!isDigitAt(f, i)) {\n i += 1;\n continue;\n }\n const start = i;\n let j = i;\n while (j < n && isDigitAt(f, j)) {\n j += 1;\n work.steps += 1;\n }\n while (j + 1 < n && isSep(j) && isDigitAt(f, j + 1)) {\n j += 1;\n while (j < n && isDigitAt(f, j)) {\n j += 1;\n work.steps += 1;\n }\n }\n i = j;\n const before = start > 0 ? f[start - 1]! : \"\";\n // a name (A320), a fraction with no integer part (.5) or a piece of a longer number\n if (isLetterAt(f, start - 1) || before === \".\" || (before === \",\" && isDigitAt(f, start - 2)))\n continue;\n const signed = (before === \"-\" || before === \"+\") && !isWordAt(f, start - 2);\n const from = signed ? start - 1 : start;\n const readings = numberReadings(f.slice(from, j));\n if (readings.length > 0) {\n out.push({ i: from, j, readings });\n continue;\n }\n // the separators make no number (1.2.3, 12,34,5): each run of digits is a number\n let a = start;\n while (a < j) {\n let b = a;\n while (b < j && isDigitAt(f, b)) b += 1;\n out.push({ i: a, j: b, readings: [makeReading(\"\", f.slice(a, b), \"\", null, null)] });\n a = b + 1;\n }\n }\n return out;\n });\n\nconst numberLabels = (claim: NumReading, reading: NumReading): string[] => {\n const labels = new Set<string>();\n if (claim.group !== reading.group) labels.add(\"thousands-separator\");\n if (claim.point !== reading.point && (claim.point === \",\" || reading.point === \",\"))\n labels.add(\"decimal-comma\");\n // the same value with other digits: zeros at the start or the end, or a plus sign\n if (claim.plain !== reading.plain) labels.add(\"number-form\");\n return [...labels];\n};\n\nconst NUMBER: Comparator<NumReading, NumReading> = {\n compare: (claim, reading) => {\n if (claim.value !== reading.value) return undefined;\n const labels = numberLabels(claim, reading);\n // the same digits as written: only the characters differ (fullwidth digits, a minus sign)\n return {\n clean: true,\n key: reading.value,\n reading: reading.value,\n labels: labels.length === 0 ? [\"unicode\"] : labels,\n };\n },\n display: (reading) => reading.value,\n};\n\nconst numberCands = (doc: Doc, work: Work): Cand<NumReading>[] =>\n numberTokens(doc, work).map((token) => {\n const span = spanOf(doc, token.i, token.j);\n const exact = doc.text.slice(span.start, span.end);\n return {\n where: { span, path: null },\n raw: exact,\n exact,\n excerpt: () => excerptOf(doc.text, span.start, span.end),\n readings: token.readings,\n };\n });\n\n// ── money ───────────────────────────────────────────────────────────────────\n\n/** The codes of ISO 4217 that the check knows, for the common currencies. */\nconst CODES: readonly string[] = [\n \"USD\",\n \"EUR\",\n \"GBP\",\n \"JPY\",\n \"CNY\",\n \"CAD\",\n \"AUD\",\n \"NZD\",\n \"CHF\",\n \"SEK\",\n \"NOK\",\n \"DKK\",\n \"ISK\",\n \"PLN\",\n \"CZK\",\n \"HUF\",\n \"RON\",\n \"BGN\",\n \"TRY\",\n \"ILS\",\n \"INR\",\n \"PKR\",\n \"BDT\",\n \"LKR\",\n \"KRW\",\n \"SGD\",\n \"HKD\",\n \"TWD\",\n \"THB\",\n \"MYR\",\n \"IDR\",\n \"PHP\",\n \"VND\",\n \"BRL\",\n \"MXN\",\n \"ARS\",\n \"CLP\",\n \"COP\",\n \"PEN\",\n \"UYU\",\n \"ZAR\",\n \"NGN\",\n \"EGP\",\n \"KES\",\n \"MAD\",\n \"AED\",\n \"SAR\",\n \"QAR\",\n \"KWD\",\n \"BHD\",\n \"OMR\",\n \"JOD\",\n \"RUB\",\n \"UAH\",\n \"KZT\",\n \"GEL\",\n];\n\n/** The currency symbols, folded, with the currencies of each. A symbol of more than one currency does not say which. */\nconst SYMBOLS: ReadonlyArray<readonly [string, readonly string[]]> = [\n [\"us$\", [\"USD\"]],\n [\"ca$\", [\"CAD\"]],\n [\"c$\", [\"CAD\"]],\n [\"au$\", [\"AUD\"]],\n [\"a$\", [\"AUD\"]],\n [\"nz$\", [\"NZD\"]],\n [\"hk$\", [\"HKD\"]],\n [\"s$\", [\"SGD\"]],\n [\"r$\", [\"BRL\"]],\n [\"mx$\", [\"MXN\"]],\n [\"nt$\", [\"TWD\"]],\n [\"$\", [\"USD\", \"CAD\", \"AUD\", \"NZD\", \"MXN\", \"SGD\", \"HKD\", \"TWD\", \"ARS\", \"CLP\", \"COP\"]],\n [\"€\", [\"EUR\"]],\n [\"£\", [\"GBP\"]],\n [\"cn¥\", [\"CNY\"]],\n [\"jp¥\", [\"JPY\"]],\n [\"¥\", [\"JPY\", \"CNY\"]],\n [\"₹\", [\"INR\"]],\n [\"₩\", [\"KRW\"]],\n [\"₽\", [\"RUB\"]],\n [\"₺\", [\"TRY\"]],\n [\"₪\", [\"ILS\"]],\n [\"₫\", [\"VND\"]],\n [\"฿\", [\"THB\"]],\n [\"₱\", [\"PHP\"]],\n [\"₦\", [\"NGN\"]],\n [\"₴\", [\"UAH\"]],\n [\"₸\", [\"KZT\"]],\n [\"₾\", [\"GEL\"]],\n [\"zł\", [\"PLN\"]],\n [\"kč\", [\"CZK\"]],\n [\"kr\", [\"SEK\", \"NOK\", \"DKK\", \"ISK\"]],\n];\n\ninterface Marker {\n /** This text is the marker as the folded text writes it. */\n readonly text: string;\n readonly codes: readonly string[];\n /** This label names the currency: its code, or the symbol when the symbol has more than one currency. */\n readonly label: string;\n readonly alpha: boolean;\n /** This value is true for a code of ISO 4217. A page writes a code in capitals, so `15 try` is not an amount. */\n readonly code: boolean;\n}\n\nconst MARKERS: readonly Marker[] = [\n ...SYMBOLS.map(([text, codes]) => ({\n text,\n codes,\n label: codes.length === 1 ? codes[0]! : text,\n alpha: /^[a-z]/.test(text),\n code: false,\n })),\n ...CODES.map((code) => ({\n text: code.toLowerCase(),\n codes: [code],\n label: code,\n alpha: true,\n code: true,\n })),\n].sort((a, b) => b.text.length - a.text.length);\n\n/** This function finds a currency marker that ends at a position (exclusive), or undefined. */\nconst markerEndingAt = (f: string, end: number): Marker | undefined => {\n for (const marker of MARKERS) {\n const from = end - marker.text.length;\n if (from < 0 || !f.startsWith(marker.text, from)) continue;\n if (marker.alpha && isLetterAt(f, from - 1)) continue;\n return marker;\n }\n return undefined;\n};\n\n/** This function finds a currency marker that starts at a position, or undefined. */\nconst markerStartingAt = (f: string, from: number): Marker | undefined => {\n for (const marker of MARKERS) {\n if (!f.startsWith(marker.text, from)) continue;\n if (marker.alpha && isLetterAt(f, from + marker.text.length)) continue;\n return marker;\n }\n return undefined;\n};\n\ninterface MoneyReading {\n readonly amount: NumReading;\n readonly marker: Marker | null;\n readonly before: boolean;\n}\n\nconst moneyText = (reading: MoneyReading): string =>\n reading.marker === null\n ? reading.amount.value\n : `${reading.marker.label} ${reading.amount.value}`;\n\nconst MONEY: Comparator<MoneyReading, MoneyReading> = {\n compare: (claim, reading) => {\n if (claim.amount.value !== reading.amount.value || reading.marker === null) return undefined;\n const labels = new Set(numberLabels(claim.amount, reading.amount));\n let clean = true;\n if (claim.marker === null) labels.add(\"amount-only\");\n else {\n if (claim.marker.label !== reading.marker.label) {\n if (!claim.marker.codes.some((code) => reading.marker!.codes.includes(code)))\n return undefined;\n // \"$\" can be one of several currencies, so it does not say which one the value names\n clean = false;\n }\n if (claim.marker.text !== reading.marker.text) labels.add(\"currency-symbol\");\n if (claim.before !== reading.before) labels.add(\"currency-position\");\n }\n if (labels.size === 0) labels.add(\"whitespace\");\n return { clean, key: moneyText(reading), reading: moneyText(reading), labels: [...labels] };\n },\n display: moneyText,\n};\n\n/** This function reads a proposed amount of money: an amount, with a currency symbol or code before or after it. */\nconst claimMoney = (value: string | number): MoneyReading[] => {\n if (typeof value === \"number\")\n return claimNumber(value).map((amount) => ({ amount, marker: null, before: false }));\n const text = fold(value, \"quote\").trim();\n const head = markerStartingAt(text, 0);\n if (head !== undefined) {\n return claimNumber(text.slice(head.text.length).trim()).map((amount) => ({\n amount,\n marker: head,\n before: true,\n }));\n }\n const tail = markerEndingAt(text, text.length);\n if (tail !== undefined) {\n return claimNumber(text.slice(0, text.length - tail.text.length).trim()).map((amount) => ({\n amount,\n marker: tail,\n before: false,\n }));\n }\n return claimNumber(text).map((amount) => ({ amount, marker: null, before: false }));\n};\n\n/** This function finds the amounts of money of a text. An amount is a number with a currency marker before or after it, with one space or none. */\nconst moneyCands = (doc: Doc, work: Work): Cand<MoneyReading>[] =>\n cached(doc, \"money\", () => {\n const f = doc.folded.text;\n const out: Cand<MoneyReading>[] = [];\n // a code of ISO 4217 counts only in capitals in the original text\n const written = (marker: Marker | undefined, from: number): Marker | undefined => {\n if (marker === undefined || !marker.code) return marker;\n const original = doc.text.slice(\n doc.folded.start[from]!,\n doc.folded.end[from + marker.text.length - 1]!,\n );\n return original === marker.label ? marker : undefined;\n };\n for (const token of numberTokens(doc, work)) {\n work.steps += MARKERS.length;\n const gapBefore = f[token.i - 1] === \" \" ? 1 : 0;\n const found = markerEndingAt(f, token.i - gapBefore);\n const before = written(\n found,\n token.i - gapBefore - (found === undefined ? 0 : found.text.length),\n );\n let marker = before;\n let i = token.i;\n let j = token.j;\n if (before !== undefined) i = token.i - gapBefore - before.text.length;\n else {\n const gapAfter = f[token.j] === \" \" ? 1 : 0;\n const after = written(markerStartingAt(f, token.j + gapAfter), token.j + gapAfter);\n if (after === undefined) continue;\n marker = after;\n j = token.j + gapAfter + after.text.length;\n }\n const span = spanOf(doc, i, j);\n const amount = spanOf(doc, token.i, token.j);\n const exact = doc.text.slice(span.start, span.end);\n out.push({\n where: { span, path: null },\n raw: exact,\n exact,\n excerpt: () => excerptOf(doc.text, span.start, span.end),\n readings: token.readings.map((reading) => ({\n amount: reading,\n marker: marker ?? null,\n before: before !== undefined,\n })),\n alt: {\n where: { span: amount, path: null },\n exact: doc.text.slice(amount.start, amount.end),\n },\n });\n }\n return out;\n });\n\n// ── dates ───────────────────────────────────────────────────────────────────\n\ntype DateForm = \"iso\" | \"ymd\" | \"dmy\" | \"mdy\" | \"numeric\" | \"written\";\n\ninterface DateReading {\n readonly y: number | null;\n readonly m: number;\n readonly d: number | null;\n readonly form: DateForm;\n}\n\nconst MONTHS: ReadonlyMap<string, number> = new Map([\n [\"january\", 1],\n [\"jan\", 1],\n [\"february\", 2],\n [\"feb\", 2],\n [\"march\", 3],\n [\"mar\", 3],\n [\"april\", 4],\n [\"apr\", 4],\n [\"may\", 5],\n [\"june\", 6],\n [\"jun\", 6],\n [\"july\", 7],\n [\"jul\", 7],\n [\"august\", 8],\n [\"aug\", 8],\n [\"september\", 9],\n [\"sept\", 9],\n [\"sep\", 9],\n [\"october\", 10],\n [\"oct\", 10],\n [\"november\", 11],\n [\"nov\", 11],\n [\"december\", 12],\n [\"dec\", 12],\n]);\nconst WEEKDAYS: ReadonlySet<string> = new Set([\n \"monday\",\n \"mon\",\n \"tuesday\",\n \"tue\",\n \"tues\",\n \"wednesday\",\n \"wed\",\n \"thursday\",\n \"thu\",\n \"thur\",\n \"thurs\",\n \"friday\",\n \"fri\",\n \"saturday\",\n \"sat\",\n \"sunday\",\n \"sun\",\n]);\nconst ORDINALS: ReadonlySet<string> = new Set([\"st\", \"nd\", \"rd\", \"th\"]);\n\nconst daysIn = (m: number, y: number | null): number =>\n m === 2\n ? y === null || (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0))\n ? 29\n : 28\n : [4, 6, 9, 11].includes(m)\n ? 30\n : 31;\n\nconst validDate = (y: number | null, m: number, d: number | null): boolean =>\n m >= 1 &&\n m <= 12 &&\n (y === null || (y >= 1000 && y <= 2999)) &&\n (d === null || (d >= 1 && d <= daysIn(m, y)));\n\nconst pad = (value: number, size: number): string => String(value).padStart(size, \"0\");\nconst dateText = (reading: DateReading): string =>\n reading.y === null\n ? `--${pad(reading.m, 2)}${reading.d === null ? \"\" : `-${pad(reading.d, 2)}`}`\n : `${pad(reading.y, 4)}-${pad(reading.m, 2)}${reading.d === null ? \"\" : `-${pad(reading.d, 2)}`}`;\n\nconst NUM = 0;\nconst ALPHA = 1;\nconst PUNCT = 2;\n\n/** A token of a folded text: a run of digits, a run of letters or one other character. */\ninterface Lexeme {\n readonly type: 0 | 1 | 2;\n readonly i: number;\n readonly j: number;\n readonly text: string;\n /** This value is true when a space comes before the token. */\n readonly gap: boolean;\n}\n\nconst lex = (f: string, work: Work): Lexeme[] => {\n const out: Lexeme[] = [];\n let gap = false;\n let i = 0;\n while (i < f.length) {\n work.steps += 1;\n const char = f[i]!;\n if (char === \" \") {\n gap = true;\n i += 1;\n continue;\n }\n let j = i + 1;\n let type: 0 | 1 | 2 = PUNCT;\n if (isDigitAt(f, i)) {\n type = NUM;\n while (isDigitAt(f, j)) j += 1;\n } else if (isLetterAt(f, i)) {\n type = ALPHA;\n while (isLetterAt(f, j)) j += 1;\n }\n work.steps += j - i;\n out.push({ type, i, j, text: f.slice(i, j), gap });\n gap = false;\n i = j;\n }\n return out;\n};\n\nconst isNum = (token: Lexeme | undefined, min: number, max: number): token is Lexeme =>\n token !== undefined && token.type === NUM && token.text.length >= min && token.text.length <= max;\nconst isPunct = (token: Lexeme | undefined, chars: string): token is Lexeme =>\n token !== undefined && token.type === PUNCT && !token.gap && chars.includes(token.text);\nconst glued = (token: Lexeme | undefined): boolean => token !== undefined && !token.gap;\n\ninterface DateMatch {\n /** This value is the index of the token after the date. */\n readonly end: number;\n readonly readings: readonly DateReading[];\n}\n\n/** This function reads a day after a month or before it: one or two digits, and an ordinal suffix. */\nconst dayAt = (\n tokens: readonly Lexeme[],\n k: number,\n): { readonly d: number; readonly next: number } | undefined => {\n const token = tokens[k];\n if (!isNum(token, 1, 2)) return undefined;\n const after = tokens[k + 1];\n if (after !== undefined && glued(after)) {\n if (after.type === ALPHA) {\n if (!ORDINALS.has(after.text)) return undefined;\n return { d: Number(token.text), next: k + 2 };\n }\n // a time (5:30) or a decimal (4.5) is not a day\n if (\n after.type === PUNCT &&\n (after.text === \":\" || after.text === \".\") &&\n isNum(tokens[k + 2], 1, 4) &&\n glued(tokens[k + 2])\n )\n return undefined;\n if (after.type === NUM) return undefined;\n }\n return { d: Number(token.text), next: k + 1 };\n};\n\nconst yearAt = (\n tokens: readonly Lexeme[],\n k: number,\n): { readonly y: number; readonly next: number } | undefined => {\n let at = k;\n if (isPunct(tokens[at], \",\")) at += 1;\n const token = tokens[at];\n if (!isNum(token, 4, 4)) return undefined;\n if (glued(tokens[at + 1]) && tokens[at + 1]!.type === NUM) return undefined;\n return { y: Number(token.text), next: at + 1 };\n};\n\n/** This function reads a date that starts at a token, or gives undefined. It looks at a constant number of tokens. */\nconst dateAt = (tokens: readonly Lexeme[], t: number): DateMatch | undefined => {\n const a = tokens[t]!;\n const previous = tokens[t - 1];\n const chained =\n previous !== undefined &&\n !a.gap &&\n (previous.type === NUM || (previous.type === PUNCT && \"/.-\".includes(previous.text)));\n if (a.type === NUM && !chained) {\n const p1 = tokens[t + 1];\n const b = tokens[t + 2];\n const p2 = tokens[t + 3];\n const c = tokens[t + 4];\n const numeric =\n isPunct(p1, \"-/.\") &&\n isNum(b, 1, 2) &&\n !b.gap &&\n isPunct(p2, p1.text) &&\n glued(c) &&\n c!.type === NUM;\n if (numeric && a.text.length === 4 && isNum(c, 1, 2)) {\n const y = Number(a.text);\n const m = Number(b.text);\n const d = Number(c.text);\n if (!validDate(y, m, d)) return undefined;\n const iso = p1.text === \"-\" && b.text.length === 2 && c.text.length === 2;\n return { end: t + 5, readings: [{ y, m, d, form: iso ? \"iso\" : \"ymd\" }] };\n }\n if (numeric && a.text.length <= 2 && isNum(c, 4, 4)) {\n const first = Number(a.text);\n const second = Number(b.text);\n const y = Number(c.text);\n if (glued(tokens[t + 5]) && tokens[t + 5]!.type === NUM) return undefined;\n const readings: DateReading[] = [];\n if (first === second) {\n if (validDate(y, first, second)) readings.push({ y, m: first, d: second, form: \"numeric\" });\n } else {\n if (validDate(y, first, second)) readings.push({ y, m: first, d: second, form: \"mdy\" });\n if (validDate(y, second, first)) readings.push({ y, m: second, d: first, form: \"dmy\" });\n }\n return readings.length === 0 ? undefined : { end: t + 5, readings };\n }\n }\n if (a.type === ALPHA) {\n const m = MONTHS.get(a.text);\n if (m === undefined) return undefined;\n let k = t + 1;\n if (isPunct(tokens[k], \".\")) k += 1;\n const day = tokens[k]?.gap === true || k > t + 1 ? dayAt(tokens, k) : undefined;\n if (day !== undefined) {\n const year = yearAt(tokens, day.next);\n const y = year?.y ?? null;\n if (!validDate(y, m, day.d)) return undefined;\n return { end: year?.next ?? day.next, readings: [{ y, m, d: day.d, form: \"written\" }] };\n }\n const year = yearAt(tokens, k);\n if (year === undefined || !validDate(year.y, m, null)) return undefined;\n return { end: year.next, readings: [{ y: year.y, m, d: null, form: \"written\" }] };\n }\n if (a.type === NUM && !chained) {\n const day = dayAt(tokens, t);\n if (day === undefined) return undefined;\n let k = day.next;\n if (tokens[k]?.type === ALPHA && tokens[k]!.text === \"of\") k += 1;\n const month = tokens[k];\n if (month === undefined || month.type !== ALPHA || !month.gap) return undefined;\n const m = MONTHS.get(month.text);\n if (m === undefined) return undefined;\n k += 1;\n if (isPunct(tokens[k], \".\")) k += 1;\n const year = yearAt(tokens, k);\n const y = year?.y ?? null;\n if (!validDate(y, m, day.d)) return undefined;\n return { end: year?.next ?? k, readings: [{ y, m, d: day.d, form: \"written\" }] };\n }\n return undefined;\n};\n\ninterface DateToken {\n readonly i: number;\n readonly j: number;\n readonly readings: readonly DateReading[];\n}\n\nconst scanDates = (f: string, work: Work): DateToken[] => {\n const tokens = lex(f, work);\n const out: DateToken[] = [];\n let t = 0;\n while (t < tokens.length) {\n work.steps += 1;\n const found = dateAt(tokens, t);\n if (found === undefined) {\n t += 1;\n continue;\n }\n out.push({ i: tokens[t]!.i, j: tokens[found.end - 1]!.j, readings: found.readings });\n t = found.end;\n }\n return out;\n};\n\nconst dateCands = (doc: Doc, work: Work): Cand<DateReading>[] =>\n cached(doc, \"date\", () =>\n scanDates(doc.folded.text, work).map((token) => {\n const span = spanOf(doc, token.i, token.j);\n const exact = doc.text.slice(span.start, span.end);\n return {\n where: { span, path: null },\n raw: exact,\n exact,\n excerpt: () => excerptOf(doc.text, span.start, span.end),\n readings: token.readings,\n };\n }),\n );\n\n/** This function reads a proposed date: ISO 8601, written English months, or numbers. It gives no reading for a text that is not one date. */\nconst claimDate = (\n value: string | number,\n): { readonly readings: DateReading[]; readonly time: boolean } => {\n if (typeof value === \"number\") return { readings: [], time: false };\n const text = fold(value, \"quote\").trim();\n const iso =\n /^(\\d{4})-(\\d{2})(?:-(\\d{2}))?(t\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:z|[+-]\\d{2}:?\\d{2})?)?$/.exec(\n text,\n );\n if (iso !== null) {\n const y = Number(iso[1]);\n const m = Number(iso[2]);\n const d = iso[3] === undefined ? null : Number(iso[3]);\n if (iso[4] !== undefined && d === null) return { readings: [], time: false };\n return validDate(y, m, d)\n ? { readings: [{ y, m, d, form: \"iso\" }], time: iso[4] !== undefined }\n : { readings: [], time: false };\n }\n const work: Work = { steps: 0 };\n const tokens = lex(text, work);\n let t = 0;\n if (tokens[0]?.type === ALPHA && WEEKDAYS.has(tokens[0].text)) {\n t = 1;\n if (tokens[1]?.type === PUNCT && tokens[1].text === \",\") t = 2;\n }\n const found = t < tokens.length ? dateAt(tokens, t) : undefined;\n const rest = found === undefined ? [] : tokens.slice(found.end);\n if (found === undefined || rest.some((token) => token.text !== \".\"))\n return { readings: [], time: false };\n return { readings: [...found.readings], time: false };\n};\n\nconst DATE: Comparator<DateReading, DateReading> = {\n compare: (claim, reading) => {\n if (claim.m !== reading.m) return undefined;\n if (claim.y !== null && reading.y !== null && claim.y !== reading.y) return undefined;\n if (claim.d !== null && reading.d !== null && claim.d !== reading.d) return undefined;\n // the stored text says less than the value: the check does not add the year or the day\n const missing =\n (claim.y !== null && reading.y === null) || (claim.d !== null && reading.d === null);\n const partial =\n missing ||\n (claim.y === null && reading.y !== null) ||\n (claim.d === null && reading.d !== null);\n const labels = [claim.form === reading.form ? \"date-form\" : `date-${reading.form}`];\n if (partial) labels.push(\"date-partial\");\n return { clean: !missing, key: dateText(reading), reading: dateText(reading), labels };\n },\n display: dateText,\n};\n\n// ── phone numbers ───────────────────────────────────────────────────────────\n\ninterface PhoneReading {\n /** These digits are the number with no international prefix and no national trunk prefix. */\n readonly digits: string;\n /** This value is true when the number starts with `+` or `00`. */\n readonly intl: boolean;\n}\n\nconst phoneReading = (written: string): PhoneReading | undefined => {\n // \"+44 (0)20\" writes the trunk prefix of the national form in brackets\n const text = written.replace(/\\(0\\)/g, \"\");\n let digits = text.replace(/\\D/g, \"\");\n let intl = text.trimStart().startsWith(\"+\");\n if (!intl && digits.startsWith(\"00\")) {\n intl = true;\n digits = digits.slice(2);\n } else if (!intl && digits.startsWith(\"0\")) digits = digits.slice(1);\n if (digits.length < 7 || digits.length > 15) return undefined;\n return { digits, intl };\n};\n\nconst phoneText = (reading: PhoneReading): string => `${reading.intl ? \"+\" : \"\"}${reading.digits}`;\n\n/** The longest country code has three digits. */\nconst COUNTRY_DIGITS = 3;\n\nconst PHONE: Comparator<PhoneReading, PhoneReading> = {\n compare: (claim, reading) => {\n const key = reading.digits;\n if (claim.digits === reading.digits)\n return { clean: true, key, reading: phoneText(reading), labels: [\"phone-digits\"] };\n if (reading.digits.endsWith(claim.digits))\n return { clean: true, key, reading: phoneText(reading), labels: [\"phone-suffix\"] };\n if (claim.digits.endsWith(reading.digits)) {\n // the stored number has no country code, and the value has one: the rest is the national number\n const country = claim.digits.length - reading.digits.length;\n const clean = claim.intl && !reading.intl && country <= COUNTRY_DIGITS;\n return { clean, key, reading: phoneText(reading), labels: [\"phone-suffix\"] };\n }\n return undefined;\n },\n display: phoneText,\n same: (a, b) => a.key.endsWith(b.key) || b.key.endsWith(a.key),\n};\n\nconst PHONE_CHARS = \"-.()/\";\n\n/** This function finds the phone numbers of a folded text: 7 to 15 digits with spaces, dashes, points, brackets and slashes. */\nconst phoneCands = (doc: Doc, work: Work): Cand<PhoneReading>[] =>\n cached(doc, \"phone\", () => {\n const f = doc.folded.text;\n const n = f.length;\n const out: Cand<PhoneReading>[] = [];\n let i = 0;\n while (i < n) {\n work.steps += 1;\n const char = f[i]!;\n const starts =\n (isDigitAt(f, i) ||\n ((char === \"+\" || char === \"(\") && (isDigitAt(f, i + 1) || f[i + 1] === \"(\"))) &&\n !isWordAt(f, i - 1) &&\n f[i - 1] !== \"+\";\n if (!starts) {\n i += 1;\n continue;\n }\n let j = i;\n while (j < n) {\n work.steps += 1;\n const unit = f[j]!;\n if (isDigitAt(f, j) || PHONE_CHARS.includes(unit) || (unit === \"+\" && j === i)) {\n j += 1;\n continue;\n }\n if (unit === \" \" && (isDigitAt(f, j + 1) || f[j + 1] === \"(\")) {\n j += 1;\n continue;\n }\n break;\n }\n let e = j;\n while (e > i && !isDigitAt(f, e - 1)) e -= 1;\n const next = Math.max(j, i + 1);\n if (e > i && !isWordAt(f, e)) {\n const span = spanOf(doc, i, e);\n const exact = doc.text.slice(span.start, span.end);\n const reading = phoneReading(f.slice(i, e));\n if (reading !== undefined) {\n out.push({\n where: { span, path: null },\n raw: exact,\n exact,\n excerpt: () => excerptOf(doc.text, span.start, span.end),\n readings: [reading],\n });\n }\n }\n i = next;\n }\n return out;\n });\n\nconst claimPhone = (value: string | number): PhoneReading[] => {\n const text = (typeof value === \"number\" ? String(value) : fold(value, \"quote\").trim()).replace(\n /^tel:/,\n \"\",\n );\n if (!/^\\+?[\\d\\s\\-.()/]+$/.test(text)) return [];\n const reading = phoneReading(text);\n return reading === undefined ? [] : [reading];\n};\n\n// ── URLs ────────────────────────────────────────────────────────────────────\n\ninterface UrlReading {\n /** This URL is the canonical form (`canonicalUrl`). */\n readonly url: string;\n /** This value is true when the stored text has no scheme (`www.example.com`). */\n readonly schemeless: boolean;\n /** This value is true when the value was relative and the check resolved it against the page URL. */\n readonly relative: boolean;\n readonly written: string;\n}\n\nconst withoutScheme = (url: string): string => url.replace(/^[a-z][a-z0-9+.-]*:/i, \"\");\n\nconst URL_STOP = /[\\s<>\"`{}|\\\\^]/u;\nconst URL_TRAIL = /[.,;:!?'\"]/;\n\n/** This function finds the URLs of a text: `http://`, `https://` and `www.` forms. It reads the original text, because a path keeps its case. */\nconst urlCands = (doc: Doc, work: Work): Cand<UrlReading>[] =>\n cached(doc, \"url\", () => {\n const text = doc.text;\n const n = text.length;\n const out: Cand<UrlReading>[] = [];\n let i = 0;\n while (i < n) {\n work.steps += 1;\n const code = text.charCodeAt(i) | 0x20;\n let prefix = 0;\n if (code === 0x68 && !isWordAt(text, i - 1)) {\n const head = text.slice(i, i + 8).toLowerCase();\n prefix = head.startsWith(\"https://\") ? 8 : head.startsWith(\"http://\") ? 7 : 0;\n } else if (\n code === 0x77 &&\n !isWordAt(text, i - 1) &&\n text[i - 1] !== \"/\" &&\n text[i - 1] !== \".\"\n ) {\n prefix = text.slice(i, i + 4).toLowerCase() === \"www.\" ? 4 : 0;\n }\n if (prefix === 0) {\n i += 1;\n continue;\n }\n const start = i;\n let j = start + prefix;\n while (j < n && !URL_STOP.test(text[j]!)) {\n j += 1;\n work.steps += 1;\n }\n i = j;\n let e = j;\n // a final point or bracket belongs to the sentence, not to the URL; the counts make it linear\n const balance = { \")\": 0, \"]\": 0 };\n for (let at = start; at < j; at += 1) {\n const char = text[at];\n if (char === \"(\") balance[\")\"] += 1;\n else if (char === \")\") balance[\")\"] -= 1;\n else if (char === \"[\") balance[\"]\"] += 1;\n else if (char === \"]\") balance[\"]\"] -= 1;\n }\n work.steps += j - start;\n while (e > start + prefix) {\n work.steps += 1;\n const last = text[e - 1]!;\n if (URL_TRAIL.test(last)) {\n e -= 1;\n continue;\n }\n if ((last === \")\" || last === \"]\") && balance[last] < 0) {\n balance[last] += 1;\n e -= 1;\n continue;\n }\n break;\n }\n if (e <= start + prefix) continue;\n const written = text.slice(start, e);\n const schemeless = prefix === 4;\n let url: string;\n try {\n url = canonicalUrl(schemeless ? `https://${written}` : written);\n } catch {\n continue;\n }\n const span: FieldSpan = { start, end: e, in: doc.name };\n out.push({\n where: { span, path: null },\n raw: written,\n exact: written,\n excerpt: () => excerptOf(text, start, e),\n readings: [{ url, schemeless, relative: false, written }],\n });\n }\n return out;\n });\n\n/** This function gives the stored links of a page as places of a URL. The store keeps the canonical form of each link. */\nconst linkCands = (meta: Readonly<Record<string, unknown>>, work: Work): Cand<UrlReading>[] => {\n const links = meta[\"links\"];\n if (!Array.isArray(links)) return [];\n const out: Cand<UrlReading>[] = [];\n for (const [index, link] of links.entries()) {\n work.steps += 1;\n if (link === null || typeof link !== \"object\") continue;\n const href = (link as { href?: unknown }).href;\n const label = (link as { text?: unknown }).text;\n if (typeof href !== \"string\") continue;\n out.push({\n where: { span: null, path: `links[${String(index)}]` },\n raw: href,\n exact: href,\n excerpt: () => (typeof label === \"string\" && label.length > 0 ? `${label} (${href})` : href),\n readings: [{ url: href, schemeless: false, relative: false, written: href }],\n });\n }\n return out;\n};\n\n/** This function reads a proposed URL, and resolves a relative URL against the URL of the page. Only `http` and `https` URLs are URLs of this kind. */\nconst claimUrl = (value: string | number, base: string | null): UrlReading[] => {\n if (typeof value === \"number\") return [];\n const text = value.trim().replace(/^<(.*)>$/s, \"$1\");\n if (text.length === 0 || /\\s/.test(text)) return [];\n const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(text);\n const written = !hasScheme && /^www\\./i.test(text) ? `//${text}` : text;\n let resolved: URL;\n try {\n resolved = hasScheme ? new URL(written) : new URL(written, base ?? undefined);\n } catch {\n return [];\n }\n if (resolved.protocol !== \"http:\" && resolved.protocol !== \"https:\") return [];\n return [\n { url: canonicalUrl(resolved.href), schemeless: false, relative: !hasScheme, written: text },\n ];\n};\n\n/** A text with an `http` or `https` scheme, a `www.` host, or a path from the root. */\nconst LOOKS_LIKE_URL = /^(?:https?:\\/\\/|www\\.|\\/|\\.\\.?\\/)/i;\n\nconst URL_KIND: Comparator<UrlReading, UrlReading> = {\n compare: (claim, reading) => {\n const labels: string[] = [];\n if (claim.relative || reading.relative) labels.push(\"url-resolved\");\n if (reading.schemeless) {\n if (withoutScheme(claim.url) !== withoutScheme(reading.url)) return undefined;\n labels.push(\"url-scheme\");\n return { clean: true, key: claim.url, reading: claim.url, labels };\n }\n if (claim.url !== reading.url) return undefined;\n if (labels.length === 0) labels.push(\"url-form\");\n return { clean: true, key: reading.url, reading: reading.url, labels };\n },\n display: (reading) => reading.url,\n};\n\n// ── email addresses ─────────────────────────────────────────────────────────\n\nconst LOCAL = /^[A-Za-z0-9._%+-]$/;\nconst DOMAIN = /^[A-Za-z0-9.-]$/;\nconst DOMAIN_NAME = /^[a-z0-9-]+(\\.[a-z0-9-]+)*\\.[a-z]{2,}$/i;\n\n/** This function finds the email addresses of a text. Each unit is read two times at most. */\nconst emailCands = (doc: Doc, work: Work): Cand<string>[] =>\n cached(doc, \"email\", () => {\n const text = doc.text;\n const n = text.length;\n const out: Cand<string>[] = [];\n let i = 0;\n while (i < n) {\n work.steps += 1;\n if (text[i] !== \"@\") {\n i += 1;\n continue;\n }\n let s = i;\n while (s > 0 && LOCAL.test(text[s - 1]!)) {\n s -= 1;\n work.steps += 1;\n }\n while (s < i && text[s] === \".\") s += 1;\n let e = i + 1;\n while (e < n && DOMAIN.test(text[e]!)) {\n e += 1;\n work.steps += 1;\n }\n const next = e;\n while (e > i + 1 && (text[e - 1] === \".\" || text[e - 1] === \"-\")) e -= 1;\n if (s < i && DOMAIN_NAME.test(text.slice(i + 1, e))) {\n const written = text.slice(s, e);\n const start = s;\n const end = e;\n out.push({\n where: { span: { start, end, in: doc.name }, path: null },\n raw: written,\n exact: written,\n excerpt: () => excerptOf(text, start, end),\n readings: [written.toLowerCase()],\n });\n }\n i = Math.max(next, i + 1);\n }\n return out;\n });\n\nconst claimEmail = (value: string | number): string[] => {\n if (typeof value === \"number\") return [];\n const text = value.trim().replace(/^mailto:/i, \"\");\n const at = text.lastIndexOf(\"@\");\n if (at <= 0 || !/^[A-Za-z0-9._%+-]+$/.test(text.slice(0, at))) return [];\n return DOMAIN_NAME.test(text.slice(at + 1)) ? [text.toLowerCase()] : [];\n};\n\nconst EMAIL: Comparator<string, string> = {\n compare: (claim, reading) =>\n claim === reading ? { clean: true, key: reading, reading, labels: [\"email-case\"] } : undefined,\n display: (reading) => reading,\n};\n\n// ── JSON-LD ─────────────────────────────────────────────────────────────────\n\n/** A value of the JSON-LD at a path, with that path. */\ninterface Leaf {\n readonly path: string;\n readonly value: string | number | boolean;\n}\n\n/** A key of the JSON-LD goes into a path only when it is a name. Thus a path holds no other text of the page. */\nconst PATH_KEY = /^@?[A-Za-z_][A-Za-z0-9_-]{0,63}$/;\n\ninterface Visit {\n readonly value: unknown;\n readonly path: string;\n readonly depth: number;\n}\n\n/**\n * This function gives the values of the JSON-LD at a path. The first part of the path names a\n * schema.org type (`*` is each top-level record), and the path starts at each object of that type,\n * at any depth. Each array on the path gives each of its values.\n */\nconst leavesAt = (records: readonly unknown[], path: string, work: Work): Leaf[] => {\n const [head = \"\", ...rest] = path.split(\".\");\n const type = head.toLowerCase();\n const starts: Array<{ readonly value: unknown; readonly path: string }> = [];\n // an explicit stack: JSON-LD nested deeply cannot overflow the call stack\n const stack: Visit[] = [];\n for (let index = records.length - 1; index >= 0; index -= 1)\n stack.push({ value: records[index], path: `jsonld[${String(index)}]`, depth: 0 });\n while (stack.length > 0) {\n work.steps += 1;\n const { value, path: at, depth } = stack.pop()!;\n if (Array.isArray(value)) {\n for (let index = value.length - 1; index >= 0; index -= 1)\n stack.push({ value: value[index], path: `${at}[${String(index)}]`, depth: depth + 1 });\n continue;\n }\n if (value === null || typeof value !== \"object\") continue;\n const record = value as Record<string, unknown>;\n if (type === \"*\") {\n if (depth === 0) starts.push({ value: record, path: at });\n continue;\n }\n if (typesOf(record).some((name) => name.toLowerCase() === type))\n starts.push({ value: record, path: at });\n const keys = Object.keys(record);\n for (let index = keys.length - 1; index >= 0; index -= 1) {\n const key = keys[index]!;\n if (PATH_KEY.test(key))\n stack.push({ value: record[key], path: `${at}.${key}`, depth: depth + 1 });\n }\n }\n const out: Leaf[] = [];\n for (const start of starts) {\n const walk: Visit[] = [{ value: start.value, path: start.path, depth: 0 }];\n while (walk.length > 0) {\n work.steps += 1;\n const { value, path: at, depth } = walk.pop()!;\n if (Array.isArray(value)) {\n for (let index = value.length - 1; index >= 0; index -= 1)\n walk.push({ value: value[index], path: `${at}[${String(index)}]`, depth });\n continue;\n }\n if (depth === rest.length) {\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\")\n out.push({ path: at, value });\n else if (value !== null && typeof value === \"object\") {\n const inner = (value as Record<string, unknown>)[\"@value\"];\n if (typeof inner === \"string\" || typeof inner === \"number\" || typeof inner === \"boolean\")\n out.push({ path: `${at}.@value`, value: inner });\n }\n continue;\n }\n if (value === null || typeof value !== \"object\") continue;\n const key = rest[depth]!;\n const next = (value as Record<string, unknown>)[key];\n if (next !== undefined) walk.push({ value: next, path: `${at}.${key}`, depth: depth + 1 });\n }\n }\n return out;\n};\n\nconst leafText = (value: string | number | boolean): string =>\n typeof value === \"number\" ? (decimalOf(value) ?? String(value)) : String(value);\n\n/** This function checks a value against the JSON-LD at a path. It compares as a number, a date, a URL and a text, in that sequence. */\nconst checkJsonLd = (\n kind: string,\n path: string,\n value: string | number,\n claimText: string,\n source: ExtractSource,\n work: Work,\n): FieldCheck => {\n const meta = source.meta ?? {};\n const records = Array.isArray(meta[\"jsonld\"]) ? (meta[\"jsonld\"] as unknown[]) : [];\n const leaves = leavesAt(records, path, work);\n const dropped = meta[\"jsonld_dropped\"];\n const missing =\n typeof dropped === \"number\" && dropped > 0\n ? `the store keeps only a part of the JSON-LD of this page (${String(dropped)} records left out), so the path can be in the part that it left out`\n : null;\n if (leaves.length === 0) return empty(\"not-found\", kind, missing);\n const base = source.url ?? null;\n const cand = <R>(leaf: Leaf, readings: readonly R[]): Cand<R> => ({\n where: { span: null, path: leaf.path },\n raw: leaf.value,\n exact: leafText(leaf.value),\n excerpt: () => JSON.stringify(leaf.value).slice(0, 200),\n readings,\n });\n const stringOf = (leaf: Leaf): string | undefined =>\n typeof leaf.value === \"string\" ? leaf.value : undefined;\n const folded = fold(claimText, \"quote\");\n const TEXT: Comparator<string, string> = {\n compare: (claim, reading) =>\n fold(reading, \"quote\") === folded\n ? { clean: true, key: folded, reading: folded, labels: textLabels(reading, claim) }\n : undefined,\n display: (reading) => reading,\n };\n const attempts: Array<() => FieldCheck | undefined> = [\n () => {\n const claims = claimNumber(value);\n if (claims.length === 0) return undefined;\n const cands = leaves.map((leaf) =>\n cand(leaf, typeof leaf.value === \"boolean\" ? [] : claimNumber(leaf.value)),\n );\n return decide(kind, claimText, claims, cands, NUMBER, work);\n },\n () => {\n const claim = claimDate(value);\n if (claim.readings.length === 0) return undefined;\n const cands = leaves.map((leaf) => {\n const text = stringOf(leaf);\n return cand(leaf, text === undefined ? [] : claimDate(text).readings);\n });\n return decide(kind, claimText, claim.readings, cands, DATE, work);\n },\n () => {\n // only a text that looks like a URL is a URL here: each word is a relative URL\n const claims = LOOKS_LIKE_URL.test(claimText) ? claimUrl(value, base) : [];\n if (claims.length === 0) return undefined;\n const cands = leaves.map((leaf) => {\n const text = stringOf(leaf);\n return cand(\n leaf,\n text === undefined || !LOOKS_LIKE_URL.test(text.trim()) ? [] : claimUrl(text, base),\n );\n });\n return decide(kind, claimText, claims, cands, URL_KIND, work);\n },\n () => {\n if (folded.length === 0) return undefined;\n const cands = leaves.map((leaf) => cand(leaf, [leafText(leaf.value)]));\n return decide(kind, claimText, [claimText], cands, TEXT, work);\n },\n ];\n for (const attempt of attempts) {\n const result = attempt();\n if (result !== undefined && result.status !== \"not-found\") return result;\n }\n return empty(\"not-found\", kind, missing);\n};\n\n// ── the check of the fields ─────────────────────────────────────────────────\n\ninterface Context {\n readonly source: ExtractSource;\n readonly text: Doc;\n full: Doc | undefined | null;\n readonly work: Work;\n}\n\n/** This function gives the whole text of a page that the reader cut, or null. It folds that text at its first use only. */\nconst fullOf = (context: Context): Doc | null => {\n if (context.full === undefined) {\n const full = context.source.full_text;\n context.full =\n full === undefined || full === context.source.text\n ? null\n : prepare(\"full_text\", full, context.work);\n }\n return context.full;\n};\n\n/** This function checks a field in the text. When the reader cut the text and the text does not hold the value, it checks the whole text too. */\nconst inText = (context: Context, check: (doc: Doc) => FieldCheck): FieldCheck => {\n const first = check(context.text);\n if (first.status !== \"not-found\") return first;\n const full = fullOf(context);\n if (full === null) return first;\n const second = check(full);\n return second.status === \"not-found\" ? first : second;\n};\n\n/** This function reads the kind of a field. A kind that is not a kind throws a `RangeError`. */\nconst kindOf = (name: string, claim: FieldClaim): string => {\n const kind: string = claim.kind ?? (typeof claim.value === \"number\" ? \"number\" : \"text\");\n if (FIELD_KINDS.includes(kind)) return kind;\n if (kind.startsWith(\"json-ld:\") && kind.length > \"json-ld:\".length && !/\\s/.test(kind))\n return kind;\n throw new RangeError(\n `the kind of the field ${name} is ${FIELD_KINDS.join(\", \")} or json-ld:<path>, not ${JSON.stringify(kind)}`,\n );\n};\n\nconst INVALID: Readonly<Record<string, string>> = {\n number: \"the value is not a number\",\n money: \"the value is not an amount of money\",\n date: \"the value is not a date\",\n url: \"the value is not an http or https URL\",\n email: \"the value is not an email address\",\n phone: \"the value is not a phone number of 7 to 15 digits\",\n};\n\nconst checkOne = (name: string, claim: FieldClaim, context: Context): FieldCheck => {\n const kind = kindOf(name, claim);\n const value = claim.value;\n if (typeof value === \"number\" && !Number.isFinite(value))\n return empty(\"invalid\", kind, \"the value is not a finite number\");\n // a mailto: or tel: link names the address or the number, so the exact text is without it\n const prefix = kind === \"email\" ? /^mailto:/i : kind === \"phone\" ? /^tel:/i : undefined;\n const trimmed = typeof value === \"number\" ? (decimalOf(value) ?? \"\") : value.trim();\n const claimText = prefix === undefined ? trimmed : trimmed.replace(prefix, \"\");\n if (claimText.length === 0) return empty(\"invalid\", kind, \"the value is empty\");\n const work = context.work;\n const invalid = (): FieldCheck =>\n empty(\"invalid\", kind, INVALID[kind] ?? \"the value is not valid\");\n switch (kind) {\n case \"text\":\n return inText(context, (doc) => checkText(doc, claimText, work));\n case \"number\": {\n const claims = claimNumber(value);\n if (claims.length === 0) return invalid();\n return inText(context, (doc) =>\n decide(kind, claimText, claims, numberCands(doc, work), NUMBER, work),\n );\n }\n case \"money\": {\n const claims = claimMoney(value);\n if (claims.length === 0) return invalid();\n return inText(context, (doc) =>\n decide(kind, claimText, claims, moneyCands(doc, work), MONEY, work),\n );\n }\n case \"date\": {\n const claim = claimDate(value);\n if (claim.readings.length === 0) return invalid();\n const result = inText(context, (doc) =>\n decide(kind, claimText, claim.readings, dateCands(doc, work), DATE, work),\n );\n if (!claim.time || result.status === \"verified\") return result;\n const timeNote = \"the check compares the date of the value, not its time\";\n return { ...result, note: result.note === null ? timeNote : `${result.note}; ${timeNote}` };\n }\n case \"url\": {\n const claims = claimUrl(value, context.source.url ?? null);\n if (claims.length === 0) return invalid();\n const links = linkCands(context.source.meta ?? {}, work);\n return inText(context, (doc) =>\n decide(kind, claimText, claims, [...urlCands(doc, work), ...links], URL_KIND, work),\n );\n }\n case \"email\": {\n const claims = claimEmail(value);\n if (claims.length === 0) return invalid();\n return inText(context, (doc) =>\n decide(kind, claimText, claims, emailCands(doc, work), EMAIL, work),\n );\n }\n case \"phone\": {\n const claims = claimPhone(value);\n if (claims.length === 0) return invalid();\n return inText(context, (doc) =>\n decide(kind, claimText, claims, phoneCands(doc, work), PHONE, work),\n );\n }\n default:\n return checkJsonLd(\n kind,\n kind.slice(\"json-ld:\".length),\n value,\n claimText,\n context.source,\n work,\n );\n }\n};\n\n/** This function validates the fields, and gives their names and values. A field that is not valid throws a `RangeError`. */\nconst claimsOf = (\n fields: Readonly<Record<string, FieldClaim>>,\n): Array<readonly [string, FieldClaim]> => {\n if (fields === null || typeof fields !== \"object\" || Array.isArray(fields))\n throw new RangeError(\"fields must be an object of names and values\");\n const entries = Object.entries(fields);\n for (const [name, claim] of entries) {\n if (name.length === 0) throw new RangeError(\"the name of a field must not be empty\");\n if (claim === null || typeof claim !== \"object\")\n throw new RangeError(`the field ${name} must be an object with a value`);\n if (typeof claim.value !== \"string\" && typeof claim.value !== \"number\")\n throw new RangeError(`the value of the field ${name} must be a text or a number`);\n kindOf(name, claim);\n }\n return entries;\n};\n\nconst summaryOf = (statuses: readonly FieldStatus[]): ExtractSummary => {\n const count = (status: FieldStatus): number => statuses.filter((one) => one === status).length;\n return {\n fields: statuses.length,\n verified: count(\"verified\"),\n normalized: count(\"normalized\"),\n ambiguous: count(\"ambiguous\"),\n not_found: count(\"not-found\"),\n held_back: count(\"held-back\"),\n invalid: count(\"invalid\"),\n };\n};\n\n/**\n * This function checks the proposed values of fields against the text and the metadata of a\n * document. It sends no request and writes nothing. It does not make values. A field that is not\n * valid throws a `RangeError`. The work grows in proportion to the length of the text.\n */\nexport const checkFields = (\n source: ExtractSource,\n fields: Readonly<Record<string, FieldClaim>>,\n): FieldsChecked => {\n const claims = claimsOf(fields);\n const work: Work = { steps: 0 };\n const context: Context = {\n source,\n text: prepare(\"text\", source.text, work),\n full: undefined,\n work,\n };\n const checked: Record<string, FieldCheck> = {};\n for (const [name, claim] of claims) checked[name] = checkOne(name, claim, context);\n return {\n fields: checked,\n summary: summaryOf(Object.values(checked).map((field) => field.status)),\n steps: work.steps,\n };\n};\n\n// ── the check of a stored document ──────────────────────────────────────────\n\nconst withCapture = (\n fields: Readonly<Record<string, FieldCheck>>,\n capture: string | null,\n fetchedAt: string | null,\n): Record<string, FieldResult> =>\n Object.fromEntries(\n Object.entries(fields).map(([name, field]) => [\n name,\n { ...field, capture, fetched_at: fetchedAt },\n ]),\n );\n\n/** This function gives the same result for each field, with no check: for a held-back document, or a time before the first capture. */\nconst sameForEach = (\n claims: ReadonlyArray<readonly [string, FieldClaim]>,\n status: FieldStatus,\n note: string,\n): Record<string, FieldResult> =>\n Object.fromEntries(\n claims.map(([name, claim]) => [\n name,\n { ...empty(status, kindOf(name, claim), note), capture: null, fetched_at: null },\n ]),\n );\n\n/**\n * This function checks the proposed values of fields against a stored document. It does not make\n * values. It applies the rules of `verify`:\n *\n * - With no option, it checks the current version. `capture` checks the version of one capture,\n * and `at` checks the version that was current at a time.\n * - A held-back document gives `held-back` for each field, with no position and no capture, unless\n * `userProvided`. The crawler then logs an override.\n * - A field that is not valid throws a `RangeError`, and an unknown document throws an error.\n */\nexport const extractDocument = async (\n runtime: CrawlRuntime,\n id: string,\n fields: Readonly<Record<string, FieldClaim>>,\n options: ExtractOptions = {},\n): Promise<ExtractResult> => {\n const claims = claimsOf(fields);\n if (options.at !== undefined && options.capture !== undefined)\n throw new RangeError(\"give capture or at, not the two: each one chooses a version\");\n const at = options.at === undefined ? undefined : isoTime(\"at\", options.at);\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 version: VersionText | undefined;\n let first: string | undefined;\n if (options.capture !== undefined) {\n version = runtime.index.versionText(id, options.capture);\n if (version === undefined)\n throw new Error(`the document ${id} has no version from the capture ${options.capture}`);\n } else if (at !== undefined) ({ version, first } = versionAtTime(runtime, id, at));\n else version = runtime.index.versionText(id);\n const aiInput: AiInput =\n doc.ai_input === \"no\" || version?.ai_input === \"no\"\n ? \"no\"\n : (version?.ai_input ?? doc.ai_input);\n const userProvided = options.userProvided ?? false;\n const base = {\n doc: doc.doc,\n url: doc.url,\n captures: doc.captures,\n ...(at === undefined ? {} : { at }),\n };\n if (isHeldBack({ ai_input: aiInput }, userProvided)) {\n // no check runs: the result is the same for each value, so it tells nothing about the text\n return {\n ...base,\n capture: null,\n blob: null,\n fetched_at: null,\n held_back: true,\n fields: sameForEach(claims, \"held-back\", HELD_BACK),\n summary: summaryOf(claims.map(() => \"held-back\")),\n note: HELD_BACK,\n };\n }\n if (aiInput === \"no\" && userProvided) await recordOverride(runtime, doc, \"extract\");\n if (version === undefined) {\n const note = `the document ${id} has no version at ${at ?? \"this time\"} or before${first === undefined ? \"\" : `: its first capture is from ${first}`}`;\n return {\n ...base,\n capture: null,\n blob: null,\n fetched_at: null,\n held_back: false,\n fields: sameForEach(claims, \"not-found\", note),\n summary: summaryOf(claims.map(() => \"not-found\")),\n note,\n before_first: true,\n };\n }\n const checked = checkFields(\n {\n text: version.text,\n full_text: version.full_text,\n url: version.url ?? doc.url,\n meta: version.meta,\n },\n Object.fromEntries(claims),\n );\n const incomplete = version.meta[\"truncated\"] === true && version.full_text === undefined;\n // an archived copy says so, as in read and verify, so a citation cannot present it as the current page\n const archived = archivedOf(version.meta);\n const notes = [\n ...(incomplete && checked.summary.not_found > 0\n ? [\n \"the reader stopped before the end of the file, so a value that is not found can be in the part that it did not read\",\n ]\n : []),\n ...(archived === undefined ? [] : [archivedNote(archived)]),\n ];\n return {\n ...base,\n capture: version.capture,\n blob: version.blob,\n fetched_at: version.fetched_at,\n held_back: false,\n fields: withCapture(checked.fields, version.capture, version.fetched_at),\n summary: checked.summary,\n note: notes.length > 0 ? notes.join(\"; \") : null,\n ...(archived === undefined ? {} : { archived }),\n };\n};\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"log-Dx-wXjJl.mjs","names":[],"sources":["../src/blobs.ts","../src/log.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { mkdir, open, readFile, rename, rm, stat } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * A blob store keeps the bytes of each response, and the hash of the bytes is the key. The crawler\n * needs only these two operations, so an application can give its own store.\n */\nexport interface BlobStore {\n put(bytes: Uint8Array): Promise<{ readonly hash: string }>;\n get(hash: string): Promise<Uint8Array | undefined>;\n}\n\n/** These options change a file blob store. */\nexport interface FileBlobStoreOptions {\n /** When this value is true (the default), a blob is on the disk (`fsync`) before `put` finishes. */\n readonly sync?: boolean | undefined;\n}\n\nconst HASH = /^[0-9a-f]{64}$/;\n\n/** A rename can fail with these codes when a different writer puts the same blob at the same time. */\nconst RACE_CODES: ReadonlySet<string> = new Set([\n \"EPERM\",\n \"EEXIST\",\n \"EBUSY\",\n \"EACCES\",\n \"ENOTEMPTY\",\n]);\n\nconst blobPath = (root: string, hash: string): string =>\n join(root, hash.slice(0, 2), hash.slice(2, 4), hash);\n\nconst hashOf = (bytes: Uint8Array): string => createHash(\"sha256\").update(bytes).digest(\"hex\");\n\nconst codeOf = (error: unknown): string =>\n typeof error === \"object\" && error !== null && \"code\" in error ? String(error.code) : \"\";\n\nconst statOf = (path: string): Promise<Stats | undefined> => stat(path).catch(() => undefined);\n\n/** This function writes a folder to the disk, so a rename in it stays after a power cut. Windows cannot open a folder, so it skips this step. */\nconst syncFolder = async (path: string): Promise<void> => {\n if (process.platform === \"win32\") return;\n try {\n const handle = await open(path, \"r\");\n try {\n await handle.sync();\n } finally {\n await handle.close();\n }\n } catch {\n // a file system that cannot sync a folder keeps the blob in any case\n }\n};\n\n/**\n * This store keeps each blob in a file: `<dir>/ab/cd/abcd…`, where the name is the SHA-256 hash\n * of the bytes. These are its rules:\n *\n * - Equal bytes are written one time only. A file of the wrong size (a write that a crash cut) is\n * written again.\n * - A write goes to a temporary file with a random name, goes to the disk, and then gets its name.\n * A crash thus cannot leave half a blob under its name.\n * - Two writers can put the same bytes at the same time. When the rename of one fails because the\n * other was first, the put succeeds if the stored file has the same hash.\n * - The method `get` examines the hash of the bytes. A damaged file gets a new name\n * (`.damaged`), and `get` gives no bytes, so the crawler fetches the body again.\n */\nexport class FileBlobStore implements BlobStore {\n private readonly writing = new Map<\n string,\n Promise<{ readonly hash: string; readonly existed: boolean }>\n >();\n private readonly sync: boolean;\n\n constructor(\n readonly dir: string,\n options: FileBlobStoreOptions = {},\n ) {\n this.sync = options.sync ?? true;\n }\n\n put(bytes: Uint8Array): Promise<{ readonly hash: string; readonly existed: boolean }> {\n const hash = hashOf(bytes);\n const running = this.writing.get(hash);\n if (running !== undefined) return running;\n const work = this.write(hash, bytes).finally(() => this.writing.delete(hash));\n this.writing.set(hash, work);\n return work;\n }\n\n /** This method tells if a file holds exactly the bytes of a hash. */\n private static async holds(path: string, hash: string): Promise<boolean> {\n try {\n return hashOf(await readFile(path)) === hash;\n } catch {\n return false;\n }\n }\n\n private async write(\n hash: string,\n bytes: Uint8Array,\n ): Promise<{ readonly hash: string; readonly existed: boolean }> {\n const target = blobPath(this.dir, hash);\n const existing = await statOf(target);\n if (existing?.isFile() === true && existing.size === bytes.length)\n return { hash, existed: true };\n const folder = dirname(target);\n await mkdir(folder, { recursive: true, mode: 0o700 });\n const temporary = `${target}.${randomUUID()}.tmp`;\n try {\n const handle = await open(temporary, \"wx\", 0o600);\n try {\n await handle.writeFile(bytes);\n if (this.sync) await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await rename(temporary, target);\n } catch (error) {\n // a different writer put the same bytes first, and Windows does not replace a file in use\n if (!RACE_CODES.has(codeOf(error)) || !(await FileBlobStore.holds(target, hash)))\n throw error;\n return { hash, existed: true };\n }\n if (this.sync) await syncFolder(folder);\n return { hash, existed: false };\n } finally {\n await rm(temporary, { force: true }).catch(() => undefined);\n }\n }\n\n async get(hash: string): Promise<Uint8Array | undefined> {\n if (!HASH.test(hash)) return undefined;\n const target = blobPath(this.dir, hash);\n let bytes: Buffer;\n try {\n bytes = await readFile(target);\n } catch {\n return undefined;\n }\n if (hashOf(bytes) !== hash) {\n // a torn or damaged file is never the body: it moves away, and the next put writes it again\n await rename(target, `${target}.${randomUUID()}.damaged`).catch(() => undefined);\n return undefined;\n }\n return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n }\n}\n","import { createReadStream, existsSync } from \"node:fs\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { mkdir, open, readdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { CaptureRecord } from \"./record.ts\";\n\n/** A scan filter selects the records of one source, one kind, or the records after one time. */\nexport interface CaptureFilter {\n readonly source?: string | undefined;\n readonly kind?: string | undefined;\n /** This value is an ISO 8601 time. The scan gives the records received at this time or later. */\n readonly since?: string | undefined;\n}\n\n/** A damaged line is a line of the log that is not a record: a torn append, or bytes that a crash left. */\nexport interface DamagedLine {\n /** This value is the path of the file. */\n readonly path: string;\n /** This value is the number of the line in the file, from 1. */\n readonly line: number;\n}\n\n/** These options change a scan. */\nexport interface ScanOptions {\n /** The scan skips a damaged line, and it gives the line to this function. */\n readonly onDamaged?: ((damaged: DamagedLine) => void) | undefined;\n}\n\n/** The read side of the capture log is what `reindex` replays. */\nexport interface CaptureReader {\n scan(filter?: CaptureFilter, options?: ScanOptions): AsyncIterable<CaptureRecord>;\n}\n\n/** These options change a capture log. */\nexport interface CaptureLogOptions {\n /**\n * When this value is true (the default), the log writes each batch of appends to the disk\n * (`fdatasync`) before the appends finish. Thus a power cut does not lose a record that the\n * crawler reported.\n */\n readonly sync?: boolean | undefined;\n}\n\ninterface Pending {\n readonly path: string;\n readonly line: string;\n readonly resolve: () => void;\n readonly reject: (error: unknown) => void;\n}\n\nconst MONTH_FILE = /^(\\d{2})(?:\\.[^.]+)?\\.ndjson$/;\nconst YEAR_DIR = /^\\d{4}$/;\nconst NEWLINE = 0x0a;\n\n/** This function writes the whole buffer. A write to a file can write less than the buffer, and the loop writes the rest. */\nconst writeAll = async (handle: FileHandle, bytes: Buffer): Promise<void> => {\n let at = 0;\n while (at < bytes.length) {\n const { bytesWritten } = await handle.write(bytes, at, bytes.length - at);\n at += bytesWritten;\n }\n};\n\n/**\n * This function reads a file line by line, as bytes, so a long file does not become one string. It\n * gives each line and tells if a line break ends it.\n */\nasync function* linesOf(\n path: string,\n): AsyncGenerator<{ readonly text: string; readonly complete: boolean }> {\n const decoder = new TextDecoder(\"utf-8\");\n let rest: Buffer | undefined;\n for await (const chunk of createReadStream(path) as AsyncIterable<Buffer>) {\n const data = rest === undefined ? chunk : Buffer.concat([rest, chunk]);\n rest = undefined;\n let start = 0;\n for (;;) {\n const end = data.indexOf(NEWLINE, start);\n if (end === -1) break;\n yield { text: decoder.decode(data.subarray(start, end)), complete: true };\n start = end + 1;\n }\n if (start < data.length) rest = Buffer.from(data.subarray(start));\n }\n if (rest !== undefined && rest.length > 0) yield { text: decoder.decode(rest), complete: false };\n}\n\nconst monthKey = (year: string, file: string): string =>\n `${year}-${MONTH_FILE.exec(file)?.[1] ?? \"00\"}`;\n\n/**\n * The capture log keeps each fetch decision as one line of JSON in\n * `<dir>/<source>/<yyyy>/<mm>.ndjson`. It is the record of each access that the crawler makes.\n *\n * These are the rules of the log:\n *\n * - The log appends a batch of records with one write, and it writes the batch to the disk before\n * the appends finish.\n * - An append always starts on a new line. After a crash leaves a partial line, the next append\n * starts after a line break. The partial line stays as a damaged line, and the record is good.\n * - A scan reads each file as a stream. It skips a damaged line, counts it, and continues.\n * - The log does not remove or change a line.\n *\n * Two processes can append to one log, because each batch is one write to a file in append mode.\n */\nexport class CaptureLog implements CaptureReader {\n private readonly handles = new Map<string, FileHandle>();\n private readonly pending: Pending[] = [];\n private draining: Promise<void> | undefined;\n private readonly sync: boolean;\n\n constructor(\n readonly dir: string,\n options: CaptureLogOptions = {},\n ) {\n this.sync = options.sync ?? true;\n }\n\n private pathOf(record: CaptureRecord): string {\n return join(\n this.dir,\n record.source,\n record.received_at.slice(0, 4),\n `${record.received_at.slice(5, 7)}.ndjson`,\n );\n }\n\n private async handleFor(path: string): Promise<FileHandle> {\n const known = this.handles.get(path);\n if (known !== undefined) return known;\n // the folders and the files are for the owner only, where the system has modes\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n const handle = await open(path, \"a+\", 0o600);\n this.handles.set(path, handle);\n return handle;\n }\n\n /** This method tells if the last byte of a file is not a line break: a partial line that a crash left. */\n private static async needsBreak(handle: FileHandle): Promise<boolean> {\n const { size } = await handle.stat();\n if (size === 0) return false;\n const last = Buffer.alloc(1);\n await handle.read(last, 0, 1, size - 1);\n return last[0] !== NEWLINE;\n }\n\n append(record: CaptureRecord): Promise<void> {\n let line: string;\n try {\n line = `${JSON.stringify(record)}\\n`;\n } catch (error) {\n return Promise.reject(error instanceof Error ? error : new Error(String(error)));\n }\n return new Promise<void>((resolve, reject) => {\n this.pending.push({ path: this.pathOf(record), line, resolve, reject });\n this.kick();\n });\n }\n\n /** This method starts the writer when it is not in progress. A record that comes as the writer stops starts it again. */\n private kick(): void {\n if (this.draining !== undefined) return;\n this.draining = this.drain().finally(() => {\n this.draining = undefined;\n if (this.pending.length > 0) this.kick();\n });\n }\n\n /** This method writes the waiting records in batches: one write and one sync for each file. */\n private async drain(): Promise<void> {\n while (this.pending.length > 0) {\n const batch = this.pending.splice(0);\n const byPath = new Map<string, Pending[]>();\n for (const item of batch) {\n const list = byPath.get(item.path);\n if (list === undefined) byPath.set(item.path, [item]);\n else list.push(item);\n }\n for (const [path, items] of byPath) {\n try {\n const handle = await this.handleFor(path);\n const lead = (await CaptureLog.needsBreak(handle)) ? \"\\n\" : \"\";\n await writeAll(\n handle,\n Buffer.from(`${lead}${items.map((item) => item.line).join(\"\")}`, \"utf8\"),\n );\n if (this.sync) await handle.datasync();\n for (const item of items) item.resolve();\n } catch (error) {\n // a handle that failed is opened again for the next append\n const handle = this.handles.get(path);\n this.handles.delete(path);\n await handle?.close().catch(() => undefined);\n for (const item of items) item.reject(error);\n }\n }\n }\n }\n\n /** This method waits until each append in progress is on the disk. */\n async flush(): Promise<void> {\n while (this.draining !== undefined) await this.draining;\n }\n\n /** This method gives the record files of one source, in the sequence of time. It skips the months before `since`. */\n private async filesOf(source: string, since: string | undefined): Promise<string[]> {\n const sourceDir = join(this.dir, source);\n if (!existsSync(sourceDir)) return [];\n const from = since?.slice(0, 7);\n const years = (await readdir(sourceDir, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && YEAR_DIR.test(entry.name))\n .map((entry) => entry.name)\n .sort();\n const files: string[] = [];\n for (const year of years) {\n if (from !== undefined && year < from.slice(0, 4)) continue;\n const months = (await readdir(join(sourceDir, year)))\n .filter((name) => MONTH_FILE.test(name))\n .sort();\n for (const month of months) {\n if (from !== undefined && monthKey(year, month) < from) continue;\n files.push(join(sourceDir, year, month));\n }\n }\n return files;\n }\n\n /** This method reads the records of one source in the sequence of the files. */\n private async *readSource(\n source: string,\n filter: CaptureFilter | undefined,\n options: ScanOptions | undefined,\n ): AsyncGenerator<CaptureRecord> {\n for (const path of await this.filesOf(source, filter?.since)) {\n let number = 0;\n for await (const { text, complete } of linesOf(path)) {\n number += 1;\n const trimmed = text.trim();\n if (trimmed.length === 0) continue;\n let row: CaptureRecord;\n try {\n row = JSON.parse(trimmed) as CaptureRecord;\n if (typeof row !== \"object\" || row === null || typeof row.received_at !== \"string\") {\n throw new Error(\"not a record\");\n }\n } catch {\n // the append of a different process can be in progress at the end of the file\n if (complete) options?.onDamaged?.({ path, line: number });\n continue;\n }\n if (filter?.kind !== undefined && row.kind !== filter.kind) continue;\n if (filter?.since !== undefined && row.received_at < filter.since) continue;\n yield row;\n }\n }\n }\n\n /**\n * This method reads the records that agree with the filter. It first waits for the appends that\n * are in progress. It reads each file as a stream, so its memory does not grow with the log.\n *\n * The records of one source come in the sequence of the files. The records of two or more sources\n * merge in the sequence of `received_at`.\n */\n async *scan(filter?: CaptureFilter, options?: ScanOptions): AsyncIterable<CaptureRecord> {\n await this.flush();\n const sources =\n filter?.source !== undefined\n ? [filter.source]\n : existsSync(this.dir)\n ? (await readdir(this.dir, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .sort()\n : [];\n if (sources.length === 1) {\n yield* this.readSource(sources[0]!, filter, options);\n return;\n }\n // a merge of the sources: the next record is the earliest head\n const streams = sources.map((source) =>\n this.readSource(source, filter, options)[Symbol.asyncIterator](),\n );\n const heads: Array<CaptureRecord | undefined> = [];\n for (const stream of streams)\n heads.push((await stream.next()).value as CaptureRecord | undefined);\n for (;;) {\n let pick = -1;\n for (const [index, head] of heads.entries()) {\n if (head !== undefined && (pick === -1 || head.received_at < heads[pick]!.received_at)) {\n pick = index;\n }\n }\n if (pick === -1) return;\n yield heads[pick]!;\n heads[pick] = (await streams[pick]!.next()).value as CaptureRecord | undefined;\n }\n }\n\n /** This method writes the appends in progress, then closes the files. A later append opens them again. */\n async close(): Promise<void> {\n await this.flush();\n const handles = [...this.handles.values()];\n this.handles.clear();\n for (const handle of handles) await handle.close();\n }\n}\n"],"mappings":"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"}
@@ -1,22 +0,0 @@
1
- import { isIP } from "node:net";
2
- //#region src/loopback.ts
3
- /**
4
- * This function tells if a host name or an address is a loopback host: `localhost`, an address in
5
- * `127.0.0.0/8`, or `::1`. The MCP server over HTTP binds to a loopback host unless the operator
6
- * permits a different host. The function imports only Node.js, so the command line uses it before
7
- * the store opens.
8
- */
9
- const isLoopbackHost = (host) => {
10
- const name = host.trim().toLowerCase().replace(/^\[(.*)\]$/, "$1");
11
- if (name === "localhost" || name.endsWith(".localhost")) return true;
12
- const mapped = name.startsWith("::ffff:") ? name.slice(7) : name;
13
- if (isIP(mapped) === 4) return mapped.startsWith("127.");
14
- if (isIP(name) === 6) return /^(?:0{0,4}:){7}0{0,3}1$|^::1$/.test(name);
15
- return false;
16
- };
17
- /** This function gives a host for a URL: an IPv6 address goes in brackets. */
18
- const urlHost = (host) => isIP(host) === 6 ? `[${host}]` : host;
19
- //#endregion
20
- export { urlHost as n, isLoopbackHost as t };
21
-
22
- //# sourceMappingURL=loopback-C_QYowSf.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loopback-C_QYowSf.mjs","names":[],"sources":["../src/loopback.ts"],"sourcesContent":["import { isIP } from \"node:net\";\n\n/**\n * This function tells if a host name or an address is a loopback host: `localhost`, an address in\n * `127.0.0.0/8`, or `::1`. The MCP server over HTTP binds to a loopback host unless the operator\n * permits a different host. The function imports only Node.js, so the command line uses it before\n * the store opens.\n */\nexport const isLoopbackHost = (host: string): boolean => {\n const name = host\n .trim()\n .toLowerCase()\n .replace(/^\\[(.*)\\]$/, \"$1\");\n if (name === \"localhost\" || name.endsWith(\".localhost\")) return true;\n const mapped = name.startsWith(\"::ffff:\") ? name.slice(7) : name;\n if (isIP(mapped) === 4) return mapped.startsWith(\"127.\");\n if (isIP(name) === 6) return /^(?:0{0,4}:){7}0{0,3}1$|^::1$/.test(name);\n return false;\n};\n\n/** This function gives a host for a URL: an IPv6 address goes in brackets. */\nexport const urlHost = (host: string): string => (isIP(host) === 6 ? `[${host}]` : host);\n"],"mappings":";;;;;;;;AAQA,MAAa,kBAAkB,SAA0B;CACvD,MAAM,OAAO,KACV,KAAK,CAAC,CACN,YAAY,CAAC,CACb,QAAQ,cAAc,IAAI;CAC7B,IAAI,SAAS,eAAe,KAAK,SAAS,YAAY,GAAG,OAAO;CAChE,MAAM,SAAS,KAAK,WAAW,SAAS,IAAI,KAAK,MAAM,CAAC,IAAI;CAC5D,IAAI,KAAK,MAAM,MAAM,GAAG,OAAO,OAAO,WAAW,MAAM;CACvD,IAAI,KAAK,IAAI,MAAM,GAAG,OAAO,gCAAgC,KAAK,IAAI;CACtE,OAAO;AACT;;AAGA,MAAa,WAAW,SAA0B,KAAK,IAAI,MAAM,IAAI,IAAI,KAAK,KAAK"}
package/build/mcp.d.mts DELETED
@@ -1,199 +0,0 @@
1
- import { E as OpenOptions, O as PoliteCrawler, s as DaemonMcpHost } from "./daemon-wwIWsUmU.mjs";
2
- import { McpServer, McpServer as McpServer$1 } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { Request, RequestId, Result, Task } from "@modelcontextprotocol/sdk/types.js";
4
- import { CreateTaskOptions, TaskStore } from "@modelcontextprotocol/sdk/experimental/tasks/interfaces.js";
5
- import "@modelcontextprotocol/sdk/shared/protocol.js";
6
- //#region src/mcp-tasks.d.ts
7
- /** These are the limits of the tasks of one MCP server. */
8
- export declare const TASK_LIMITS: {
9
- /** A task and its result stay for this time after the task starts or ends: 1 hour by default. */
10
- readonly ttlMs: 3_600_000;
11
- /** The longest time that a client can ask for: 1 day. */
12
- readonly maxTtlMs: 86_400_000;
13
- /** The time that the server proposes between two polls of the status: 1 second. */
14
- readonly pollMs: 1_000;
15
- /** The most tasks that run at one time. A new task above the limit is an error. */
16
- readonly maxRunning: 8;
17
- };
18
- /** This is a task of the server, as the status of the daemon shows it. */
19
- interface TaskInfo {
20
- /** This id is the task id that the client got. */
21
- readonly id: string;
22
- /** This value is the name of the tool, for example `crawl_site`. */
23
- readonly tool: string;
24
- /** This value is `working`, `input_required`, `completed`, `failed` or `cancelled`. */
25
- readonly status: string;
26
- /** This text tells the progress, for example `3 of 10 done`, or null. */
27
- readonly message: string | null;
28
- readonly created_at: string;
29
- readonly updated_at: string;
30
- }
31
- /** These options make a task store. */
32
- interface CrawlTaskStoreOptions {
33
- /** This value is the most tasks that run at one time. The default is 8. */
34
- readonly maxRunning?: number | undefined;
35
- /** This function operates when a task starts, changes its status or ends. */
36
- readonly onChange?: (() => void) | undefined;
37
- }
38
- /**
39
- * This store keeps the tasks of the long tools in memory. It wraps the `InMemoryTaskStore` of the
40
- * SDK, which binds each task to its session. It also keeps an abort signal for each task: the
41
- * cancel of a task (`tasks/cancel`) aborts the work of its tool. One store serves each session of
42
- * one server.
43
- */
44
- export declare class CrawlTaskStore implements TaskStore {
45
- private readonly options;
46
- private readonly store;
47
- private readonly jobs;
48
- private readonly maxRunning;
49
- private closed;
50
- constructor(options?: CrawlTaskStoreOptions);
51
- private changed;
52
- /** This method removes the jobs whose tasks the SDK store removed at the end of their time. */
53
- private prune;
54
- createTask(params: CreateTaskOptions, requestId: RequestId, request: Request, sessionId?: string): Promise<Task>;
55
- getTask(taskId: string, sessionId?: string): Promise<Task | null>;
56
- storeTaskResult(taskId: string, status: "completed" | "failed", result: Result, sessionId?: string): Promise<void>;
57
- getTaskResult(taskId: string, sessionId?: string): Promise<Result>;
58
- updateTaskStatus(taskId: string, status: Task["status"], statusMessage?: string, sessionId?: string): Promise<void>;
59
- listTasks(cursor?: string, sessionId?: string): Promise<{
60
- tasks: Task[];
61
- nextCursor?: string;
62
- }>;
63
- /** This method gives the abort signal of a task. A cancel of the task aborts it. */
64
- signal(taskId: string): AbortSignal;
65
- /** This method gives the reason that a task did not start, or undefined for a task that started. */
66
- refusal(taskId: string): string | undefined;
67
- /** This method tells if the client asked for the task. False is the call of a client with no tasks. */
68
- augmented(taskId: string): boolean;
69
- /** This method gives the tasks that did not end, the oldest first. */
70
- running(): TaskInfo[];
71
- /** This method gives the sessions that have a task in progress. */
72
- busySessions(): Set<string>;
73
- /** This method cancels the tasks of a session that closed. Their work stops. */
74
- cancelSession(sessionId: string, message?: string): Promise<void>;
75
- /** This method cancels each task that runs, and removes all the tasks. A second close does nothing. */
76
- close(): Promise<void>;
77
- }
78
- //#endregion
79
- //#region src/mcp-http.d.ts
80
- /** These are the defaults of the MCP server over HTTP. */
81
- export declare const MCP_HTTP_DEFAULTS: {
82
- /** The port of the server. */
83
- readonly port: 9307;
84
- /** The host of the server: the IPv4 loopback address. */
85
- readonly host: "127.0.0.1";
86
- /** The path of the MCP endpoint. */
87
- readonly path: "/mcp";
88
- /** The path of the status of the daemon. */
89
- readonly statusPath: "/status";
90
- /** The most sessions at one time. A new session above the limit closes the session that was idle longest. */
91
- readonly maxSessions: 32;
92
- /** The largest body of a request that starts a session: 4 MiB. */
93
- readonly maxBodyBytes: 4_194_304;
94
- /** The shortest token: 16 characters. */
95
- readonly minTokenLength: 16;
96
- };
97
- /** These options serve MCP over HTTP. */
98
- interface McpHttpOptions {
99
- /** This value is the port. The default is 9307. The value 0 takes a free port. */
100
- readonly port?: number | undefined;
101
- /** This value is the host to bind. The default is `127.0.0.1`. A host that is not loopback needs `allowRemote`. */
102
- readonly host?: string | undefined;
103
- /** The value true permits a host that is not loopback. Then other computers can try the token. */
104
- readonly allowRemote?: boolean | undefined;
105
- /** This value is the bearer token. The default reads `tokenFile`, or makes a random token. */
106
- readonly token?: string | undefined;
107
- /** This file holds the bearer token, when `token` is not given. */
108
- readonly tokenFile?: string | undefined;
109
- /** This function gives the JSON of `GET /status`. Without it, the path gives 404. */
110
- readonly status?: (() => unknown) | undefined;
111
- /** This store keeps the tasks of each session. The default is a new `CrawlTaskStore`. */
112
- readonly tasks?: CrawlTaskStore | undefined;
113
- /** This function writes the messages of the server. The default writes nothing. */
114
- readonly log?: ((line: string) => void) | undefined;
115
- }
116
- /** This is a server that serves MCP over HTTP. */
117
- interface McpHttpServer {
118
- /** This URL is the MCP endpoint, for example `http://127.0.0.1:9307/mcp`. */
119
- readonly url: string;
120
- readonly host: string;
121
- readonly port: number;
122
- /** This value is the bearer token. Give it to the client, and keep it secret. */
123
- readonly token: string;
124
- /** This store keeps the tasks of the long tools. */
125
- readonly tasks: CrawlTaskStore;
126
- /** This method counts the open sessions. */
127
- sessions(): number;
128
- /** This method closes each session, cancels each task and stops the server. A second close does nothing. */
129
- close(): Promise<void>;
130
- }
131
- /**
132
- * This function compares a token with the token of the server in constant time. It compares the
133
- * SHA-256 digests, so the time does not depend on the length or the text of either token.
134
- */
135
- export declare const sameToken: (given: string, token: string) => boolean;
136
- /** This function makes a random token: 32 bytes in base64url, 43 characters. */
137
- export declare const makeToken: () => string;
138
- //#endregion
139
- //#region src/mcp.d.ts
140
- /** These options change the tools of the MCP server. */
141
- export interface CrawlServerOptions {
142
- /**
143
- * These product tokens get their robots.txt rules applied to each fetch, in addition to the token
144
- * of the crawler. The default is `["Claude-User"]`, because an assistant asks for these pages.
145
- */
146
- readonly onBehalfOf?: readonly string[] | undefined;
147
- /**
148
- * The value true lets `user_provided` release held-back text when the client cannot ask the
149
- * user. It is the opt-in of the operator. The default is false. When the client can ask the
150
- * user (MCP elicitation), the server always asks first.
151
- */
152
- readonly allowUserProvided?: boolean | undefined;
153
- /** This text tells why the server renders no page. The result of a page that needs JavaScript shows it. */
154
- readonly renderOff?: string | undefined;
155
- /**
156
- * This store makes the long tools (`crawl_site` and `crawl_discover`) MCP tasks. A client that
157
- * asks for a task gets a task id at once. A client with no tasks gets the result in the call. The
158
- * server over HTTP gives the store. Without it, the long tools are normal tools.
159
- */
160
- readonly tasks?: CrawlTaskStore | undefined;
161
- }
162
- /** These options start the MCP server on the standard input and output. */
163
- export interface StartMcpOptions extends OpenOptions, CrawlServerOptions {
164
- /** This function writes the messages of the server. The default writes them to the standard error. */
165
- readonly log?: ((line: string) => void) | undefined;
166
- }
167
- /**
168
- * This function makes an MCP server with nine tools over one crawler: `crawl_fetch`, `crawl_find`,
169
- * `crawl_read`, `crawl_verify`, `crawl_extract`, `crawl_discover`, `crawl_site`, `crawl_diff` and
170
- * `crawl_watch_list`.
171
- * The server does not connect to a transport. Each fetch applies the robots.txt rules for
172
- * `Claude-User` too, unless `onBehalfOf` gives other tokens. No tool adds a watch or an export,
173
- * because they are actions of the operator.
174
- */
175
- export declare const createMcpServer: (crawler: PoliteCrawler, options?: CrawlServerOptions) => McpServer$1;
176
- /**
177
- * This function starts the MCP server on the standard input and output. The fetches apply the
178
- * robots.txt rules for `Claude-User` too, because an assistant asks for these pages. When the
179
- * default renderer has no browser, the server turns rendering off at start and writes one note.
180
- * The server stops when its standard input closes, or at `SIGINT` or `SIGTERM`.
181
- */
182
- export declare const startMcpServer: (options: StartMcpOptions) => Promise<void>;
183
- /** These options serve the tools over HTTP: the options of the tools, and of the HTTP server. */
184
- export interface StartMcpHttpOptions extends McpHttpOptions, Omit<CrawlServerOptions, "tasks"> {}
185
- /**
186
- * This function serves the tools of a crawler over the Streamable HTTP transport of MCP. Each
187
- * session gets an MCP server with the nine tools of the server on the standard input and output.
188
- * The envelope, the elicitation and the opt-ins are the same. `crawl_site` and `crawl_discover` are MCP tasks.
189
- * Each request needs the bearer token. The function does not close the crawler.
190
- */
191
- export declare const startMcpHttpServer: (crawler: PoliteCrawler, options?: StartMcpHttpOptions) => Promise<McpHttpServer>;
192
- /**
193
- * This host serves the tools of the daemon over HTTP. It is the seam between the daemon and its MCP
194
- * transport: a different MCP host can replace it with `DaemonOptions.mcpHost`.
195
- */
196
- export declare const mcpHttpHost: DaemonMcpHost;
197
- //#endregion
198
- export type { CrawlTaskStoreOptions, McpHttpOptions, McpHttpServer, McpServer, TaskInfo };
199
- //# sourceMappingURL=mcp.d.mts.map