@nitpicker/crawler 0.14.0 → 0.16.0

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 (155) hide show
  1. package/lib/archive/archive.d.ts +136 -10
  2. package/lib/archive/archive.js +147 -9
  3. package/lib/archive/body-hash/compute-body-hash.d.ts +22 -0
  4. package/lib/archive/body-hash/compute-body-hash.js +31 -0
  5. package/lib/archive/body-hash/extract-body.d.ts +18 -0
  6. package/lib/archive/body-hash/extract-body.js +29 -0
  7. package/lib/archive/body-hash/mask-dynamic-ids.d.ts +33 -0
  8. package/lib/archive/body-hash/mask-dynamic-ids.js +44 -0
  9. package/lib/archive/body-hash/normalize-url-like-strings.d.ts +16 -0
  10. package/lib/archive/body-hash/normalize-url-like-strings.js +19 -0
  11. package/lib/archive/cache/clear-archive-cache-entry.d.ts +19 -0
  12. package/lib/archive/cache/clear-archive-cache-entry.js +25 -0
  13. package/lib/archive/cache/clear-archive-cache-root.d.ts +26 -0
  14. package/lib/archive/cache/clear-archive-cache-root.js +32 -0
  15. package/lib/archive/cache/list-archive-cache-entries.d.ts +23 -0
  16. package/lib/archive/cache/list-archive-cache-entries.js +125 -0
  17. package/lib/archive/cache/path-exists.d.ts +9 -0
  18. package/lib/archive/cache/path-exists.js +18 -0
  19. package/lib/archive/cache/types.d.ts +33 -0
  20. package/lib/archive/cache/types.js +1 -0
  21. package/lib/archive/create-adjunct-tables.d.ts +14 -0
  22. package/lib/archive/create-adjunct-tables.js +169 -0
  23. package/lib/archive/create-entity-tables.js +21 -1
  24. package/lib/archive/database.d.ts +67 -2
  25. package/lib/archive/database.js +92 -3
  26. package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +1 -0
  27. package/lib/archive/db-ops/_shared/compress-payload.d.ts +26 -0
  28. package/lib/archive/db-ops/_shared/compress-payload.js +30 -0
  29. package/lib/archive/db-ops/_shared/create-write-ref-caches.js +1 -0
  30. package/lib/archive/db-ops/_shared/types.d.ts +2 -0
  31. package/lib/archive/db-ops/_shared/upsert-json-ref.js +3 -3
  32. package/lib/archive/db-ops/analysis/replace-page-templates.d.ts +12 -3
  33. package/lib/archive/db-ops/analysis/replace-page-templates.js +35 -3
  34. package/lib/archive/db-ops/analysis/types.d.ts +61 -0
  35. package/lib/archive/db-ops/analysis/types.js +1 -0
  36. package/lib/archive/db-ops/console-logs/compute-console-log-hash.d.ts +19 -0
  37. package/lib/archive/db-ops/console-logs/compute-console-log-hash.js +30 -0
  38. package/lib/archive/db-ops/console-logs/replace-console-logs.d.ts +34 -0
  39. package/lib/archive/db-ops/console-logs/replace-console-logs.js +123 -0
  40. package/lib/archive/db-ops/console-logs/stringify-console-log-args.d.ts +16 -0
  41. package/lib/archive/db-ops/console-logs/stringify-console-log-args.js +26 -0
  42. package/lib/archive/db-ops/console-logs/types.d.ts +38 -0
  43. package/lib/archive/db-ops/console-logs/types.js +1 -0
  44. package/lib/archive/db-ops/console-logs/upsert-console-log-item.d.ts +29 -0
  45. package/lib/archive/db-ops/console-logs/upsert-console-log-item.js +52 -0
  46. package/lib/archive/db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.d.ts +18 -0
  47. package/lib/archive/db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.js +23 -0
  48. package/lib/archive/db-ops/dedupe-cap/finalize-dedupe-cap-event.d.ts +12 -0
  49. package/lib/archive/db-ops/dedupe-cap/finalize-dedupe-cap-event.js +15 -0
  50. package/lib/archive/db-ops/dedupe-cap/insert-dedupe-cap-event.d.ts +14 -0
  51. package/lib/archive/db-ops/dedupe-cap/insert-dedupe-cap-event.js +30 -0
  52. package/lib/archive/db-ops/dedupe-cap/list-dedupe-cap-shape-keys.d.ts +21 -0
  53. package/lib/archive/db-ops/dedupe-cap/list-dedupe-cap-shape-keys.js +27 -0
  54. package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +11 -0
  55. package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +21 -4
  56. package/lib/archive/db-ops/inventory/record-inventory-run.js +1 -0
  57. package/lib/archive/db-ops/lifecycle/init.d.ts +11 -2
  58. package/lib/archive/db-ops/lifecycle/init.js +21 -2
  59. package/lib/archive/db-ops/outages/close-network-outage.d.ts +12 -0
  60. package/lib/archive/db-ops/outages/close-network-outage.js +15 -0
  61. package/lib/archive/db-ops/outages/close-stale-open-network-outages.d.ts +26 -0
  62. package/lib/archive/db-ops/outages/close-stale-open-network-outages.js +42 -0
  63. package/lib/archive/db-ops/outages/compute-outage-clamp-timestamp.d.ts +26 -0
  64. package/lib/archive/db-ops/outages/compute-outage-clamp-timestamp.js +31 -0
  65. package/lib/archive/db-ops/outages/insert-network-outage.d.ts +14 -0
  66. package/lib/archive/db-ops/outages/insert-network-outage.js +29 -0
  67. package/lib/archive/db-ops/outages/list-network-outages.d.ts +28 -0
  68. package/lib/archive/db-ops/outages/list-network-outages.js +42 -0
  69. package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +13 -0
  70. package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +35 -7
  71. package/lib/archive/db-ops/pages/write/update-page.js +12 -0
  72. package/lib/archive/db-ops/pages/write/write-page-html-blob.js +6 -6
  73. package/lib/archive/filesystem/output-binary.d.ts +12 -0
  74. package/lib/archive/filesystem/output-binary.js +17 -0
  75. package/lib/archive/get-failed-page-messages.d.ts +20 -8
  76. package/lib/archive/get-failed-page-messages.js +29 -15
  77. package/lib/archive/migrate-content-items-alias-of-id.d.ts +47 -0
  78. package/lib/archive/migrate-content-items-alias-of-id.js +58 -0
  79. package/lib/archive/migrate-inventory-runs-invalid-skipped.d.ts +11 -0
  80. package/lib/archive/migrate-inventory-runs-invalid-skipped.js +24 -0
  81. package/lib/archive/migrate-page-meta-body-hash.d.ts +39 -0
  82. package/lib/archive/migrate-page-meta-body-hash.js +52 -0
  83. package/lib/archive/migrate-page-meta-console-error-count.d.ts +22 -0
  84. package/lib/archive/migrate-page-meta-console-error-count.js +34 -0
  85. package/lib/archive/page.d.ts +1 -1
  86. package/lib/archive/types.d.ts +60 -2
  87. package/lib/archive/url-alias/compute-tier-a-alias-key.d.ts +31 -0
  88. package/lib/archive/url-alias/compute-tier-a-alias-key.js +36 -0
  89. package/lib/archive/url-alias/compute-tier-b-alias-key.d.ts +19 -0
  90. package/lib/archive/url-alias/compute-tier-b-alias-key.js +30 -0
  91. package/lib/archive/url-alias/format-alias-key.d.ts +13 -0
  92. package/lib/archive/url-alias/format-alias-key.js +14 -0
  93. package/lib/archive/url-alias/parse-alias-key-parts.d.ts +15 -0
  94. package/lib/archive/url-alias/parse-alias-key-parts.js +40 -0
  95. package/lib/archive/url-alias/types.d.ts +7 -0
  96. package/lib/archive/url-alias/types.js +1 -0
  97. package/lib/classify-error-kind.d.ts +1 -0
  98. package/lib/classify-error-kind.js +14 -0
  99. package/lib/crawler/assert-chrome-installed.d.ts +24 -0
  100. package/lib/crawler/assert-chrome-installed.js +43 -0
  101. package/lib/crawler/choose-probe-host.d.ts +24 -0
  102. package/lib/crawler/choose-probe-host.js +38 -0
  103. package/lib/crawler/clear-dns-burned-host-cache.js +2 -0
  104. package/lib/crawler/crawler.d.ts +12 -0
  105. package/lib/crawler/crawler.js +496 -29
  106. package/lib/crawler/decode-auth-credential.d.ts +29 -0
  107. package/lib/crawler/decode-auth-credential.js +39 -0
  108. package/lib/crawler/dedupe/compute-meta-signature.d.ts +30 -0
  109. package/lib/crawler/dedupe/compute-meta-signature.js +0 -0
  110. package/lib/crawler/dedupe/compute-shape-key.d.ts +37 -0
  111. package/lib/crawler/dedupe/compute-shape-key.js +56 -0
  112. package/lib/crawler/dedupe/dedupe-cap-tracker.d.ts +84 -0
  113. package/lib/crawler/dedupe/dedupe-cap-tracker.js +185 -0
  114. package/lib/crawler/dedupe/is-predicted-content-duplicate.d.ts +24 -0
  115. package/lib/crawler/dedupe/is-predicted-content-duplicate.js +26 -0
  116. package/lib/crawler/dedupe/is-shape-capped.d.ts +10 -0
  117. package/lib/crawler/dedupe/is-shape-capped.js +12 -0
  118. package/lib/crawler/dedupe/resolve-og-url-mismatch.d.ts +31 -0
  119. package/lib/crawler/dedupe/resolve-og-url-mismatch.js +40 -0
  120. package/lib/crawler/dedupe/types.d.ts +42 -0
  121. package/lib/crawler/dedupe/types.js +1 -0
  122. package/lib/crawler/dns-burned-host-burn-timestamps.d.ts +17 -0
  123. package/lib/crawler/dns-burned-host-burn-timestamps.js +17 -0
  124. package/lib/crawler/evict-network-classified-destination-cache-entries.d.ts +26 -0
  125. package/lib/crawler/evict-network-classified-destination-cache-entries.js +34 -0
  126. package/lib/crawler/evict-outage-tainted-dns-burns.d.ts +23 -0
  127. package/lib/crawler/evict-outage-tainted-dns-burns.js +26 -0
  128. package/lib/crawler/fetch-destination.js +14 -2
  129. package/lib/crawler/generate-predicted-urls.d.ts +12 -0
  130. package/lib/crawler/generate-predicted-urls.js +33 -2
  131. package/lib/crawler/is-puppeteer-fallback-candidate.js +3 -0
  132. package/lib/crawler/network-gate.d.ts +49 -0
  133. package/lib/crawler/network-gate.js +78 -0
  134. package/lib/crawler/network-outage-detector.d.ts +51 -0
  135. package/lib/crawler/network-outage-detector.js +81 -0
  136. package/lib/crawler/network-outage-summary-counter.d.ts +21 -0
  137. package/lib/crawler/network-outage-summary-counter.js +18 -0
  138. package/lib/crawler/probe-network.d.ts +26 -0
  139. package/lib/crawler/probe-network.js +21 -0
  140. package/lib/crawler/types.d.ts +185 -1
  141. package/lib/crawler-orchestrator.d.ts +74 -18
  142. package/lib/crawler-orchestrator.js +267 -27
  143. package/lib/crawler.d.ts +22 -0
  144. package/lib/crawler.js +18 -0
  145. package/lib/is-within-outage-window.d.ts +49 -0
  146. package/lib/is-within-outage-window.js +33 -0
  147. package/lib/network-related-error-kinds.d.ts +23 -0
  148. package/lib/network-related-error-kinds.js +28 -0
  149. package/lib/permanent-error-kinds.d.ts +9 -4
  150. package/lib/permanent-error-kinds.js +10 -4
  151. package/lib/types.d.ts +11 -2
  152. package/lib/utils/array/each-splitted.d.ts +1 -1
  153. package/lib/utils/compute-file-sha256.d.ts +17 -17
  154. package/lib/utils/compute-file-sha256.js +18 -47
  155. package/package.json +3 -3
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `ErrorKind`s that plausibly indicate trouble with the operator's own
3
+ * network path rather than a genuinely dead or misconfigured target site.
4
+ *
5
+ * Single source of truth shared by two consumers that must agree on
6
+ * exactly the same set:
7
+ *
8
+ * - `NetworkOutageDetector` — only these kinds contribute to the sliding
9
+ * window that detects a suspect outage.
10
+ * - `evict-network-classified-destination-cache-entries.ts` — on recovery,
11
+ * only `destinationCache` entries whose cached `Error` classifies into
12
+ * one of these kinds are evicted (a cached `tls` or `client-blocked`
13
+ * failure is a site-specific fact and must survive an outage recovery
14
+ * unrelated to it).
15
+ *
16
+ * `dns` is included despite `permanent-error-kinds.ts` treating it as a
17
+ * permanent, site-specific verdict in isolation — that classification is
18
+ * exactly what outage detection exists to override when the surrounding
19
+ * evidence (many hosts, tight time window) points to the local network
20
+ * instead.
21
+ */
22
+ export const NETWORK_RELATED_ERROR_KINDS = new Set([
23
+ 'dns',
24
+ 'dns-transient',
25
+ 'local-network',
26
+ 'connection-timeout',
27
+ 'connection-reset',
28
+ ]);
@@ -7,11 +7,12 @@ import type { ErrorKind } from './types.js';
7
7
  * Used by `resetFailedPages` to exclude pages whose latest recorded error
8
8
  * falls in this set, so `--retry-failed` actually converges: without the
9
9
  * exclusion, NXDOMAIN / TLS mismatch / `ERR_BLOCKED_BY_CLIENT` /
10
- * `ECONNREFUSED` / HTTP parse-error pages would be reset to pending on every
11
- * iteration, the crawler would re-attempt them, they would fail again the
12
- * same way, and the retry-target count would stay constant forever.
10
+ * `ECONNREFUSED` / HTTP parse-error / redirect-loop pages would be reset to
11
+ * pending on every iteration, the crawler would re-attempt them, they would
12
+ * fail again the same way, and the retry-target count would stay constant
13
+ * forever.
13
14
  *
14
- * Why these five and not others:
15
+ * Why these six and not others:
15
16
  * - **dns** — `ENOTFOUND` / `ERR_NAME_NOT_RESOLVED` are authoritative DNS
16
17
  * answers; the host is gone (or never existed). EAI_AGAIN is split out as
17
18
  * `dns-transient` precisely so it is NOT in this set.
@@ -28,6 +29,10 @@ import type { ErrorKind } from './types.js';
28
29
  * the listener; either no process is listening on the port or its accept
29
30
  * queue rejected the connection. Either way the answer is final until the
30
31
  * server operator intervenes.
32
+ * - **redirect-loop** — `Maximum number of redirects exceeded` /
33
+ * `ERR_TOO_MANY_REDIRECTS` means the site's own redirect chain never
34
+ * terminates; the exact same chain is served on every future fetch until
35
+ * the site operator fixes it.
31
36
  *
32
37
  * Notably absent (intentionally retryable):
33
38
  * - `connection-reset` / `connection-timeout` — could be middlebox or
@@ -6,11 +6,12 @@
6
6
  * Used by `resetFailedPages` to exclude pages whose latest recorded error
7
7
  * falls in this set, so `--retry-failed` actually converges: without the
8
8
  * exclusion, NXDOMAIN / TLS mismatch / `ERR_BLOCKED_BY_CLIENT` /
9
- * `ECONNREFUSED` / HTTP parse-error pages would be reset to pending on every
10
- * iteration, the crawler would re-attempt them, they would fail again the
11
- * same way, and the retry-target count would stay constant forever.
9
+ * `ECONNREFUSED` / HTTP parse-error / redirect-loop pages would be reset to
10
+ * pending on every iteration, the crawler would re-attempt them, they would
11
+ * fail again the same way, and the retry-target count would stay constant
12
+ * forever.
12
13
  *
13
- * Why these five and not others:
14
+ * Why these six and not others:
14
15
  * - **dns** — `ENOTFOUND` / `ERR_NAME_NOT_RESOLVED` are authoritative DNS
15
16
  * answers; the host is gone (or never existed). EAI_AGAIN is split out as
16
17
  * `dns-transient` precisely so it is NOT in this set.
@@ -27,6 +28,10 @@
27
28
  * the listener; either no process is listening on the port or its accept
28
29
  * queue rejected the connection. Either way the answer is final until the
29
30
  * server operator intervenes.
31
+ * - **redirect-loop** — `Maximum number of redirects exceeded` /
32
+ * `ERR_TOO_MANY_REDIRECTS` means the site's own redirect chain never
33
+ * terminates; the exact same chain is served on every future fetch until
34
+ * the site operator fixes it.
30
35
  *
31
36
  * Notably absent (intentionally retryable):
32
37
  * - `connection-reset` / `connection-timeout` — could be middlebox or
@@ -45,4 +50,5 @@ export const PERMANENT_ERROR_KINDS = new Set([
45
50
  'client-blocked',
46
51
  'parse-error',
47
52
  'connection-refused',
53
+ 'redirect-loop',
48
54
  ]);
package/lib/types.d.ts CHANGED
@@ -10,7 +10,7 @@ import type { CrawlerError, PageData } from './utils/types/types.js';
10
10
  * documented per-property rather than scattered across the two emit sites.
11
11
  */
12
12
  export interface InventoryRunAggregates {
13
- /** Total non-empty lines in the input list (= `inventoryUrls.length` before any filtering). Stored verbatim as `inventory_runs.total_lines`. */
13
+ /** `inventoryUrls.length` as received by `CrawlerOrchestrator.inventory` — the CLI (`inventoryCrawl`) has already warned-and-dropped unparseable-URL lines before this point, so this counts valid URLs, not raw source-file lines. Stored verbatim as `inventory_runs.total_lines`. */
14
14
  inventoryUrlsCount: number;
15
15
  /** Number of novel URLs classified as HTML and queued for render. Stored as `new_pages` (excludes anchor-discovered descendants — those add later via the crawler graph and are NOT counted here). */
16
16
  htmlSeedsCount: number;
@@ -32,6 +32,14 @@ export interface InventoryRunAggregates {
32
32
  * `source_file_sha256` will be `NULL`.
33
33
  */
34
34
  sourceFileSha256: string | null;
35
+ /**
36
+ * Number of source-file lines the CLI warned-and-dropped for failing
37
+ * URL validation, before `inventoryUrlsCount` was ever counted. Stored
38
+ * verbatim as `inventory_runs.invalid_skipped`. `null` for programmatic
39
+ * callers that built `inventoryUrls` in-memory — there is no source
40
+ * file, so no line was ever dropped as invalid.
41
+ */
42
+ invalidSkipped: number | null;
35
43
  }
36
44
  /**
37
45
  * Coarse cause of a crawl/scrape failure.
@@ -58,6 +66,7 @@ export interface InventoryRunAggregates {
58
66
  * | `local-network` | **yes** | no | local machine's network is unreachable / changed (WiFi, sleep, ICMP-unreachable, …) |
59
67
  * | `parse-error` | mostly persistent | no | HTTP response could not be parsed (proxy, garbage, MITM) |
60
68
  * | `client-blocked` | persistent (per browser) | no | Chromium-side `ERR_BLOCKED_BY_*` family — the browser actively refused the request (ad/tracker heuristics, CSP, CORP, administrator block list, …) |
69
+ * | `redirect-loop` | no | no | the redirect chain exceeded `follow-redirects`' `maxRedirects` limit — the site's own redirect configuration never converges |
61
70
  * | `protocol` | yes | no | puppeteer protocol layer (frame detached, target closed, …) |
62
71
  * | `timeout` | yes | no | puppeteer navigation timeout or HEAD pre-flight race timeout (`Timeout: <url>`) |
63
72
  * | `unknown` | unknown | no | catch-all for messages no matcher recognised |
@@ -82,7 +91,7 @@ export interface InventoryRunAggregates {
82
91
  * entry), or a recoverable URL never reaching the puppeteer fallback (no
83
92
  * PUPPETEER_FALLBACK entry).
84
93
  */
85
- export type ErrorKind = 'dns' | 'dns-transient' | 'connection-refused' | 'connection-reset' | 'connection-timeout' | 'tls' | 'local-network' | 'parse-error' | 'client-blocked' | 'timeout' | 'protocol' | 'unknown';
94
+ export type ErrorKind = 'dns' | 'dns-transient' | 'connection-refused' | 'connection-reset' | 'connection-timeout' | 'tls' | 'local-network' | 'parse-error' | 'client-blocked' | 'redirect-loop' | 'timeout' | 'protocol' | 'unknown';
86
95
  /**
87
96
  * Event map for the `CrawlerOrchestrator` class.
88
97
  *
@@ -7,4 +7,4 @@
7
7
  * @param callback - A function to invoke on each chunk. May be synchronous or asynchronous.
8
8
  * @returns A promise that resolves when all chunk callbacks have completed.
9
9
  */
10
- export declare function eachSplitted<T>(a: T[], count: number, callback: (items: T[]) => void | Promise<void>): Promise<void>;
10
+ export declare function eachSplitted<T>(a: readonly T[], count: number, callback: (items: T[]) => void | Promise<void>): Promise<void>;
@@ -1,24 +1,24 @@
1
1
  /**
2
- * Compute the SHA-256 digest of a file's bytes, streaming so memory stays
3
- * O(1) regardless of file size.
2
+ * Compute the SHA-256 digest of an in-memory byte buffer.
4
3
  *
5
- * Used by `CrawlerOrchestrator.inventory` to fingerprint the source
6
- * URL list `.txt` and store the digest on the `inventory_runs` row. The
7
- * digest is the content-identity key for "was this exact list applied
8
- * before" recorded for operator audit; nothing dedupes on it at write
9
- * time.
4
+ * Used by `CrawlerOrchestrator.inventory` to fingerprint the source URL
5
+ * list the digest is both recorded on the `inventory_runs` audit row
6
+ * (the content-identity key for "was this exact list applied before") and
7
+ * used as the file name under which the raw list is archived
8
+ * (`Archive.saveInventorySourceList`).
10
9
  *
11
- * Returns `null` instead of throwing when the file cannot be read (e.g.
12
- * vanished mid-run, permissions issue) so the inventory run's audit row
13
- * can still be written with a NULL digest. Hashing failure is an audit
14
- * loss, not a correctness failure the actual ingestion has already
15
- * succeeded by the time this is called.
16
- * @param filePath - Absolute or relative path to the file to hash.
17
- * @returns Lower-case hex digest (64 chars), or `null` if reading failed.
10
+ * Takes an already-read `Buffer` rather than a file path: the CLI reads the
11
+ * source file exactly once (`inventoryCrawl`) and derives the digest, the
12
+ * parsed URL list, and the archived copy all from that single buffer. A
13
+ * separate read-then-hash pass would let the file change between the two
14
+ * reads and desync the archived bytes from the hash naming them.
15
+ * @param bytes - The exact bytes to hash.
16
+ * @returns Lower-case hex digest (64 chars).
18
17
  * @example
19
18
  * ```ts
20
- * const sha = await computeFileSha256('/tmp/list.txt');
21
- * if (sha) console.log(sha); // 64-char hex string
19
+ * const bytes = await fs.readFile('/tmp/list.txt');
20
+ * const sha = computeFileSha256(bytes);
21
+ * console.log(sha); // 64-char hex string
22
22
  * ```
23
23
  */
24
- export declare function computeFileSha256(filePath: string): Promise<string | null>;
24
+ export declare function computeFileSha256(bytes: Buffer): string;
@@ -1,56 +1,27 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { createReadStream } from 'node:fs';
3
- import { crawlerLog } from '../debug.js';
4
2
  /**
5
- * Compute the SHA-256 digest of a file's bytes, streaming so memory stays
6
- * O(1) regardless of file size.
3
+ * Compute the SHA-256 digest of an in-memory byte buffer.
7
4
  *
8
- * Used by `CrawlerOrchestrator.inventory` to fingerprint the source
9
- * URL list `.txt` and store the digest on the `inventory_runs` row. The
10
- * digest is the content-identity key for "was this exact list applied
11
- * before" recorded for operator audit; nothing dedupes on it at write
12
- * time.
5
+ * Used by `CrawlerOrchestrator.inventory` to fingerprint the source URL
6
+ * list the digest is both recorded on the `inventory_runs` audit row
7
+ * (the content-identity key for "was this exact list applied before") and
8
+ * used as the file name under which the raw list is archived
9
+ * (`Archive.saveInventorySourceList`).
13
10
  *
14
- * Returns `null` instead of throwing when the file cannot be read (e.g.
15
- * vanished mid-run, permissions issue) so the inventory run's audit row
16
- * can still be written with a NULL digest. Hashing failure is an audit
17
- * loss, not a correctness failure the actual ingestion has already
18
- * succeeded by the time this is called.
19
- * @param filePath - Absolute or relative path to the file to hash.
20
- * @returns Lower-case hex digest (64 chars), or `null` if reading failed.
11
+ * Takes an already-read `Buffer` rather than a file path: the CLI reads the
12
+ * source file exactly once (`inventoryCrawl`) and derives the digest, the
13
+ * parsed URL list, and the archived copy all from that single buffer. A
14
+ * separate read-then-hash pass would let the file change between the two
15
+ * reads and desync the archived bytes from the hash naming them.
16
+ * @param bytes - The exact bytes to hash.
17
+ * @returns Lower-case hex digest (64 chars).
21
18
  * @example
22
19
  * ```ts
23
- * const sha = await computeFileSha256('/tmp/list.txt');
24
- * if (sha) console.log(sha); // 64-char hex string
20
+ * const bytes = await fs.readFile('/tmp/list.txt');
21
+ * const sha = computeFileSha256(bytes);
22
+ * console.log(sha); // 64-char hex string
25
23
  * ```
26
24
  */
27
- export async function computeFileSha256(filePath) {
28
- return await new Promise((resolve) => {
29
- const hash = createHash('sha256');
30
- const stream = createReadStream(filePath);
31
- let settled = false;
32
- const settle = (value) => {
33
- if (settled) {
34
- return;
35
- }
36
- settled = true;
37
- resolve(value);
38
- };
39
- stream.on('data', (chunk) => hash.update(chunk));
40
- stream.on('end', () => settle(hash.digest('hex')));
41
- stream.on('error', (error) => {
42
- // Trace the failure so operators inspecting `--inventory`
43
- // audit rows with `source_file_sha256 = NULL` can recover
44
- // the underlying cause via `DEBUG=Nitpicker:Crawler:*`.
45
- crawlerLog('compute-file-sha256 failed for %s: %s', filePath, error.message);
46
- settle(null);
47
- });
48
- // `'close'` is the last-resort settler. If the stream is
49
- // destroyed externally between `'data'` and `'end'` (e.g. a
50
- // test or signal handler), neither `'end'` nor `'error'` may
51
- // fire — leaving the promise pending forever. Falling back to
52
- // `null` matches the documented contract ("hashing failure
53
- // yields null").
54
- stream.on('close', () => settle(null));
55
- });
25
+ export function computeFileSha256(bytes) {
26
+ return createHash('sha256').update(bytes).digest('hex');
56
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitpicker/crawler",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Web crawler engine with headless browser rendering and archive storage",
5
5
  "author": "D-ZERO",
6
6
  "license": "Apache-2.0",
@@ -27,7 +27,7 @@
27
27
  "clean": "tsc --build --clean"
28
28
  },
29
29
  "dependencies": {
30
- "@d-zero/beholder": "4.0.0",
30
+ "@d-zero/beholder": "4.1.0",
31
31
  "@d-zero/dealer": "1.9.4",
32
32
  "@d-zero/fs": "0.2.6",
33
33
  "@d-zero/shared": "0.22.2",
@@ -48,5 +48,5 @@
48
48
  "@types/tar": "7.0.87",
49
49
  "@types/unzipper": "0.10.11"
50
50
  },
51
- "gitHead": "98222c2bbce55ab6d253787a251f040b51c7a9a0"
51
+ "gitHead": "bef8b6d48e3ca5167fee643d6aba8644a065df4a"
52
52
  }