mcp-scraper 0.40.2 → 0.40.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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/api-server.cjs +1517 -236
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +155 -87
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +6 -5
  14. package/dist/bin/mcp-stdio-server.js.map +1 -1
  15. package/dist/bin/paa-harvest.cjs +31 -9
  16. package/dist/bin/paa-harvest.cjs.map +1 -1
  17. package/dist/bin/paa-harvest.js +4 -4
  18. package/dist/{chunk-XORPNO3Z.js → chunk-27DUCRAZ.js} +2 -2
  19. package/dist/{chunk-44HZLHDV.js → chunk-5UN33CGU.js} +38 -1
  20. package/dist/chunk-5UN33CGU.js.map +1 -0
  21. package/dist/{chunk-X2LKCX6H.js → chunk-6J57U6HA.js} +3 -3
  22. package/dist/{chunk-LP6E462I.js → chunk-6SZ52BQ5.js} +49 -101
  23. package/dist/chunk-6SZ52BQ5.js.map +1 -0
  24. package/dist/chunk-BBI7RGOT.js +99 -0
  25. package/dist/chunk-BBI7RGOT.js.map +1 -0
  26. package/dist/{chunk-NVXNEOUQ.js → chunk-CS4HE6QY.js} +47 -11
  27. package/dist/chunk-CS4HE6QY.js.map +1 -0
  28. package/dist/{chunk-3PIWJS6Y.js → chunk-EHES33KB.js} +2 -2
  29. package/dist/{chunk-GUVKHCKE.js → chunk-IQTT7CAB.js} +94 -3
  30. package/dist/chunk-IQTT7CAB.js.map +1 -0
  31. package/dist/{chunk-7XBBFBYY.js → chunk-JVIW4GK2.js} +44 -12
  32. package/dist/chunk-JVIW4GK2.js.map +1 -0
  33. package/dist/chunk-XU4ZLIB2.js +7 -0
  34. package/dist/chunk-XU4ZLIB2.js.map +1 -0
  35. package/dist/{db-W3CP562I.js → db-LVVU6NOK.js} +16 -2
  36. package/dist/{extract-bundle-K4PG3RZJ.js → extract-bundle-SKXEDG4Z.js} +4 -4
  37. package/dist/index.cjs +31 -9
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.js +4 -4
  40. package/dist/{location-data-repository-RLQX6SNM.js → location-data-repository-NBTCOA7B.js} +3 -3
  41. package/dist/{server-KM3CFWCF.js → server-W76RUYD3.js} +1108 -66
  42. package/dist/server-W76RUYD3.js.map +1 -0
  43. package/dist/{site-extract-repository-2SMMFKKL.js → site-extract-repository-6GX72U4N.js} +4 -4
  44. package/dist/{worker-FXAGFYOE.js → worker-5RWLFSAJ.js} +27 -28
  45. package/dist/worker-5RWLFSAJ.js.map +1 -0
  46. package/package.json +1 -1
  47. package/dist/chunk-44HZLHDV.js.map +0 -1
  48. package/dist/chunk-7XBBFBYY.js.map +0 -1
  49. package/dist/chunk-GUVKHCKE.js.map +0 -1
  50. package/dist/chunk-LP6E462I.js.map +0 -1
  51. package/dist/chunk-NVXNEOUQ.js.map +0 -1
  52. package/dist/chunk-ZUJLSICT.js +0 -7
  53. package/dist/chunk-ZUJLSICT.js.map +0 -1
  54. package/dist/server-KM3CFWCF.js.map +0 -1
  55. package/dist/worker-FXAGFYOE.js.map +0 -1
  56. /package/dist/{chunk-XORPNO3Z.js.map → chunk-27DUCRAZ.js.map} +0 -0
  57. /package/dist/{chunk-X2LKCX6H.js.map → chunk-6J57U6HA.js.map} +0 -0
  58. /package/dist/{chunk-3PIWJS6Y.js.map → chunk-EHES33KB.js.map} +0 -0
  59. /package/dist/{db-W3CP562I.js.map → db-LVVU6NOK.js.map} +0 -0
  60. /package/dist/{extract-bundle-K4PG3RZJ.js.map → extract-bundle-SKXEDG4Z.js.map} +0 -0
  61. /package/dist/{location-data-repository-RLQX6SNM.js.map → location-data-repository-NBTCOA7B.js.map} +0 -0
  62. /package/dist/{site-extract-repository-2SMMFKKL.js.map → site-extract-repository-6GX72U4N.js.map} +0 -0
@@ -0,0 +1,99 @@
1
+ import {
2
+ publicErrorMessage
3
+ } from "./chunk-5UN33CGU.js";
4
+
5
+ // src/api/outbound-sanitize.ts
6
+ var KEY_RENAMES = {
7
+ kernel: "browserRuntime"
8
+ };
9
+ var SANITIZED_VALUE_KEYS = /error|message/i;
10
+ var INTERNAL_DIAGNOSTIC_KEYS = /* @__PURE__ */ new Set([
11
+ "kernel_session_id",
12
+ "browser_session_id",
13
+ "kernelSessionId",
14
+ "browserSessionId",
15
+ "sessionId",
16
+ "kernel_delete_started",
17
+ "session_cleanup_started",
18
+ "kernelDeleteStarted",
19
+ "sessionCleanupStarted",
20
+ "kernel_delete_succeeded",
21
+ "session_cleanup_succeeded",
22
+ "kernelDeleteSucceeded",
23
+ "sessionCleanupSucceeded",
24
+ "kernel_delete_error",
25
+ "session_cleanup_error",
26
+ "kernelDeleteError",
27
+ "sessionCleanupError",
28
+ "kernelProxyId",
29
+ "proxyId",
30
+ "requestedProxyIdSuffix",
31
+ "retrievedProxyIdSuffix",
32
+ "provider",
33
+ "providerName",
34
+ "service",
35
+ "serviceName",
36
+ "vendor",
37
+ "vendorName",
38
+ "organization",
39
+ "organizationId",
40
+ "accountId"
41
+ ]);
42
+ var VENDOR_URL_KEYS = /* @__PURE__ */ new Set([
43
+ "hosted_url",
44
+ "hostedUrl",
45
+ "live_view_url",
46
+ "liveViewUrl",
47
+ "cdp_ws_url",
48
+ "cdpWsUrl",
49
+ "browser_live_view_url"
50
+ ]);
51
+ var VENDOR_URL_RE = /\b(?:wss?|https?):\/\/[^\s"'<>]*\bonkernel\.com[^\s"'<>]*/gi;
52
+ function redactVendorUrls(value) {
53
+ return value.replace(VENDOR_URL_RE, "[browser-service]");
54
+ }
55
+ function sanitizeOutboundDiagnostics(value, parentKey = "") {
56
+ if (typeof value === "string") {
57
+ let out = redactVendorUrls(value);
58
+ if (SANITIZED_VALUE_KEYS.test(parentKey)) {
59
+ out = publicErrorMessage("service_unavailable");
60
+ }
61
+ return out;
62
+ }
63
+ if (Array.isArray(value)) return value.map((v) => sanitizeOutboundDiagnostics(v, parentKey));
64
+ if (value !== null && typeof value === "object") {
65
+ const out = {};
66
+ for (const [key, val] of Object.entries(value)) {
67
+ if (INTERNAL_DIAGNOSTIC_KEYS.has(key)) continue;
68
+ const renamed = KEY_RENAMES[key] ?? key;
69
+ if (VENDOR_URL_KEYS.has(key)) {
70
+ out[renamed] = null;
71
+ continue;
72
+ }
73
+ out[renamed] = sanitizeOutboundDiagnostics(val, key);
74
+ }
75
+ return out;
76
+ }
77
+ return value;
78
+ }
79
+ function sanitizeAttempts(attempts) {
80
+ return attempts.map((a) => sanitizeOutboundDiagnostics(a));
81
+ }
82
+ function sanitizeHarvestResult(result) {
83
+ const diagnostics = result?.diagnostics;
84
+ if (!diagnostics?.debug) return result;
85
+ return {
86
+ ...result,
87
+ diagnostics: {
88
+ ...diagnostics,
89
+ debug: sanitizeOutboundDiagnostics(diagnostics.debug)
90
+ }
91
+ };
92
+ }
93
+
94
+ export {
95
+ sanitizeOutboundDiagnostics,
96
+ sanitizeAttempts,
97
+ sanitizeHarvestResult
98
+ };
99
+ //# sourceMappingURL=chunk-BBI7RGOT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api/outbound-sanitize.ts"],"sourcesContent":["import { publicErrorMessage } from '../errors.js'\n\nconst KEY_RENAMES: Record<string, string> = {\n kernel: 'browserRuntime',\n}\n\nconst SANITIZED_VALUE_KEYS = /error|message/i\nconst INTERNAL_DIAGNOSTIC_KEYS = new Set([\n 'kernel_session_id',\n 'browser_session_id',\n 'kernelSessionId',\n 'browserSessionId',\n 'sessionId',\n 'kernel_delete_started',\n 'session_cleanup_started',\n 'kernelDeleteStarted',\n 'sessionCleanupStarted',\n 'kernel_delete_succeeded',\n 'session_cleanup_succeeded',\n 'kernelDeleteSucceeded',\n 'sessionCleanupSucceeded',\n 'kernel_delete_error',\n 'session_cleanup_error',\n 'kernelDeleteError',\n 'sessionCleanupError',\n 'kernelProxyId',\n 'proxyId',\n 'requestedProxyIdSuffix',\n 'retrievedProxyIdSuffix',\n 'provider',\n 'providerName',\n 'service',\n 'serviceName',\n 'vendor',\n 'vendorName',\n 'organization',\n 'organizationId',\n 'accountId',\n])\n\nconst VENDOR_URL_KEYS = new Set([\n 'hosted_url',\n 'hostedUrl',\n 'live_view_url',\n 'liveViewUrl',\n 'cdp_ws_url',\n 'cdpWsUrl',\n 'browser_live_view_url',\n])\nconst VENDOR_URL_RE = /\\b(?:wss?|https?):\\/\\/[^\\s\"'<>]*\\bonkernel\\.com[^\\s\"'<>]*/gi\n\nfunction redactVendorUrls(value: string): string {\n return value.replace(VENDOR_URL_RE, '[browser-service]')\n}\n\nexport function sanitizeOutboundDiagnostics<T>(value: T, parentKey = ''): T {\n if (typeof value === 'string') {\n let out = redactVendorUrls(value)\n if (SANITIZED_VALUE_KEYS.test(parentKey)) {\n out = publicErrorMessage('service_unavailable')\n }\n return out as T\n }\n if (Array.isArray(value)) return value.map(v => sanitizeOutboundDiagnostics(v, parentKey)) as T\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n if (INTERNAL_DIAGNOSTIC_KEYS.has(key)) continue\n const renamed = KEY_RENAMES[key] ?? key\n if (VENDOR_URL_KEYS.has(key)) {\n out[renamed] = null\n continue\n }\n out[renamed] = sanitizeOutboundDiagnostics(val, key)\n }\n return out as T\n }\n return value\n}\n\nexport function sanitizeAttempts<T>(attempts: T[]): T[] {\n return attempts.map(a => sanitizeOutboundDiagnostics(a))\n}\n\nexport function sanitizeHarvestResult<T extends Record<string, unknown>>(result: T): T {\n const diagnostics = result?.diagnostics as Record<string, unknown> | undefined\n if (!diagnostics?.debug) return result\n return {\n ...result,\n diagnostics: {\n ...diagnostics,\n debug: sanitizeOutboundDiagnostics(diagnostics.debug),\n },\n }\n}\n"],"mappings":";;;;;AAEA,IAAM,cAAsC;AAAA,EAC1C,QAAQ;AACV;AAEA,IAAM,uBAAuB;AAC7B,IAAM,2BAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,kBAAkB,oBAAI,IAAI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,gBAAgB;AAEtB,SAAS,iBAAiB,OAAuB;AAC/C,SAAO,MAAM,QAAQ,eAAe,mBAAmB;AACzD;AAEO,SAAS,4BAA+B,OAAU,YAAY,IAAO;AAC1E,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,MAAM,iBAAiB,KAAK;AAChC,QAAI,qBAAqB,KAAK,SAAS,GAAG;AACxC,YAAM,mBAAmB,qBAAqB;AAAA,IAChD;AACA,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,OAAK,4BAA4B,GAAG,SAAS,CAAC;AACzF,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,KAAgC,GAAG;AACzE,UAAI,yBAAyB,IAAI,GAAG,EAAG;AACvC,YAAM,UAAU,YAAY,GAAG,KAAK;AACpC,UAAI,gBAAgB,IAAI,GAAG,GAAG;AAC5B,YAAI,OAAO,IAAI;AACf;AAAA,MACF;AACA,UAAI,OAAO,IAAI,4BAA4B,KAAK,GAAG;AAAA,IACrD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,iBAAoB,UAAoB;AACtD,SAAO,SAAS,IAAI,OAAK,4BAA4B,CAAC,CAAC;AACzD;AAEO,SAAS,sBAAyD,QAAc;AACrF,QAAM,cAAc,QAAQ;AAC5B,MAAI,CAAC,aAAa,MAAO,QAAO;AAChC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa;AAAA,MACX,GAAG;AAAA,MACH,OAAO,4BAA4B,YAAY,KAAK;AAAA,IACtD;AAAA,EACF;AACF;","names":[]}
@@ -1,12 +1,34 @@
1
1
  import {
2
2
  CaptchaError,
3
3
  LocationMismatchError,
4
- RequestAbortedError
5
- } from "./chunk-44HZLHDV.js";
4
+ PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
5
+ RequestAbortedError,
6
+ publicErrorMessage
7
+ } from "./chunk-5UN33CGU.js";
6
8
  import {
7
9
  finishHarvestAttempt,
8
10
  startHarvestAttempt
9
- } from "./chunk-GUVKHCKE.js";
11
+ } from "./chunk-IQTT7CAB.js";
12
+
13
+ // src/api/webhook.ts
14
+ async function deliverWebhook(url, payload, retries = 3) {
15
+ for (let attempt = 1; attempt <= retries; attempt++) {
16
+ try {
17
+ const res = await fetch(url, {
18
+ method: "POST",
19
+ headers: { "content-type": "application/json" },
20
+ body: JSON.stringify(payload),
21
+ signal: AbortSignal.timeout(1e4)
22
+ });
23
+ if (res.ok) return;
24
+ console.warn(`[webhook] attempt ${attempt} \u2192 ${res.status} from ${url}`);
25
+ } catch (err) {
26
+ console.warn(`[webhook] attempt ${attempt} failed:`, err instanceof Error ? err.message : err);
27
+ }
28
+ if (attempt < retries) await new Promise((r) => setTimeout(r, 1e3 * attempt * 2));
29
+ }
30
+ console.error(`[webhook] gave up after ${retries} attempts for ${url}`);
31
+ }
10
32
 
11
33
  // src/api/harvest-problems.ts
12
34
  function errorMessage(err) {
@@ -25,13 +47,16 @@ function looksLikeProxyTunnelFailure(message) {
25
47
  function looksLikeProxyUnavailable(message) {
26
48
  return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message);
27
49
  }
50
+ function looksLikeInternalServiceUnavailable(message) {
51
+ return /(?:spending|billing|organization|account).{0,80}(?:cap|limit|blocked|disabled)|(?:quota|capacity).{0,40}(?:reached|exceeded)|https?:\/\/\S*dashboard/i.test(message);
52
+ }
28
53
  function classifyHarvestProblem(err) {
29
54
  const message = errorMessage(err);
30
55
  if (err instanceof RequestAbortedError) {
31
56
  return {
32
57
  error_code: "request_aborted",
33
58
  error_type: "request_aborted",
34
- message,
59
+ message: publicErrorMessage("request_aborted"),
35
60
  retryable: true,
36
61
  httpStatus: 408,
37
62
  terminalStatus: "cancelled"
@@ -41,7 +66,7 @@ function classifyHarvestProblem(err) {
41
66
  return {
42
67
  error_code: "captcha_exhausted",
43
68
  error_type: "captcha",
44
- message,
69
+ message: publicErrorMessage("captcha_exhausted"),
45
70
  retryable: true,
46
71
  httpStatus: 503,
47
72
  terminalStatus: "failed"
@@ -51,7 +76,7 @@ function classifyHarvestProblem(err) {
51
76
  return {
52
77
  error_code: "location_mismatch",
53
78
  error_type: "location_mismatch",
54
- message,
79
+ message: publicErrorMessage("location_mismatch"),
55
80
  retryable: true,
56
81
  httpStatus: 503,
57
82
  terminalStatus: "failed"
@@ -61,7 +86,7 @@ function classifyHarvestProblem(err) {
61
86
  return {
62
87
  error_code: "proxy_tunnel_failed",
63
88
  error_type: "proxy_tunnel_failed",
64
- message,
89
+ message: publicErrorMessage("proxy_tunnel_failed"),
65
90
  retryable: true,
66
91
  httpStatus: 503,
67
92
  terminalStatus: "failed"
@@ -71,7 +96,7 @@ function classifyHarvestProblem(err) {
71
96
  return {
72
97
  error_code: "proxy_unavailable",
73
98
  error_type: "proxy_unavailable",
74
- message,
99
+ message: publicErrorMessage("proxy_unavailable"),
75
100
  retryable: true,
76
101
  httpStatus: 503,
77
102
  terminalStatus: "failed"
@@ -81,16 +106,26 @@ function classifyHarvestProblem(err) {
81
106
  return {
82
107
  error_code: "harvest_timeout",
83
108
  error_type: "timeout",
84
- message,
109
+ message: publicErrorMessage("harvest_timeout"),
85
110
  retryable: true,
86
111
  httpStatus: 504,
87
112
  terminalStatus: "failed"
88
113
  };
89
114
  }
115
+ if (looksLikeInternalServiceUnavailable(message)) {
116
+ return {
117
+ error_code: "service_unavailable",
118
+ error_type: "service_unavailable",
119
+ message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
120
+ retryable: true,
121
+ httpStatus: 503,
122
+ terminalStatus: "failed"
123
+ };
124
+ }
90
125
  return {
91
126
  error_code: "extraction_failed",
92
127
  error_type: "extraction",
93
- message,
128
+ message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
94
129
  retryable: false,
95
130
  httpStatus: 500,
96
131
  terminalStatus: "failed"
@@ -150,9 +185,10 @@ function createHarvestAttemptRecorder(jobId, userId) {
150
185
  }
151
186
 
152
187
  export {
188
+ deliverWebhook,
153
189
  classifyHarvestProblem,
154
190
  serializeHarvestProblem,
155
191
  harvestProblemResponse,
156
192
  createHarvestAttemptRecorder
157
193
  };
158
- //# sourceMappingURL=chunk-NVXNEOUQ.js.map
194
+ //# sourceMappingURL=chunk-CS4HE6QY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api/webhook.ts","../src/api/harvest-problems.ts","../src/api/harvest-attempt-events.ts"],"sourcesContent":["export async function deliverWebhook(url: string, payload: object, retries = 3): Promise<void> {\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(10_000),\n })\n if (res.ok) return\n console.warn(`[webhook] attempt ${attempt} → ${res.status} from ${url}`)\n } catch (err) {\n console.warn(`[webhook] attempt ${attempt} failed:`, err instanceof Error ? err.message : err)\n }\n if (attempt < retries) await new Promise((r) => setTimeout(r, 1000 * attempt * 2))\n }\n console.error(`[webhook] gave up after ${retries} attempts for ${url}`)\n}\n","import {\n CaptchaError,\n LocationMismatchError,\n PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,\n RequestAbortedError,\n publicErrorMessage,\n} from '../errors.js'\n\nexport type HarvestProblemCode =\n | 'request_aborted'\n | 'captcha_exhausted'\n | 'location_mismatch'\n | 'proxy_tunnel_failed'\n | 'proxy_unavailable'\n | 'harvest_timeout'\n | 'service_unavailable'\n | 'extraction_failed'\n\nexport interface HarvestProblem {\n error_code: HarvestProblemCode\n error_type: string\n message: string\n retryable: boolean\n httpStatus: number\n terminalStatus: 'cancelled' | 'failed'\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction looksLikeTimeout(err: unknown, message: string): boolean {\n if (err instanceof DOMException && (err.name === 'TimeoutError' || err.name === 'AbortError')) return true\n return /timeout|timed out|Timeout \\d+ms exceeded|deadline/i.test(message)\n}\n\nfunction looksLikeCaptcha(message: string): boolean {\n return /captcha|recaptcha|unusual traffic|google\\.com\\/sorry|blocked/i.test(message)\n}\n\nfunction looksLikeProxyTunnelFailure(message: string): boolean {\n return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message)\n}\n\nfunction looksLikeProxyUnavailable(message: string): boolean {\n return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message)\n}\n\nfunction looksLikeInternalServiceUnavailable(message: string): boolean {\n return /(?:spending|billing|organization|account).{0,80}(?:cap|limit|blocked|disabled)|(?:quota|capacity).{0,40}(?:reached|exceeded)|https?:\\/\\/\\S*dashboard/i.test(message)\n}\n\nexport function classifyHarvestProblem(err: unknown): HarvestProblem {\n const message = errorMessage(err)\n\n if (err instanceof RequestAbortedError) {\n return {\n error_code: 'request_aborted',\n error_type: 'request_aborted',\n message: publicErrorMessage('request_aborted'),\n retryable: true,\n httpStatus: 408,\n terminalStatus: 'cancelled',\n }\n }\n\n if (err instanceof CaptchaError || looksLikeCaptcha(message)) {\n return {\n error_code: 'captcha_exhausted',\n error_type: 'captcha',\n message: publicErrorMessage('captcha_exhausted'),\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (err instanceof LocationMismatchError) {\n return {\n error_code: 'location_mismatch',\n error_type: 'location_mismatch',\n message: publicErrorMessage('location_mismatch'),\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeProxyTunnelFailure(message)) {\n return {\n error_code: 'proxy_tunnel_failed',\n error_type: 'proxy_tunnel_failed',\n message: publicErrorMessage('proxy_tunnel_failed'),\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeProxyUnavailable(message)) {\n return {\n error_code: 'proxy_unavailable',\n error_type: 'proxy_unavailable',\n message: publicErrorMessage('proxy_unavailable'),\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeTimeout(err, message)) {\n return {\n error_code: 'harvest_timeout',\n error_type: 'timeout',\n message: publicErrorMessage('harvest_timeout'),\n retryable: true,\n httpStatus: 504,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeInternalServiceUnavailable(message)) {\n return {\n error_code: 'service_unavailable',\n error_type: 'service_unavailable',\n message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n return {\n error_code: 'extraction_failed',\n error_type: 'extraction',\n message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,\n retryable: false,\n httpStatus: 500,\n terminalStatus: 'failed',\n }\n}\n\nexport function serializeHarvestProblem(problem: HarvestProblem): string {\n return JSON.stringify({\n error_code: problem.error_code,\n error_type: problem.error_type,\n message: problem.message,\n retryable: problem.retryable,\n })\n}\n\nexport function harvestProblemResponse(problem: HarvestProblem): {\n error: string\n error_code: HarvestProblemCode\n error_type: string\n retryable: boolean\n} {\n return {\n error: problem.message,\n error_code: problem.error_code,\n error_type: problem.error_type,\n retryable: problem.retryable,\n }\n}\n","import type { HarvestAttemptLogEvent } from '../harvest.js'\nimport { finishHarvestAttempt, startHarvestAttempt } from './db.js'\n\nexport function createHarvestAttemptRecorder(jobId: string, userId: number | bigint) {\n return async (event: HarvestAttemptLogEvent): Promise<void> => {\n if (event.type === 'started') {\n await startHarvestAttempt({\n jobId,\n userId,\n attemptNumber: event.attemptNumber,\n maxAttempts: event.maxAttempts,\n query: event.query,\n location: event.location,\n maxQuestions: event.maxQuestions,\n startedAt: event.startedAt,\n })\n return\n }\n\n await finishHarvestAttempt({\n jobId,\n attemptNumber: event.attemptNumber,\n outcome: event.outcome,\n kernelSessionId: event.kernelSessionId,\n questionCount: event.questionCount,\n durationMs: event.durationMs,\n error: event.error,\n willRetry: event.willRetry,\n kernelDeleteStarted: event.cleanup.kernelDeleteStarted,\n kernelDeleteSucceeded: event.cleanup.kernelDeleteSucceeded,\n kernelDeleteError: event.cleanup.kernelDeleteError,\n browserCloseSucceeded: event.cleanup.browserCloseSucceeded,\n browserCloseError: event.cleanup.browserCloseError,\n debug: event.debug,\n completedAt: event.completedAt,\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getDb
3
- } from "./chunk-GUVKHCKE.js";
3
+ } from "./chunk-IQTT7CAB.js";
4
4
 
5
5
  // src/lib/browser-service-env.ts
6
6
  function browserServiceApiKey() {
@@ -273,4 +273,4 @@ export {
273
273
  recordKernelSession,
274
274
  recordVendorUsage
275
275
  };
276
- //# sourceMappingURL=chunk-3PIWJS6Y.js.map
276
+ //# sourceMappingURL=chunk-EHES33KB.js.map
@@ -1389,11 +1389,15 @@ function deserialize(raw) {
1389
1389
  }
1390
1390
  async function createJob(userId, query, options, callbackUrl) {
1391
1391
  const id = randomUUID();
1392
+ await createJobWithId(id, userId, query, options, callbackUrl);
1393
+ return id;
1394
+ }
1395
+ async function createJobWithId(id, userId, query, options, callbackUrl) {
1396
+ if (!/^[a-zA-Z0-9-]{1,100}$/.test(id)) throw new Error("job id is invalid");
1392
1397
  await getDb().execute({
1393
1398
  sql: "INSERT INTO jobs (id, user_id, query, options, callback_url) VALUES (?, ?, ?, ?, ?)",
1394
1399
  args: [id, userId, query, JSON.stringify(options), callbackUrl ?? null]
1395
1400
  });
1396
- return id;
1397
1401
  }
1398
1402
  async function createRunningJob(userId, query, options) {
1399
1403
  const id = randomUUID();
@@ -1924,7 +1928,13 @@ async function countActiveJobsForUser(userId) {
1924
1928
  }
1925
1929
  async function claimPendingJob() {
1926
1930
  const db = getDb();
1927
- const res = await db.execute(`SELECT * FROM jobs WHERE status = 'pending' ORDER BY created_at LIMIT 1`);
1931
+ const res = await db.execute(`
1932
+ SELECT * FROM jobs
1933
+ WHERE status = 'pending'
1934
+ AND COALESCE(json_extract(options, '$.executionOwner'), 'cron') != 'inngest'
1935
+ ORDER BY created_at
1936
+ LIMIT 1
1937
+ `);
1928
1938
  const job = res.rows[0] ? rowToRawJob(res.rows[0]) : void 0;
1929
1939
  if (!job) return void 0;
1930
1940
  const upd = await db.execute({
@@ -1933,6 +1943,80 @@ async function claimPendingJob() {
1933
1943
  });
1934
1944
  return upd.rowsAffected === 0 ? void 0 : { ...job, status: "running" };
1935
1945
  }
1946
+ async function claimInngestPaaJob(id) {
1947
+ const db = getDb();
1948
+ const current = await getJob(id);
1949
+ if (!current || current.options.executionOwner !== "inngest" || current.options.serpOnly === true) return void 0;
1950
+ if (current.status === "running") return void 0;
1951
+ if (current.status === "pending") {
1952
+ const updated = await db.execute({
1953
+ sql: `UPDATE jobs
1954
+ SET status = 'running', started_at = COALESCE(started_at, datetime('now')), error = NULL
1955
+ WHERE id = ? AND status = 'pending'`,
1956
+ args: [id]
1957
+ });
1958
+ if (updated.rowsAffected === 0) return getJob(id);
1959
+ return { ...current, status: "running", started_at: current.started_at ?? (/* @__PURE__ */ new Date()).toISOString() };
1960
+ }
1961
+ return current;
1962
+ }
1963
+ async function checkpointJob(id, result) {
1964
+ const updated = await getDb().execute({
1965
+ sql: `UPDATE jobs SET result = ? WHERE id = ? AND status = 'running'`,
1966
+ args: [JSON.stringify(result), id]
1967
+ });
1968
+ return updated.rowsAffected === 1;
1969
+ }
1970
+ async function listPendingInngestPaaJobs(limit = 25) {
1971
+ const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)));
1972
+ const rows = await getDb().execute({
1973
+ sql: `SELECT * FROM jobs
1974
+ WHERE status = 'pending'
1975
+ AND json_extract(options, '$.executionOwner') = 'inngest'
1976
+ AND COALESCE(json_extract(options, '$.serpOnly'), 0) = 0
1977
+ ORDER BY created_at
1978
+ LIMIT ?`,
1979
+ args: [safeLimit]
1980
+ });
1981
+ return rows.rows.map((row) => deserialize(rowToRawJob(row)));
1982
+ }
1983
+ async function listRecentTerminalInngestPaaJobs(limit = 25) {
1984
+ const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)));
1985
+ const rows = await getDb().execute({
1986
+ sql: `SELECT * FROM jobs
1987
+ WHERE status IN ('done', 'failed', 'cancelled')
1988
+ AND json_extract(options, '$.executionOwner') = 'inngest'
1989
+ AND json_extract(options, '$.billingDebitKey') IS NOT NULL
1990
+ AND json_extract(options, '$.billingSettledAt') IS NULL
1991
+ ORDER BY completed_at DESC
1992
+ LIMIT ?`,
1993
+ args: [safeLimit]
1994
+ });
1995
+ return rows.rows.map((row) => deserialize(rowToRawJob(row)));
1996
+ }
1997
+ async function listStaleRunningInngestPaaJobs(limit = 25) {
1998
+ const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)));
1999
+ const rows = await getDb().execute({
2000
+ sql: `SELECT * FROM jobs
2001
+ WHERE status = 'running'
2002
+ AND started_at <= datetime('now', '-10 minutes')
2003
+ AND json_extract(options, '$.executionOwner') = 'inngest'
2004
+ ORDER BY started_at
2005
+ LIMIT ?`,
2006
+ args: [safeLimit]
2007
+ });
2008
+ return rows.rows.map((row) => deserialize(rowToRawJob(row)));
2009
+ }
2010
+ async function markJobBillingSettled(id, billedMc, settledAt = (/* @__PURE__ */ new Date()).toISOString()) {
2011
+ if (!Number.isSafeInteger(billedMc) || billedMc < 0) throw new Error("job billed amount must be a non-negative integer");
2012
+ const updated = await getDb().execute({
2013
+ sql: `UPDATE jobs
2014
+ SET options = json_set(options, '$.billingSettledAt', ?, '$.billedMc', ?)
2015
+ WHERE id = ?`,
2016
+ args: [settledAt, billedMc, id]
2017
+ });
2018
+ return updated.rowsAffected === 1;
2019
+ }
1936
2020
  async function completeJob(id, result) {
1937
2021
  await getDb().execute({ sql: `UPDATE jobs SET status = 'done', result = ?, completed_at = datetime('now') WHERE id = ?`, args: [JSON.stringify(result), id] });
1938
2022
  }
@@ -2965,6 +3049,7 @@ export {
2965
3049
  listUsers,
2966
3050
  deactivateUser,
2967
3051
  createJob,
3052
+ createJobWithId,
2968
3053
  createRunningJob,
2969
3054
  startHarvestAttempt,
2970
3055
  finishHarvestAttempt,
@@ -2999,6 +3084,12 @@ export {
2999
3084
  countActiveUsers,
3000
3085
  countActiveJobsForUser,
3001
3086
  claimPendingJob,
3087
+ claimInngestPaaJob,
3088
+ checkpointJob,
3089
+ listPendingInngestPaaJobs,
3090
+ listRecentTerminalInngestPaaJobs,
3091
+ listStaleRunningInngestPaaJobs,
3092
+ markJobBillingSettled,
3002
3093
  completeJob,
3003
3094
  failJob,
3004
3095
  cancelJob,
@@ -3047,4 +3138,4 @@ export {
3047
3138
  insertInboxMessage,
3048
3139
  listActiveUserIds
3049
3140
  };
3050
- //# sourceMappingURL=chunk-GUVKHCKE.js.map
3141
+ //# sourceMappingURL=chunk-IQTT7CAB.js.map