@detiq/crawler 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +49 -0
- package/README.md +11 -0
- package/dist/accessibility-auditor.d.ts +29 -0
- package/dist/accessibility-auditor.js +57 -0
- package/dist/accessibility-auditor.js.map +7 -0
- package/dist/ai-login.d.ts +32 -0
- package/dist/ai-login.js +177 -0
- package/dist/ai-login.js.map +7 -0
- package/dist/auth-token-rotator.d.ts +47 -0
- package/dist/auth-token-rotator.js +58 -0
- package/dist/auth-token-rotator.js.map +7 -0
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +329 -0
- package/dist/auth.js.map +7 -0
- package/dist/browser-extension.d.ts +37 -0
- package/dist/browser-extension.js +113 -0
- package/dist/browser-extension.js.map +7 -0
- package/dist/captcha-solver.d.ts +16 -0
- package/dist/captcha-solver.js +105 -0
- package/dist/captcha-solver.js.map +7 -0
- package/dist/color-contrast.d.ts +30 -0
- package/dist/color-contrast.js +78 -0
- package/dist/color-contrast.js.map +7 -0
- package/dist/content-chunker.d.ts +18 -0
- package/dist/content-chunker.js +152 -0
- package/dist/content-chunker.js.map +7 -0
- package/dist/content-dedup.d.ts +23 -0
- package/dist/content-dedup.js +60 -0
- package/dist/content-dedup.js.map +7 -0
- package/dist/cookie-consent.d.ts +25 -0
- package/dist/cookie-consent.js +78 -0
- package/dist/cookie-consent.js.map +7 -0
- package/dist/cookie-jar.d.ts +20 -0
- package/dist/cookie-jar.js +61 -0
- package/dist/cookie-jar.js.map +7 -0
- package/dist/crawler.d.ts +179 -0
- package/dist/crawler.js +3672 -0
- package/dist/crawler.js.map +7 -0
- package/dist/custom-js-injector.d.ts +42 -0
- package/dist/custom-js-injector.js +50 -0
- package/dist/custom-js-injector.js.map +7 -0
- package/dist/dark-mode-capture.d.ts +23 -0
- package/dist/dark-mode-capture.js +75 -0
- package/dist/dark-mode-capture.js.map +7 -0
- package/dist/dead-link-detector.d.ts +37 -0
- package/dist/dead-link-detector.js +50 -0
- package/dist/dead-link-detector.js.map +7 -0
- package/dist/element-hash.d.ts +9 -0
- package/dist/element-hash.js +9 -0
- package/dist/element-hash.js.map +7 -0
- package/dist/email-otp.d.ts +42 -0
- package/dist/email-otp.js +177 -0
- package/dist/email-otp.js.map +7 -0
- package/dist/engine-router.d.ts +47 -0
- package/dist/engine-router.js +94 -0
- package/dist/engine-router.js.map +7 -0
- package/dist/export/bulk-export.d.ts +26 -0
- package/dist/export/bulk-export.js +76 -0
- package/dist/export/bulk-export.js.map +7 -0
- package/dist/export/vector-export.d.ts +76 -0
- package/dist/export/vector-export.js +85 -0
- package/dist/export/vector-export.js.map +7 -0
- package/dist/extractors/advanced.d.ts +54 -0
- package/dist/extractors/advanced.js +245 -0
- package/dist/extractors/advanced.js.map +7 -0
- package/dist/extractors/artifact-envelope.d.ts +63 -0
- package/dist/extractors/artifact-envelope.js +63 -0
- package/dist/extractors/artifact-envelope.js.map +7 -0
- package/dist/failure-analysis-agent.d.ts +16 -0
- package/dist/failure-analysis-agent.js +40 -0
- package/dist/failure-analysis-agent.js.map +7 -0
- package/dist/feature-flag-detector.d.ts +30 -0
- package/dist/feature-flag-detector.js +55 -0
- package/dist/feature-flag-detector.js.map +7 -0
- package/dist/form-detector.d.ts +34 -0
- package/dist/form-detector.js +58 -0
- package/dist/form-detector.js.map +7 -0
- package/dist/frontier.d.ts +32 -0
- package/dist/frontier.js +83 -0
- package/dist/frontier.js.map +7 -0
- package/dist/gap-pass.d.ts +35 -0
- package/dist/gap-pass.js +81 -0
- package/dist/gap-pass.js.map +7 -0
- package/dist/graphql-detector.d.ts +39 -0
- package/dist/graphql-detector.js +86 -0
- package/dist/graphql-detector.js.map +7 -0
- package/dist/http-engine.d.ts +80 -0
- package/dist/http-engine.js +304 -0
- package/dist/http-engine.js.map +7 -0
- package/dist/incremental-diff.d.ts +59 -0
- package/dist/incremental-diff.js +61 -0
- package/dist/incremental-diff.js.map +7 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +7 -0
- package/dist/input-fuzzer.d.ts +30 -0
- package/dist/input-fuzzer.js +105 -0
- package/dist/input-fuzzer.js.map +7 -0
- package/dist/interactive-probe.d.ts +104 -0
- package/dist/interactive-probe.js +442 -0
- package/dist/interactive-probe.js.map +7 -0
- package/dist/job-queue.d.ts +171 -0
- package/dist/job-queue.js +2256 -0
- package/dist/job-queue.js.map +7 -0
- package/dist/js-coverage.d.ts +29 -0
- package/dist/js-coverage.js +56 -0
- package/dist/js-coverage.js.map +7 -0
- package/dist/js-router-detector.d.ts +17 -0
- package/dist/js-router-detector.js +90 -0
- package/dist/js-router-detector.js.map +7 -0
- package/dist/language-detector.d.ts +22 -0
- package/dist/language-detector.js +57 -0
- package/dist/language-detector.js.map +7 -0
- package/dist/link-classifier.d.ts +23 -0
- package/dist/link-classifier.js +40 -0
- package/dist/link-classifier.js.map +7 -0
- package/dist/live-view.d.ts +36 -0
- package/dist/live-view.js +100 -0
- package/dist/live-view.js.map +7 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +7 -0
- package/dist/markdown-extract.d.ts +8 -0
- package/dist/markdown-extract.js +54 -0
- package/dist/markdown-extract.js.map +7 -0
- package/dist/native-crawler.d.ts +69 -0
- package/dist/native-crawler.js +839 -0
- package/dist/native-crawler.js.map +7 -0
- package/dist/network-observer.d.ts +49 -0
- package/dist/network-observer.js +149 -0
- package/dist/network-observer.js.map +7 -0
- package/dist/network-throttle.d.ts +25 -0
- package/dist/network-throttle.js +34 -0
- package/dist/network-throttle.js.map +7 -0
- package/dist/openapi-generator.d.ts +49 -0
- package/dist/openapi-generator.js +89 -0
- package/dist/openapi-generator.js.map +7 -0
- package/dist/pdf-extractor.d.ts +28 -0
- package/dist/pdf-extractor.js +81 -0
- package/dist/pdf-extractor.js.map +7 -0
- package/dist/performance-metrics.d.ts +53 -0
- package/dist/performance-metrics.js +83 -0
- package/dist/performance-metrics.js.map +7 -0
- package/dist/phash.d.ts +38 -0
- package/dist/phash.js +115 -0
- package/dist/phash.js.map +7 -0
- package/dist/pii-scrubber.d.ts +21 -0
- package/dist/pii-scrubber.js +75 -0
- package/dist/pii-scrubber.js.map +7 -0
- package/dist/playwright-recorder.d.ts +39 -0
- package/dist/playwright-recorder.js +123 -0
- package/dist/playwright-recorder.js.map +7 -0
- package/dist/policies/crawl-safety.d.ts +9 -0
- package/dist/policies/crawl-safety.js +37 -0
- package/dist/policies/crawl-safety.js.map +7 -0
- package/dist/profiles/domain-profile.d.ts +19 -0
- package/dist/profiles/domain-profile.js +140 -0
- package/dist/profiles/domain-profile.js.map +7 -0
- package/dist/proxy.d.ts +50 -0
- package/dist/proxy.js +165 -0
- package/dist/proxy.js.map +7 -0
- package/dist/rate-limiter.d.ts +31 -0
- package/dist/rate-limiter.js +64 -0
- package/dist/rate-limiter.js.map +7 -0
- package/dist/redirect-tracker.d.ts +37 -0
- package/dist/redirect-tracker.js +65 -0
- package/dist/redirect-tracker.js.map +7 -0
- package/dist/redis-frontier.d.ts +48 -0
- package/dist/redis-frontier.js +126 -0
- package/dist/redis-frontier.js.map +7 -0
- package/dist/redis-queue.d.ts +48 -0
- package/dist/redis-queue.js +165 -0
- package/dist/redis-queue.js.map +7 -0
- package/dist/response-cache.d.ts +38 -0
- package/dist/response-cache.js +140 -0
- package/dist/response-cache.js.map +7 -0
- package/dist/responsive-detector.d.ts +29 -0
- package/dist/responsive-detector.js +46 -0
- package/dist/responsive-detector.js.map +7 -0
- package/dist/retry-policy.d.ts +20 -0
- package/dist/retry-policy.js +54 -0
- package/dist/retry-policy.js.map +7 -0
- package/dist/robots-txt.d.ts +9 -0
- package/dist/robots-txt.js +77 -0
- package/dist/robots-txt.js.map +7 -0
- package/dist/screen-reader.d.ts +16 -0
- package/dist/screen-reader.js +46 -0
- package/dist/screen-reader.js.map +7 -0
- package/dist/screenshot-masker.d.ts +28 -0
- package/dist/screenshot-masker.js +62 -0
- package/dist/screenshot-masker.js.map +7 -0
- package/dist/seo-checker.d.ts +33 -0
- package/dist/seo-checker.js +91 -0
- package/dist/seo-checker.js.map +7 -0
- package/dist/session-capture.d.ts +35 -0
- package/dist/session-capture.js +267 -0
- package/dist/session-capture.js.map +7 -0
- package/dist/session-export.d.ts +49 -0
- package/dist/session-export.js +117 -0
- package/dist/session-export.js.map +7 -0
- package/dist/sitemap-writer.d.ts +21 -0
- package/dist/sitemap-writer.js +47 -0
- package/dist/sitemap-writer.js.map +7 -0
- package/dist/sitemap.d.ts +51 -0
- package/dist/sitemap.js +152 -0
- package/dist/sitemap.js.map +7 -0
- package/dist/skyvern.d.ts +56 -0
- package/dist/skyvern.js +129 -0
- package/dist/skyvern.js.map +7 -0
- package/dist/sqs-queue.d.ts +2 -0
- package/dist/sqs-queue.js +47 -0
- package/dist/sqs-queue.js.map +7 -0
- package/dist/sso-token.d.ts +29 -0
- package/dist/sso-token.js +76 -0
- package/dist/sso-token.js.map +7 -0
- package/dist/stealth/human-behavior.d.ts +20 -0
- package/dist/stealth/human-behavior.js +168 -0
- package/dist/stealth/human-behavior.js.map +7 -0
- package/dist/telemetry.d.ts +34 -0
- package/dist/telemetry.js +77 -0
- package/dist/telemetry.js.map +7 -0
- package/dist/test-executor.d.ts +67 -0
- package/dist/test-executor.js +311 -0
- package/dist/test-executor.js.map +7 -0
- package/dist/tls-fingerprint.d.ts +50 -0
- package/dist/tls-fingerprint.js +81 -0
- package/dist/tls-fingerprint.js.map +7 -0
- package/dist/url-pattern-completer.d.ts +28 -0
- package/dist/url-pattern-completer.js +87 -0
- package/dist/url-pattern-completer.js.map +7 -0
- package/dist/visual-diff-verdict.d.ts +40 -0
- package/dist/visual-diff-verdict.js +25 -0
- package/dist/visual-diff-verdict.js.map +7 -0
- package/dist/webhook-notifier.d.ts +30 -0
- package/dist/webhook-notifier.js +76 -0
- package/dist/webhook-notifier.js.map +7 -0
- package/dist/websocket-detector.d.ts +24 -0
- package/dist/websocket-detector.js +60 -0
- package/dist/websocket-detector.js.map +7 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +33 -0
- package/dist/worker.js.map +7 -0
- package/package.json +45 -0
- package/src/accessibility-auditor.ts +102 -0
- package/src/ai-login.ts +218 -0
- package/src/auth-token-rotator.ts +89 -0
- package/src/auth.ts +396 -0
- package/src/browser-extension.ts +186 -0
- package/src/captcha-solver.ts +143 -0
- package/src/color-contrast.ts +116 -0
- package/src/content-chunker.ts +170 -0
- package/src/content-dedup.ts +99 -0
- package/src/cookie-consent.ts +106 -0
- package/src/cookie-jar.ts +91 -0
- package/src/crawler.ts +4305 -0
- package/src/custom-js-injector.ts +93 -0
- package/src/dark-mode-capture.ts +110 -0
- package/src/dead-link-detector.ts +76 -0
- package/src/element-hash.ts +16 -0
- package/src/email-otp.ts +252 -0
- package/src/engine-router.ts +141 -0
- package/src/export/bulk-export.ts +85 -0
- package/src/export/vector-export.ts +160 -0
- package/src/extractors/advanced.ts +283 -0
- package/src/extractors/artifact-envelope.ts +111 -0
- package/src/failure-analysis-agent.ts +49 -0
- package/src/feature-flag-detector.ts +97 -0
- package/src/form-detector.ts +103 -0
- package/src/frontier.test.ts +64 -0
- package/src/frontier.ts +112 -0
- package/src/gap-pass.ts +122 -0
- package/src/graphql-detector.ts +129 -0
- package/src/http-engine.ts +433 -0
- package/src/incremental-diff.ts +121 -0
- package/src/index.ts +35 -0
- package/src/input-fuzzer.ts +129 -0
- package/src/interactive-probe.ts +623 -0
- package/src/job-queue.test.ts +120 -0
- package/src/job-queue.ts +2701 -0
- package/src/js-coverage.ts +87 -0
- package/src/js-router-detector.ts +112 -0
- package/src/language-detector.ts +82 -0
- package/src/link-classifier.ts +65 -0
- package/src/live-view.ts +141 -0
- package/src/logger.ts +9 -0
- package/src/markdown-extract.test.ts +73 -0
- package/src/markdown-extract.ts +76 -0
- package/src/native-crawler.ts +1106 -0
- package/src/network-observer.ts +204 -0
- package/src/network-throttle.ts +69 -0
- package/src/openapi-generator.ts +135 -0
- package/src/pdf-extractor.ts +123 -0
- package/src/performance-metrics.ts +134 -0
- package/src/phash.ts +151 -0
- package/src/pii-scrubber.ts +99 -0
- package/src/playwright-recorder.ts +170 -0
- package/src/policies/crawl-safety.ts +46 -0
- package/src/profiles/domain-profile.ts +167 -0
- package/src/proxy.ts +238 -0
- package/src/rate-limiter.ts +87 -0
- package/src/redirect-tracker.ts +97 -0
- package/src/redis-frontier.ts +143 -0
- package/src/redis-queue.ts +208 -0
- package/src/response-cache.ts +150 -0
- package/src/responsive-detector.ts +92 -0
- package/src/retry-policy.ts +87 -0
- package/src/robots-txt.ts +96 -0
- package/src/screen-reader.ts +89 -0
- package/src/screenshot-masker.ts +98 -0
- package/src/seo-checker.ts +134 -0
- package/src/session-capture.ts +325 -0
- package/src/session-export.test.ts +75 -0
- package/src/session-export.ts +172 -0
- package/src/sitemap-writer.ts +65 -0
- package/src/sitemap.test.ts +131 -0
- package/src/sitemap.ts +224 -0
- package/src/skyvern.ts +199 -0
- package/src/sqs-queue.ts +49 -0
- package/src/sso-token.ts +120 -0
- package/src/stealth/human-behavior.ts +191 -0
- package/src/telemetry.ts +122 -0
- package/src/test-executor.test.ts +72 -0
- package/src/test-executor.ts +424 -0
- package/src/tls-fingerprint.ts +111 -0
- package/src/url-pattern-completer.ts +114 -0
- package/src/visual-diff-verdict.test.ts +80 -0
- package/src/visual-diff-verdict.ts +57 -0
- package/src/webhook-notifier.ts +109 -0
- package/src/websocket-detector.ts +83 -0
- package/src/worker.ts +48 -0
- package/tsconfig.json +13 -0
- package/types/accessibility-auditor.d.ts +29 -0
- package/types/ai-login.d.ts +32 -0
- package/types/auth-token-rotator.d.ts +47 -0
- package/types/auth.d.ts +20 -0
- package/types/browser-extension.d.ts +37 -0
- package/types/bull-worker.d.ts +6 -0
- package/types/captcha-solver.d.ts +16 -0
- package/types/color-contrast.d.ts +30 -0
- package/types/content-chunker.d.ts +18 -0
- package/types/content-dedup.d.ts +23 -0
- package/types/cookie-consent.d.ts +25 -0
- package/types/cookie-jar.d.ts +20 -0
- package/types/crawler.d.ts +179 -0
- package/types/custom-js-injector.d.ts +42 -0
- package/types/dark-mode-capture.d.ts +23 -0
- package/types/dead-link-detector.d.ts +37 -0
- package/types/element-hash.d.ts +9 -0
- package/types/email-otp.d.ts +42 -0
- package/types/engine-router.d.ts +47 -0
- package/types/export/bulk-export.d.ts +26 -0
- package/types/export/vector-export.d.ts +76 -0
- package/types/extractors/advanced.d.ts +54 -0
- package/types/extractors/artifact-envelope.d.ts +63 -0
- package/types/failure-analysis-agent.d.ts +16 -0
- package/types/feature-flag-detector.d.ts +30 -0
- package/types/form-detector.d.ts +34 -0
- package/types/frontier.d.ts +32 -0
- package/types/gap-pass.d.ts +35 -0
- package/types/graphql-detector.d.ts +39 -0
- package/types/http-engine.d.ts +80 -0
- package/types/incremental-diff.d.ts +59 -0
- package/types/index.d.ts +35 -0
- package/types/input-fuzzer.d.ts +30 -0
- package/types/interactive-probe.d.ts +104 -0
- package/types/job-queue.d.ts +171 -0
- package/types/js-coverage.d.ts +29 -0
- package/types/js-router-detector.d.ts +17 -0
- package/types/language-detector.d.ts +22 -0
- package/types/link-classifier.d.ts +23 -0
- package/types/live-view.d.ts +36 -0
- package/types/logger.d.ts +2 -0
- package/types/markdown-extract.d.ts +8 -0
- package/types/native-crawler.d.ts +69 -0
- package/types/network-observer.d.ts +49 -0
- package/types/network-throttle.d.ts +25 -0
- package/types/openapi-generator.d.ts +49 -0
- package/types/pdf-extractor.d.ts +28 -0
- package/types/performance-metrics.d.ts +53 -0
- package/types/phash.d.ts +38 -0
- package/types/pii-scrubber.d.ts +21 -0
- package/types/playwright-recorder.d.ts +39 -0
- package/types/policies/crawl-safety.d.ts +9 -0
- package/types/profiles/domain-profile.d.ts +19 -0
- package/types/proxy.d.ts +50 -0
- package/types/rate-limiter.d.ts +31 -0
- package/types/redirect-tracker.d.ts +37 -0
- package/types/redis-frontier.d.ts +48 -0
- package/types/redis-queue.d.ts +48 -0
- package/types/response-cache.d.ts +38 -0
- package/types/responsive-detector.d.ts +29 -0
- package/types/retry-policy.d.ts +20 -0
- package/types/robots-txt.d.ts +9 -0
- package/types/screen-reader.d.ts +16 -0
- package/types/screenshot-masker.d.ts +28 -0
- package/types/seo-checker.d.ts +33 -0
- package/types/session-capture.d.ts +38 -0
- package/types/session-export.d.ts +49 -0
- package/types/sitemap-writer.d.ts +21 -0
- package/types/sitemap.d.ts +51 -0
- package/types/skyvern.d.ts +56 -0
- package/types/sqs-queue.d.ts +2 -0
- package/types/sso-token.d.ts +29 -0
- package/types/stealth/human-behavior.d.ts +20 -0
- package/types/telemetry.d.ts +34 -0
- package/types/test-executor.d.ts +67 -0
- package/types/tls-fingerprint.d.ts +50 -0
- package/types/url-pattern-completer.d.ts +28 -0
- package/types/visual-diff-verdict.d.ts +40 -0
- package/types/webhook-notifier.d.ts +30 -0
- package/types/websocket-detector.d.ts +24 -0
- package/types/worker.d.ts +1 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DomainProfile } from './profiles/domain-profile.js';
|
|
2
|
+
export type CrawlEngineName = 'http' | 'browser' | 'browser-stealth' | 'stagehand' | 'vision' | 'recorded-session' | 'native';
|
|
3
|
+
export interface CrawlRouteDecisionInput {
|
|
4
|
+
appUrl: string;
|
|
5
|
+
appType?: string | null;
|
|
6
|
+
hasCredentials: boolean;
|
|
7
|
+
hasStorageState: boolean;
|
|
8
|
+
hasAuthToken: boolean;
|
|
9
|
+
hasSkyvern: boolean;
|
|
10
|
+
hasProxy: boolean;
|
|
11
|
+
profile: DomainProfile;
|
|
12
|
+
previousFailureReason?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CrawlRouteDecision {
|
|
15
|
+
engines: CrawlEngineName[];
|
|
16
|
+
reasons: string[];
|
|
17
|
+
confidence: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Per-domain rendering predictor — learns whether a domain requires a full browser
|
|
21
|
+
* or can be served by the fast HTTP engine. Implements adaptive crawlee-python pattern.
|
|
22
|
+
*
|
|
23
|
+
* Strategy:
|
|
24
|
+
* 1. On first encounter of a domain, classify the HTTP response body.
|
|
25
|
+
* 2. Store the decision in memory (Map). All future URLs on same domain skip re-testing.
|
|
26
|
+
* 3. "Requires browser" signals: React/Vue/Angular root with no content, tiny body (<500 chars meaningful text).
|
|
27
|
+
*/
|
|
28
|
+
export declare class RenderingTypePredictor {
|
|
29
|
+
private cache;
|
|
30
|
+
/** Return cached decision if available, undefined if unknown. */
|
|
31
|
+
getCached(url: string): 'http' | 'browser' | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Analyze an HTTP response body to predict if JS rendering is required.
|
|
34
|
+
* Returns 'browser' if JS-heavy signals detected, 'http' otherwise.
|
|
35
|
+
*/
|
|
36
|
+
predict(url: string, htmlBody: string, statusCode: number): 'http' | 'browser';
|
|
37
|
+
/** Force a domain to use a specific engine (e.g. after observing crawl errors). */
|
|
38
|
+
set(url: string, engine: 'http' | 'browser'): void;
|
|
39
|
+
stats(): {
|
|
40
|
+
cached: number;
|
|
41
|
+
browserDomains: string[];
|
|
42
|
+
httpDomains: string[];
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Singleton predictor — shared across crawl jobs in the same process. */
|
|
46
|
+
export declare const renderingPredictor: RenderingTypePredictor;
|
|
47
|
+
export declare function chooseCrawlEngines(input: CrawlRouteDecisionInput): CrawlRouteDecision;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
class RenderingTypePredictor {
|
|
2
|
+
cache = /* @__PURE__ */ new Map();
|
|
3
|
+
/** Return cached decision if available, undefined if unknown. */
|
|
4
|
+
getCached(url) {
|
|
5
|
+
try {
|
|
6
|
+
return this.cache.get(new URL(url).hostname);
|
|
7
|
+
} catch {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Analyze an HTTP response body to predict if JS rendering is required.
|
|
13
|
+
* Returns 'browser' if JS-heavy signals detected, 'http' otherwise.
|
|
14
|
+
*/
|
|
15
|
+
predict(url, htmlBody, statusCode) {
|
|
16
|
+
let decision = "http";
|
|
17
|
+
try {
|
|
18
|
+
if (statusCode >= 400) {
|
|
19
|
+
decision = "browser";
|
|
20
|
+
} else {
|
|
21
|
+
const bodyLen = htmlBody.length;
|
|
22
|
+
const textOnly = htmlBody.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
23
|
+
const wordCount = textOnly.split(/\s+/).filter((w) => w.length > 2).length;
|
|
24
|
+
const hasSpaRoot = /<div[^>]*\s+id=["'](?:root|app|__nuxt|__next|app-root)["'][^>]*>\s*<\/div>/i.test(htmlBody) || /<app-root[^>]*>\s*<\/app-root>/i.test(htmlBody);
|
|
25
|
+
const hasNoscriptFallback = /<noscript[^>]*>[\s\S]{100,}/i.test(htmlBody);
|
|
26
|
+
const tinyBody = bodyLen < 3e3 && wordCount < 50;
|
|
27
|
+
if (hasSpaRoot && wordCount < 100 || tinyBody && !hasNoscriptFallback) {
|
|
28
|
+
decision = "browser";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
this.cache.set(new URL(url).hostname, decision);
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
return decision;
|
|
38
|
+
}
|
|
39
|
+
/** Force a domain to use a specific engine (e.g. after observing crawl errors). */
|
|
40
|
+
set(url, engine) {
|
|
41
|
+
try {
|
|
42
|
+
this.cache.set(new URL(url).hostname, engine);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
stats() {
|
|
47
|
+
const browserDomains = [];
|
|
48
|
+
const httpDomains = [];
|
|
49
|
+
for (const [domain, engine] of this.cache) {
|
|
50
|
+
if (engine === "browser") browserDomains.push(domain);
|
|
51
|
+
else httpDomains.push(domain);
|
|
52
|
+
}
|
|
53
|
+
return { cached: this.cache.size, browserDomains, httpDomains };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const renderingPredictor = new RenderingTypePredictor();
|
|
57
|
+
function chooseCrawlEngines(input) {
|
|
58
|
+
const engines = [];
|
|
59
|
+
const reasons = [];
|
|
60
|
+
const appType = input.appType?.toLowerCase();
|
|
61
|
+
if (appType === "android" || appType === "ios" || appType === "windows" || appType === "macos") {
|
|
62
|
+
return { engines: ["native"], reasons: [`appType=${input.appType}`], confidence: 0.95 };
|
|
63
|
+
}
|
|
64
|
+
if (input.hasStorageState || input.hasAuthToken) {
|
|
65
|
+
engines.push("recorded-session");
|
|
66
|
+
reasons.push("session/token auth available");
|
|
67
|
+
}
|
|
68
|
+
engines.push(input.hasProxy ? "browser-stealth" : "browser");
|
|
69
|
+
reasons.push(input.hasProxy ? "proxy configured; prefer stealth browser path" : "default browser path");
|
|
70
|
+
if (input.hasCredentials || input.profile.industry !== "unknown") {
|
|
71
|
+
engines.push("stagehand");
|
|
72
|
+
reasons.push(input.hasCredentials ? "credentials available for AI login/navigation" : `domain profile=${input.profile.industry}`);
|
|
73
|
+
}
|
|
74
|
+
const hardUi = input.profile.industry === "legacy-enterprise" || /captcha|blocked|canvas|iframe|bot/i.test(input.previousFailureReason ?? "");
|
|
75
|
+
if (input.hasSkyvern && hardUi) {
|
|
76
|
+
engines.push("vision");
|
|
77
|
+
reasons.push("hard UI or previous block detected; vision fallback enabled");
|
|
78
|
+
}
|
|
79
|
+
if (engines.length === 1) {
|
|
80
|
+
engines.unshift("http");
|
|
81
|
+
reasons.push("HTTP/static extraction can be attempted before browser escalation");
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
engines: Array.from(new Set(engines)),
|
|
85
|
+
reasons,
|
|
86
|
+
confidence: input.profile.confidence
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
RenderingTypePredictor,
|
|
91
|
+
chooseCrawlEngines,
|
|
92
|
+
renderingPredictor
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=engine-router.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/engine-router.ts"],
|
|
4
|
+
"sourcesContent": ["import type { DomainProfile } from './profiles/domain-profile.js';\n\nexport type CrawlEngineName =\n | 'http'\n | 'browser'\n | 'browser-stealth'\n | 'stagehand'\n | 'vision'\n | 'recorded-session'\n | 'native';\n\nexport interface CrawlRouteDecisionInput {\n appUrl: string;\n appType?: string | null;\n hasCredentials: boolean;\n hasStorageState: boolean;\n hasAuthToken: boolean;\n hasSkyvern: boolean;\n hasProxy: boolean;\n profile: DomainProfile;\n previousFailureReason?: string;\n}\n\nexport interface CrawlRouteDecision {\n engines: CrawlEngineName[];\n reasons: string[];\n confidence: number;\n}\n\n/**\n * Per-domain rendering predictor \u2014 learns whether a domain requires a full browser\n * or can be served by the fast HTTP engine. Implements adaptive crawlee-python pattern.\n *\n * Strategy:\n * 1. On first encounter of a domain, classify the HTTP response body.\n * 2. Store the decision in memory (Map). All future URLs on same domain skip re-testing.\n * 3. \"Requires browser\" signals: React/Vue/Angular root with no content, tiny body (<500 chars meaningful text).\n */\nexport class RenderingTypePredictor {\n private cache = new Map<string, 'http' | 'browser'>();\n\n /** Return cached decision if available, undefined if unknown. */\n getCached(url: string): 'http' | 'browser' | undefined {\n try {\n return this.cache.get(new URL(url).hostname);\n } catch {\n return undefined;\n }\n }\n\n /**\n * Analyze an HTTP response body to predict if JS rendering is required.\n * Returns 'browser' if JS-heavy signals detected, 'http' otherwise.\n */\n predict(url: string, htmlBody: string, statusCode: number): 'http' | 'browser' {\n let decision: 'http' | 'browser' = 'http';\n try {\n if (statusCode >= 400) {\n decision = 'browser'; // let browser handle error pages\n } else {\n const bodyLen = htmlBody.length;\n // Strip tags to get raw text\n const textOnly = htmlBody.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\n const wordCount = textOnly.split(/\\s+/).filter(w => w.length > 2).length;\n\n // SPA signals: very short text, presence of React/Vue/Angular root markers\n const hasSpaRoot = /<div[^>]*\\s+id=[\"'](?:root|app|__nuxt|__next|app-root)[\"'][^>]*>\\s*<\\/div>/i.test(htmlBody)\n || /<app-root[^>]*>\\s*<\\/app-root>/i.test(htmlBody);\n const hasNoscriptFallback = /<noscript[^>]*>[\\s\\S]{100,}/i.test(htmlBody);\n const tinyBody = bodyLen < 3000 && wordCount < 50;\n\n if ((hasSpaRoot && wordCount < 100) || (tinyBody && !hasNoscriptFallback)) {\n decision = 'browser';\n }\n }\n } catch { /* default http */ }\n\n try {\n this.cache.set(new URL(url).hostname, decision);\n } catch { /* ignore */ }\n return decision;\n }\n\n /** Force a domain to use a specific engine (e.g. after observing crawl errors). */\n set(url: string, engine: 'http' | 'browser'): void {\n try { this.cache.set(new URL(url).hostname, engine); } catch { /* ignore */ }\n }\n\n stats(): { cached: number; browserDomains: string[]; httpDomains: string[] } {\n const browserDomains: string[] = [];\n const httpDomains: string[] = [];\n for (const [domain, engine] of this.cache) {\n if (engine === 'browser') browserDomains.push(domain);\n else httpDomains.push(domain);\n }\n return { cached: this.cache.size, browserDomains, httpDomains };\n }\n}\n\n/** Singleton predictor \u2014 shared across crawl jobs in the same process. */\nexport const renderingPredictor = new RenderingTypePredictor();\n\nexport function chooseCrawlEngines(input: CrawlRouteDecisionInput): CrawlRouteDecision {\n const engines: CrawlEngineName[] = [];\n const reasons: string[] = [];\n\n const appType = input.appType?.toLowerCase();\n if (appType === 'android' || appType === 'ios' || appType === 'windows' || appType === 'macos') {\n return { engines: ['native'], reasons: [`appType=${input.appType}`], confidence: 0.95 };\n }\n\n if (input.hasStorageState || input.hasAuthToken) {\n engines.push('recorded-session');\n reasons.push('session/token auth available');\n }\n\n engines.push(input.hasProxy ? 'browser-stealth' : 'browser');\n reasons.push(input.hasProxy ? 'proxy configured; prefer stealth browser path' : 'default browser path');\n\n if (input.hasCredentials || input.profile.industry !== 'unknown') {\n engines.push('stagehand');\n reasons.push(input.hasCredentials ? 'credentials available for AI login/navigation' : `domain profile=${input.profile.industry}`);\n }\n\n const hardUi = input.profile.industry === 'legacy-enterprise' || /captcha|blocked|canvas|iframe|bot/i.test(input.previousFailureReason ?? '');\n if (input.hasSkyvern && hardUi) {\n engines.push('vision');\n reasons.push('hard UI or previous block detected; vision fallback enabled');\n }\n\n if (engines.length === 1) {\n engines.unshift('http');\n reasons.push('HTTP/static extraction can be attempted before browser escalation');\n }\n\n return {\n engines: Array.from(new Set(engines)),\n reasons,\n confidence: input.profile.confidence,\n };\n}\n"],
|
|
5
|
+
"mappings": "AAsCO,MAAM,uBAAuB;AAAA,EAC1B,QAAQ,oBAAI,IAAgC;AAAA;AAAA,EAGpD,UAAU,KAA6C;AACrD,QAAI;AACF,aAAO,KAAK,MAAM,IAAI,IAAI,IAAI,GAAG,EAAE,QAAQ;AAAA,IAC7C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQ,KAAa,UAAkB,YAAwC;AAC7E,QAAI,WAA+B;AACnC,QAAI;AACF,UAAI,cAAc,KAAK;AACrB,mBAAW;AAAA,MACb,OAAO;AACL,cAAM,UAAU,SAAS;AAEzB,cAAM,WAAW,SAAS,QAAQ,YAAY,GAAG,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC7E,cAAM,YAAY,SAAS,MAAM,KAAK,EAAE,OAAO,OAAK,EAAE,SAAS,CAAC,EAAE;AAGlE,cAAM,aAAa,8EAA8E,KAAK,QAAQ,KACzG,kCAAkC,KAAK,QAAQ;AACpD,cAAM,sBAAsB,+BAA+B,KAAK,QAAQ;AACxE,cAAM,WAAW,UAAU,OAAQ,YAAY;AAE/C,YAAK,cAAc,YAAY,OAAS,YAAY,CAAC,qBAAsB;AACzE,qBAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAAqB;AAE7B,QAAI;AACF,WAAK,MAAM,IAAI,IAAI,IAAI,GAAG,EAAE,UAAU,QAAQ;AAAA,IAChD,QAAQ;AAAA,IAAe;AACvB,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,KAAa,QAAkC;AACjD,QAAI;AAAE,WAAK,MAAM,IAAI,IAAI,IAAI,GAAG,EAAE,UAAU,MAAM;AAAA,IAAG,QAAQ;AAAA,IAAe;AAAA,EAC9E;AAAA,EAEA,QAA6E;AAC3E,UAAM,iBAA2B,CAAC;AAClC,UAAM,cAAwB,CAAC;AAC/B,eAAW,CAAC,QAAQ,MAAM,KAAK,KAAK,OAAO;AACzC,UAAI,WAAW,UAAW,gBAAe,KAAK,MAAM;AAAA,UAC/C,aAAY,KAAK,MAAM;AAAA,IAC9B;AACA,WAAO,EAAE,QAAQ,KAAK,MAAM,MAAM,gBAAgB,YAAY;AAAA,EAChE;AACF;AAGO,MAAM,qBAAqB,IAAI,uBAAuB;AAEtD,SAAS,mBAAmB,OAAoD;AACrF,QAAM,UAA6B,CAAC;AACpC,QAAM,UAAoB,CAAC;AAE3B,QAAM,UAAU,MAAM,SAAS,YAAY;AAC3C,MAAI,YAAY,aAAa,YAAY,SAAS,YAAY,aAAa,YAAY,SAAS;AAC9F,WAAO,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,WAAW,MAAM,OAAO,EAAE,GAAG,YAAY,KAAK;AAAA,EACxF;AAEA,MAAI,MAAM,mBAAmB,MAAM,cAAc;AAC/C,YAAQ,KAAK,kBAAkB;AAC/B,YAAQ,KAAK,8BAA8B;AAAA,EAC7C;AAEA,UAAQ,KAAK,MAAM,WAAW,oBAAoB,SAAS;AAC3D,UAAQ,KAAK,MAAM,WAAW,kDAAkD,sBAAsB;AAEtG,MAAI,MAAM,kBAAkB,MAAM,QAAQ,aAAa,WAAW;AAChE,YAAQ,KAAK,WAAW;AACxB,YAAQ,KAAK,MAAM,iBAAiB,kDAAkD,kBAAkB,MAAM,QAAQ,QAAQ,EAAE;AAAA,EAClI;AAEA,QAAM,SAAS,MAAM,QAAQ,aAAa,uBAAuB,qCAAqC,KAAK,MAAM,yBAAyB,EAAE;AAC5I,MAAI,MAAM,cAAc,QAAQ;AAC9B,YAAQ,KAAK,QAAQ;AACrB,YAAQ,KAAK,6DAA6D;AAAA,EAC5E;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,QAAQ,MAAM;AACtB,YAAQ,KAAK,mEAAmE;AAAA,EAClF;AAEA,SAAO;AAAA,IACL,SAAS,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC;AAAA,IACpC;AAAA,IACA,YAAY,MAAM,QAAQ;AAAA,EAC5B;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk export — export crawled screens to CSV / JSON / Markdown digest.
|
|
3
|
+
*
|
|
4
|
+
* Outputs:
|
|
5
|
+
* - CSV: url, name, wordCount, screenshotPath, markdownPath, createdAt — for spreadsheet analysis
|
|
6
|
+
* - JSON: full screen records array — for API integration
|
|
7
|
+
* - Markdown digest: concatenated markdown of all screens — for LLM context windows
|
|
8
|
+
*/
|
|
9
|
+
export interface ExportScreen {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
url: string;
|
|
13
|
+
projectId: string;
|
|
14
|
+
wordCount?: number;
|
|
15
|
+
screenshotPath?: string | null;
|
|
16
|
+
markdownPath?: string | null;
|
|
17
|
+
markdown?: string | null;
|
|
18
|
+
createdAt?: Date;
|
|
19
|
+
updatedAt?: Date;
|
|
20
|
+
}
|
|
21
|
+
export declare function exportToCsv(screens: ExportScreen[], outputPath: string): Promise<void>;
|
|
22
|
+
export declare function exportToJson(screens: ExportScreen[], outputPath: string): Promise<void>;
|
|
23
|
+
export declare function exportToMarkdownDigest(screens: ExportScreen[], outputPath: string, opts?: {
|
|
24
|
+
readMarkdownFromPath?: boolean;
|
|
25
|
+
maxScreens?: number;
|
|
26
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
function escapeCsv(val) {
|
|
4
|
+
if (val.includes(",") || val.includes('"') || val.includes("\n")) {
|
|
5
|
+
return `"${val.replace(/"/g, '""')}"`;
|
|
6
|
+
}
|
|
7
|
+
return val;
|
|
8
|
+
}
|
|
9
|
+
async function exportToCsv(screens, outputPath) {
|
|
10
|
+
const headers = ["id", "name", "url", "projectId", "wordCount", "screenshotPath", "markdownPath", "createdAt"];
|
|
11
|
+
const rows = screens.map((s) => [
|
|
12
|
+
s.id,
|
|
13
|
+
s.name,
|
|
14
|
+
s.url,
|
|
15
|
+
s.projectId,
|
|
16
|
+
String(s.wordCount ?? 0),
|
|
17
|
+
s.screenshotPath ?? "",
|
|
18
|
+
s.markdownPath ?? "",
|
|
19
|
+
s.createdAt?.toISOString() ?? ""
|
|
20
|
+
].map(escapeCsv).join(","));
|
|
21
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
22
|
+
await fs.writeFile(outputPath, [headers.join(","), ...rows].join("\n"), "utf8");
|
|
23
|
+
}
|
|
24
|
+
async function exportToJson(screens, outputPath) {
|
|
25
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
26
|
+
const data = screens.map((s) => ({
|
|
27
|
+
id: s.id,
|
|
28
|
+
name: s.name,
|
|
29
|
+
url: s.url,
|
|
30
|
+
projectId: s.projectId,
|
|
31
|
+
wordCount: s.wordCount,
|
|
32
|
+
screenshotPath: s.screenshotPath,
|
|
33
|
+
markdownPath: s.markdownPath,
|
|
34
|
+
createdAt: s.createdAt?.toISOString(),
|
|
35
|
+
updatedAt: s.updatedAt?.toISOString()
|
|
36
|
+
}));
|
|
37
|
+
await fs.writeFile(outputPath, JSON.stringify(data, null, 2), "utf8");
|
|
38
|
+
}
|
|
39
|
+
async function exportToMarkdownDigest(screens, outputPath, opts = {}) {
|
|
40
|
+
const { readMarkdownFromPath = true, maxScreens = 200 } = opts;
|
|
41
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
42
|
+
const handle = await fs.open(outputPath, "w");
|
|
43
|
+
try {
|
|
44
|
+
await handle.write(`# ZeTa Crawl Digest
|
|
45
|
+
|
|
46
|
+
Exported: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
47
|
+
Screens: ${Math.min(screens.length, maxScreens)}
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
`);
|
|
52
|
+
for (const screen of screens.slice(0, maxScreens)) {
|
|
53
|
+
let md = screen.markdown ?? "";
|
|
54
|
+
if (!md && readMarkdownFromPath && screen.markdownPath) {
|
|
55
|
+
md = await fs.readFile(screen.markdownPath, "utf8").catch(() => "");
|
|
56
|
+
}
|
|
57
|
+
await handle.write(`## ${screen.name}
|
|
58
|
+
|
|
59
|
+
**URL:** ${screen.url}
|
|
60
|
+
|
|
61
|
+
${md.trim()}
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
} finally {
|
|
68
|
+
await handle.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
exportToCsv,
|
|
73
|
+
exportToJson,
|
|
74
|
+
exportToMarkdownDigest
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=bulk-export.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/export/bulk-export.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Bulk export \u2014 export crawled screens to CSV / JSON / Markdown digest.\n *\n * Outputs:\n * - CSV: url, name, wordCount, screenshotPath, markdownPath, createdAt \u2014 for spreadsheet analysis\n * - JSON: full screen records array \u2014 for API integration\n * - Markdown digest: concatenated markdown of all screens \u2014 for LLM context windows\n */\n\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport interface ExportScreen {\n id: string;\n name: string;\n url: string;\n projectId: string;\n wordCount?: number;\n screenshotPath?: string | null;\n markdownPath?: string | null;\n markdown?: string | null;\n createdAt?: Date;\n updatedAt?: Date;\n}\n\nfunction escapeCsv(val: string): string {\n if (val.includes(',') || val.includes('\"') || val.includes('\\n')) {\n return `\"${val.replace(/\"/g, '\"\"')}\"`;\n }\n return val;\n}\n\nexport async function exportToCsv(screens: ExportScreen[], outputPath: string): Promise<void> {\n const headers = ['id', 'name', 'url', 'projectId', 'wordCount', 'screenshotPath', 'markdownPath', 'createdAt'];\n const rows = screens.map(s => [\n s.id, s.name, s.url, s.projectId,\n String(s.wordCount ?? 0),\n s.screenshotPath ?? '',\n s.markdownPath ?? '',\n s.createdAt?.toISOString() ?? '',\n ].map(escapeCsv).join(','));\n\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, [headers.join(','), ...rows].join('\\n'), 'utf8');\n}\n\nexport async function exportToJson(screens: ExportScreen[], outputPath: string): Promise<void> {\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n const data = screens.map(s => ({\n id: s.id,\n name: s.name,\n url: s.url,\n projectId: s.projectId,\n wordCount: s.wordCount,\n screenshotPath: s.screenshotPath,\n markdownPath: s.markdownPath,\n createdAt: s.createdAt?.toISOString(),\n updatedAt: s.updatedAt?.toISOString(),\n }));\n await fs.writeFile(outputPath, JSON.stringify(data, null, 2), 'utf8');\n}\n\nexport async function exportToMarkdownDigest(\n screens: ExportScreen[],\n outputPath: string,\n opts: { readMarkdownFromPath?: boolean; maxScreens?: number } = {},\n): Promise<void> {\n const { readMarkdownFromPath = true, maxScreens = 200 } = opts;\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n\n const handle = await fs.open(outputPath, 'w');\n try {\n await handle.write(`# ZeTa Crawl Digest\\n\\nExported: ${new Date().toISOString()}\\nScreens: ${Math.min(screens.length, maxScreens)}\\n\\n---\\n\\n`);\n\n for (const screen of screens.slice(0, maxScreens)) {\n let md = screen.markdown ?? '';\n if (!md && readMarkdownFromPath && screen.markdownPath) {\n md = await fs.readFile(screen.markdownPath, 'utf8').catch(() => '');\n }\n await handle.write(`## ${screen.name}\\n\\n**URL:** ${screen.url}\\n\\n${md.trim()}\\n\\n---\\n\\n`);\n }\n } finally {\n await handle.close();\n }\n}\n"],
|
|
5
|
+
"mappings": "AASA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAetB,SAAS,UAAU,KAAqB;AACtC,MAAI,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,IAAI,GAAG;AAChE,WAAO,IAAI,IAAI,QAAQ,MAAM,IAAI,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAEA,eAAsB,YAAY,SAAyB,YAAmC;AAC5F,QAAM,UAAU,CAAC,MAAM,QAAQ,OAAO,aAAa,aAAa,kBAAkB,gBAAgB,WAAW;AAC7G,QAAM,OAAO,QAAQ,IAAI,OAAK;AAAA,IAC5B,EAAE;AAAA,IAAI,EAAE;AAAA,IAAM,EAAE;AAAA,IAAK,EAAE;AAAA,IACvB,OAAO,EAAE,aAAa,CAAC;AAAA,IACvB,EAAE,kBAAkB;AAAA,IACpB,EAAE,gBAAgB;AAAA,IAClB,EAAE,WAAW,YAAY,KAAK;AAAA,EAChC,EAAE,IAAI,SAAS,EAAE,KAAK,GAAG,CAAC;AAE1B,QAAM,GAAG,MAAM,KAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,QAAM,GAAG,UAAU,YAAY,CAAC,QAAQ,KAAK,GAAG,GAAG,GAAG,IAAI,EAAE,KAAK,IAAI,GAAG,MAAM;AAChF;AAEA,eAAsB,aAAa,SAAyB,YAAmC;AAC7F,QAAM,GAAG,MAAM,KAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,QAAM,OAAO,QAAQ,IAAI,QAAM;AAAA,IAC7B,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,KAAK,EAAE;AAAA,IACP,WAAW,EAAE;AAAA,IACb,WAAW,EAAE;AAAA,IACb,gBAAgB,EAAE;AAAA,IAClB,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE,WAAW,YAAY;AAAA,IACpC,WAAW,EAAE,WAAW,YAAY;AAAA,EACtC,EAAE;AACF,QAAM,GAAG,UAAU,YAAY,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,MAAM;AACtE;AAEA,eAAsB,uBACpB,SACA,YACA,OAAgE,CAAC,GAClD;AACf,QAAM,EAAE,uBAAuB,MAAM,aAAa,IAAI,IAAI;AAC1D,QAAM,GAAG,MAAM,KAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAE5D,QAAM,SAAS,MAAM,GAAG,KAAK,YAAY,GAAG;AAC5C,MAAI;AACF,UAAM,OAAO,MAAM;AAAA;AAAA,aAAoC,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,WAAc,KAAK,IAAI,QAAQ,QAAQ,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA,CAAa;AAE9I,eAAW,UAAU,QAAQ,MAAM,GAAG,UAAU,GAAG;AACjD,UAAI,KAAK,OAAO,YAAY;AAC5B,UAAI,CAAC,MAAM,wBAAwB,OAAO,cAAc;AACtD,aAAK,MAAM,GAAG,SAAS,OAAO,cAAc,MAAM,EAAE,MAAM,MAAM,EAAE;AAAA,MACpE;AACA,YAAM,OAAO,MAAM,MAAM,OAAO,IAAI;AAAA;AAAA,WAAgB,OAAO,GAAG;AAAA;AAAA,EAAO,GAAG,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA,CAAa;AAAA,IAC7F;AAAA,EACF,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector DB export — convert crawled screens to embedding-ready chunks.
|
|
3
|
+
*
|
|
4
|
+
* Produces a JSONL file where each line is one chunk ready for upsert into
|
|
5
|
+
* Pinecone, Qdrant, Weaviate, or any vector store. The embedding itself is
|
|
6
|
+
* NOT generated here (requires an embedding model at the user's end) — we
|
|
7
|
+
* produce the text chunks + metadata, and the caller embeds them.
|
|
8
|
+
*
|
|
9
|
+
* Format per line (JSONL):
|
|
10
|
+
* {
|
|
11
|
+
* "id": "<screenId>-<chunkIndex>",
|
|
12
|
+
* "text": "<chunk text>",
|
|
13
|
+
* "metadata": {
|
|
14
|
+
* "screenId": "...",
|
|
15
|
+
* "projectId": "...",
|
|
16
|
+
* "tenantId": "...",
|
|
17
|
+
* "url": "...",
|
|
18
|
+
* "screenName": "...",
|
|
19
|
+
* "chunkIndex": 0,
|
|
20
|
+
* "totalChunks": 3,
|
|
21
|
+
* "crawledAt": "2026-...",
|
|
22
|
+
* "wordCount": 142
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
export interface VectorChunk {
|
|
27
|
+
id: string;
|
|
28
|
+
text: string;
|
|
29
|
+
metadata: {
|
|
30
|
+
screenId: string;
|
|
31
|
+
projectId: string;
|
|
32
|
+
tenantId: string;
|
|
33
|
+
url: string;
|
|
34
|
+
screenName: string;
|
|
35
|
+
chunkIndex: number;
|
|
36
|
+
totalChunks: number;
|
|
37
|
+
crawledAt: string;
|
|
38
|
+
wordCount: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface ScreenRecord {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
url: string;
|
|
45
|
+
projectId: string;
|
|
46
|
+
tenantId: string;
|
|
47
|
+
markdown?: string | null;
|
|
48
|
+
markdownPath?: string | null;
|
|
49
|
+
updatedAt?: Date;
|
|
50
|
+
}
|
|
51
|
+
export declare function exportScreensToVectorJsonl(screens: ScreenRecord[], outputPath: string, opts?: {
|
|
52
|
+
maxChunkWords?: number;
|
|
53
|
+
readMarkdownFromPath?: boolean;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
totalChunks: number;
|
|
56
|
+
skipped: number;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Pinecone-compatible upsert batch format.
|
|
60
|
+
* Call this after generating embeddings to produce the final upsert payload.
|
|
61
|
+
*/
|
|
62
|
+
export declare function toPineconeUpsert(chunks: VectorChunk[], embeddings: number[][], namespace?: string): Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
values: number[];
|
|
65
|
+
metadata: VectorChunk['metadata'];
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Qdrant-compatible point format.
|
|
69
|
+
*/
|
|
70
|
+
export declare function toQdrantPoints(chunks: VectorChunk[], embeddings: number[][]): Array<{
|
|
71
|
+
id: string;
|
|
72
|
+
vector: number[];
|
|
73
|
+
payload: VectorChunk['metadata'] & {
|
|
74
|
+
text: string;
|
|
75
|
+
};
|
|
76
|
+
}>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
function splitIntoChunks(text, maxChunkWords = 300) {
|
|
4
|
+
const paragraphs = text.split(/\n{2,}/).filter((p) => p.trim().length > 0);
|
|
5
|
+
const chunks = [];
|
|
6
|
+
let current = "";
|
|
7
|
+
let wordCount = 0;
|
|
8
|
+
for (const para of paragraphs) {
|
|
9
|
+
const words = para.split(/\s+/).length;
|
|
10
|
+
if (wordCount + words > maxChunkWords && current.length > 0) {
|
|
11
|
+
chunks.push(current.trim());
|
|
12
|
+
current = "";
|
|
13
|
+
wordCount = 0;
|
|
14
|
+
}
|
|
15
|
+
current += (current ? "\n\n" : "") + para;
|
|
16
|
+
wordCount += words;
|
|
17
|
+
}
|
|
18
|
+
if (current.trim()) chunks.push(current.trim());
|
|
19
|
+
return chunks.length > 0 ? chunks : [text.slice(0, 2e3)];
|
|
20
|
+
}
|
|
21
|
+
async function exportScreensToVectorJsonl(screens, outputPath, opts = {}) {
|
|
22
|
+
const { maxChunkWords = 300, readMarkdownFromPath = true } = opts;
|
|
23
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
24
|
+
const handle = await fs.open(outputPath, "w");
|
|
25
|
+
let totalChunks = 0;
|
|
26
|
+
let skipped = 0;
|
|
27
|
+
try {
|
|
28
|
+
for (const screen of screens) {
|
|
29
|
+
let markdown = screen.markdown ?? "";
|
|
30
|
+
if (!markdown && readMarkdownFromPath && screen.markdownPath) {
|
|
31
|
+
try {
|
|
32
|
+
markdown = await fs.readFile(screen.markdownPath, "utf8");
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!markdown || markdown.trim().length < 20) {
|
|
37
|
+
skipped++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const textChunks = splitIntoChunks(markdown, maxChunkWords);
|
|
41
|
+
for (let i = 0; i < textChunks.length; i++) {
|
|
42
|
+
const chunk = {
|
|
43
|
+
id: `${screen.id}-${i}`,
|
|
44
|
+
text: textChunks[i],
|
|
45
|
+
metadata: {
|
|
46
|
+
screenId: screen.id,
|
|
47
|
+
projectId: screen.projectId,
|
|
48
|
+
tenantId: screen.tenantId,
|
|
49
|
+
url: screen.url,
|
|
50
|
+
screenName: screen.name,
|
|
51
|
+
chunkIndex: i,
|
|
52
|
+
totalChunks: textChunks.length,
|
|
53
|
+
crawledAt: screen.updatedAt?.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
54
|
+
wordCount: textChunks[i].split(/\s+/).length
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
await handle.write(JSON.stringify(chunk) + "\n");
|
|
58
|
+
totalChunks++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
await handle.close();
|
|
63
|
+
}
|
|
64
|
+
return { totalChunks, skipped };
|
|
65
|
+
}
|
|
66
|
+
function toPineconeUpsert(chunks, embeddings, namespace = "zeta-crawl") {
|
|
67
|
+
return chunks.map((chunk, i) => ({
|
|
68
|
+
id: chunk.id,
|
|
69
|
+
values: embeddings[i],
|
|
70
|
+
metadata: chunk.metadata
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
function toQdrantPoints(chunks, embeddings) {
|
|
74
|
+
return chunks.map((chunk, i) => ({
|
|
75
|
+
id: chunk.id,
|
|
76
|
+
vector: embeddings[i],
|
|
77
|
+
payload: { ...chunk.metadata, text: chunk.text }
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
exportScreensToVectorJsonl,
|
|
82
|
+
toPineconeUpsert,
|
|
83
|
+
toQdrantPoints
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=vector-export.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/export/vector-export.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Vector DB export \u2014 convert crawled screens to embedding-ready chunks.\n *\n * Produces a JSONL file where each line is one chunk ready for upsert into\n * Pinecone, Qdrant, Weaviate, or any vector store. The embedding itself is\n * NOT generated here (requires an embedding model at the user's end) \u2014 we\n * produce the text chunks + metadata, and the caller embeds them.\n *\n * Format per line (JSONL):\n * {\n * \"id\": \"<screenId>-<chunkIndex>\",\n * \"text\": \"<chunk text>\",\n * \"metadata\": {\n * \"screenId\": \"...\",\n * \"projectId\": \"...\",\n * \"tenantId\": \"...\",\n * \"url\": \"...\",\n * \"screenName\": \"...\",\n * \"chunkIndex\": 0,\n * \"totalChunks\": 3,\n * \"crawledAt\": \"2026-...\",\n * \"wordCount\": 142\n * }\n * }\n */\n\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport interface VectorChunk {\n id: string;\n text: string;\n metadata: {\n screenId: string;\n projectId: string;\n tenantId: string;\n url: string;\n screenName: string;\n chunkIndex: number;\n totalChunks: number;\n crawledAt: string;\n wordCount: number;\n };\n}\n\nexport interface ScreenRecord {\n id: string;\n name: string;\n url: string;\n projectId: string;\n tenantId: string;\n markdown?: string | null;\n markdownPath?: string | null;\n updatedAt?: Date;\n}\n\nfunction splitIntoChunks(text: string, maxChunkWords = 300): string[] {\n const paragraphs = text.split(/\\n{2,}/).filter(p => p.trim().length > 0);\n const chunks: string[] = [];\n let current = '';\n let wordCount = 0;\n\n for (const para of paragraphs) {\n const words = para.split(/\\s+/).length;\n if (wordCount + words > maxChunkWords && current.length > 0) {\n chunks.push(current.trim());\n current = '';\n wordCount = 0;\n }\n current += (current ? '\\n\\n' : '') + para;\n wordCount += words;\n }\n if (current.trim()) chunks.push(current.trim());\n return chunks.length > 0 ? chunks : [text.slice(0, 2000)];\n}\n\nexport async function exportScreensToVectorJsonl(\n screens: ScreenRecord[],\n outputPath: string,\n opts: { maxChunkWords?: number; readMarkdownFromPath?: boolean } = {},\n): Promise<{ totalChunks: number; skipped: number }> {\n const { maxChunkWords = 300, readMarkdownFromPath = true } = opts;\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n\n const handle = await fs.open(outputPath, 'w');\n let totalChunks = 0;\n let skipped = 0;\n\n try {\n for (const screen of screens) {\n let markdown = screen.markdown ?? '';\n\n if (!markdown && readMarkdownFromPath && screen.markdownPath) {\n try {\n markdown = await fs.readFile(screen.markdownPath, 'utf8');\n } catch { /* file missing \u2014 skip */ }\n }\n\n if (!markdown || markdown.trim().length < 20) {\n skipped++;\n continue;\n }\n\n const textChunks = splitIntoChunks(markdown, maxChunkWords);\n for (let i = 0; i < textChunks.length; i++) {\n const chunk: VectorChunk = {\n id: `${screen.id}-${i}`,\n text: textChunks[i],\n metadata: {\n screenId: screen.id,\n projectId: screen.projectId,\n tenantId: screen.tenantId,\n url: screen.url,\n screenName: screen.name,\n chunkIndex: i,\n totalChunks: textChunks.length,\n crawledAt: screen.updatedAt?.toISOString() ?? new Date().toISOString(),\n wordCount: textChunks[i].split(/\\s+/).length,\n },\n };\n await handle.write(JSON.stringify(chunk) + '\\n');\n totalChunks++;\n }\n }\n } finally {\n await handle.close();\n }\n\n return { totalChunks, skipped };\n}\n\n/**\n * Pinecone-compatible upsert batch format.\n * Call this after generating embeddings to produce the final upsert payload.\n */\nexport function toPineconeUpsert(\n chunks: VectorChunk[],\n embeddings: number[][],\n namespace = 'zeta-crawl',\n): Array<{ id: string; values: number[]; metadata: VectorChunk['metadata'] }> {\n return chunks.map((chunk, i) => ({\n id: chunk.id,\n values: embeddings[i],\n metadata: chunk.metadata,\n }));\n}\n\n/**\n * Qdrant-compatible point format.\n */\nexport function toQdrantPoints(\n chunks: VectorChunk[],\n embeddings: number[][],\n): Array<{ id: string; vector: number[]; payload: VectorChunk['metadata'] & { text: string } }> {\n return chunks.map((chunk, i) => ({\n id: chunk.id,\n vector: embeddings[i],\n payload: { ...chunk.metadata, text: chunk.text },\n }));\n}\n"],
|
|
5
|
+
"mappings": "AA0BA,YAAY,QAAQ;AACpB,YAAY,UAAU;AA6BtB,SAAS,gBAAgB,MAAc,gBAAgB,KAAe;AACpE,QAAM,aAAa,KAAK,MAAM,QAAQ,EAAE,OAAO,OAAK,EAAE,KAAK,EAAE,SAAS,CAAC;AACvE,QAAM,SAAmB,CAAC;AAC1B,MAAI,UAAU;AACd,MAAI,YAAY;AAEhB,aAAW,QAAQ,YAAY;AAC7B,UAAM,QAAQ,KAAK,MAAM,KAAK,EAAE;AAChC,QAAI,YAAY,QAAQ,iBAAiB,QAAQ,SAAS,GAAG;AAC3D,aAAO,KAAK,QAAQ,KAAK,CAAC;AAC1B,gBAAU;AACV,kBAAY;AAAA,IACd;AACA,gBAAY,UAAU,SAAS,MAAM;AACrC,iBAAa;AAAA,EACf;AACA,MAAI,QAAQ,KAAK,EAAG,QAAO,KAAK,QAAQ,KAAK,CAAC;AAC9C,SAAO,OAAO,SAAS,IAAI,SAAS,CAAC,KAAK,MAAM,GAAG,GAAI,CAAC;AAC1D;AAEA,eAAsB,2BACpB,SACA,YACA,OAAmE,CAAC,GACjB;AACnD,QAAM,EAAE,gBAAgB,KAAK,uBAAuB,KAAK,IAAI;AAC7D,QAAM,GAAG,MAAM,KAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAE5D,QAAM,SAAS,MAAM,GAAG,KAAK,YAAY,GAAG;AAC5C,MAAI,cAAc;AAClB,MAAI,UAAU;AAEd,MAAI;AACF,eAAW,UAAU,SAAS;AAC5B,UAAI,WAAW,OAAO,YAAY;AAElC,UAAI,CAAC,YAAY,wBAAwB,OAAO,cAAc;AAC5D,YAAI;AACF,qBAAW,MAAM,GAAG,SAAS,OAAO,cAAc,MAAM;AAAA,QAC1D,QAAQ;AAAA,QAA4B;AAAA,MACtC;AAEA,UAAI,CAAC,YAAY,SAAS,KAAK,EAAE,SAAS,IAAI;AAC5C;AACA;AAAA,MACF;AAEA,YAAM,aAAa,gBAAgB,UAAU,aAAa;AAC1D,eAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,cAAM,QAAqB;AAAA,UACzB,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC;AAAA,UACrB,MAAM,WAAW,CAAC;AAAA,UAClB,UAAU;AAAA,YACR,UAAU,OAAO;AAAA,YACjB,WAAW,OAAO;AAAA,YAClB,UAAU,OAAO;AAAA,YACjB,KAAK,OAAO;AAAA,YACZ,YAAY,OAAO;AAAA,YACnB,YAAY;AAAA,YACZ,aAAa,WAAW;AAAA,YACxB,WAAW,OAAO,WAAW,YAAY,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,YACrE,WAAW,WAAW,CAAC,EAAE,MAAM,KAAK,EAAE;AAAA,UACxC;AAAA,QACF;AACA,cAAM,OAAO,MAAM,KAAK,UAAU,KAAK,IAAI,IAAI;AAC/C;AAAA,MACF;AAAA,IACF;AAAA,EACF,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AAEA,SAAO,EAAE,aAAa,QAAQ;AAChC;AAMO,SAAS,iBACd,QACA,YACA,YAAY,cACgE;AAC5E,SAAO,OAAO,IAAI,CAAC,OAAO,OAAO;AAAA,IAC/B,IAAI,MAAM;AAAA,IACV,QAAQ,WAAW,CAAC;AAAA,IACpB,UAAU,MAAM;AAAA,EAClB,EAAE;AACJ;AAKO,SAAS,eACd,QACA,YAC8F;AAC9F,SAAO,OAAO,IAAI,CAAC,OAAO,OAAO;AAAA,IAC/B,IAAI,MAAM;AAAA,IACV,QAAQ,WAAW,CAAC;AAAA,IACpB,SAAS,EAAE,GAAG,MAAM,UAAU,MAAM,MAAM,KAAK;AAAA,EACjD,EAAE;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface ExtractionResult {
|
|
2
|
+
matches: string[];
|
|
3
|
+
count: number;
|
|
4
|
+
method: string;
|
|
5
|
+
}
|
|
6
|
+
export type TableRow = Record<string, string>;
|
|
7
|
+
export interface PageMetadata {
|
|
8
|
+
title: string | null;
|
|
9
|
+
description: string | null;
|
|
10
|
+
keywords: string | null;
|
|
11
|
+
ogTitle: string | null;
|
|
12
|
+
ogDescription: string | null;
|
|
13
|
+
ogImage: string | null;
|
|
14
|
+
ogUrl: string | null;
|
|
15
|
+
ogType: string | null;
|
|
16
|
+
twitterCard: string | null;
|
|
17
|
+
twitterTitle: string | null;
|
|
18
|
+
twitterDescription: string | null;
|
|
19
|
+
canonical: string | null;
|
|
20
|
+
robots: string | null;
|
|
21
|
+
jsonLd: object[];
|
|
22
|
+
schemaTypes: string[];
|
|
23
|
+
lang: string | null;
|
|
24
|
+
}
|
|
25
|
+
export declare function extractByCssSelector(html: string, selector: string, attr?: string): ExtractionResult;
|
|
26
|
+
export declare function extractByXPath(html: string, xpath: string): ExtractionResult;
|
|
27
|
+
export declare function extractByRegex(text: string, pattern: string, flags?: string): ExtractionResult;
|
|
28
|
+
export declare function extractTables(html: string): TableRow[][];
|
|
29
|
+
export declare function extractMetadata(html: string, url: string): PageMetadata;
|
|
30
|
+
export interface ProductData {
|
|
31
|
+
name: string | null;
|
|
32
|
+
description: string | null;
|
|
33
|
+
brand: string | null;
|
|
34
|
+
sku: string | null;
|
|
35
|
+
price: number | null;
|
|
36
|
+
priceCurrency: string | null;
|
|
37
|
+
availability: string | null;
|
|
38
|
+
rating: number | null;
|
|
39
|
+
reviewCount: number | null;
|
|
40
|
+
image: string | null;
|
|
41
|
+
url: string | null;
|
|
42
|
+
}
|
|
43
|
+
export declare function extractStructuredProduct(jsonLd: object[]): ProductData | null;
|
|
44
|
+
export declare function extractImages(html: string, baseUrl: string): Array<{
|
|
45
|
+
src: string;
|
|
46
|
+
alt: string;
|
|
47
|
+
width?: number;
|
|
48
|
+
height?: number;
|
|
49
|
+
}>;
|
|
50
|
+
export declare function extractMediaFiles(html: string, baseUrl: string): Array<{
|
|
51
|
+
url: string;
|
|
52
|
+
type: 'image' | 'video' | 'audio' | 'document';
|
|
53
|
+
tag: string;
|
|
54
|
+
}>;
|