@wenathlan/saddle 1.8.17 → 2.0.2

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 (1005) hide show
  1. package/Dockerfile +1339 -0
  2. package/README.md +68 -22
  3. package/alternatives.ts +1218 -0
  4. package/biome.json +153 -0
  5. package/boards.json +1517 -0
  6. package/compute.ts +3144 -0
  7. package/cores.json +1801 -0
  8. package/dist/acquisition.d.ts +359 -0
  9. package/dist/acquisition.d.ts.map +1 -0
  10. package/dist/acquisition.js +629 -0
  11. package/dist/acquisition.js.map +1 -0
  12. package/dist/alternatives.d.ts +509 -0
  13. package/dist/alternatives.d.ts.map +1 -0
  14. package/dist/alternatives.js +951 -0
  15. package/dist/alternatives.js.map +1 -0
  16. package/dist/automation.d.ts +426 -0
  17. package/dist/automation.d.ts.map +1 -0
  18. package/dist/automation.js +561 -0
  19. package/dist/automation.js.map +1 -0
  20. package/dist/browser.d.ts +562 -0
  21. package/dist/browser.d.ts.map +1 -0
  22. package/dist/browser.js +1057 -0
  23. package/dist/browser.js.map +1 -0
  24. package/dist/capacitor.config.d.ts +13 -0
  25. package/dist/capacitor.config.d.ts.map +1 -0
  26. package/dist/capacitor.config.js +26 -0
  27. package/dist/capacitor.config.js.map +1 -0
  28. package/dist/cli.d.ts +17 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +111 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/communication.d.ts +192 -0
  33. package/dist/communication.d.ts.map +1 -0
  34. package/dist/communication.js +418 -0
  35. package/dist/communication.js.map +1 -0
  36. package/dist/compute.d.ts +1161 -0
  37. package/dist/compute.d.ts.map +1 -0
  38. package/dist/compute.js +2273 -0
  39. package/dist/compute.js.map +1 -0
  40. package/dist/distribution.d.ts +529 -0
  41. package/dist/distribution.d.ts.map +1 -0
  42. package/dist/distribution.js +811 -0
  43. package/dist/distribution.js.map +1 -0
  44. package/dist/execution.d.ts +530 -0
  45. package/dist/execution.d.ts.map +1 -0
  46. package/dist/execution.js +761 -0
  47. package/dist/execution.js.map +1 -0
  48. package/dist/format.d.ts +19 -0
  49. package/dist/format.d.ts.map +1 -0
  50. package/dist/format.js +60 -0
  51. package/dist/format.js.map +1 -0
  52. package/dist/foundation.d.ts +176 -0
  53. package/dist/foundation.d.ts.map +1 -0
  54. package/dist/foundation.js +309 -0
  55. package/dist/foundation.js.map +1 -0
  56. package/dist/index.d.ts +733 -122
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +1018 -122
  59. package/dist/index.js.map +1 -1
  60. package/dist/integration.d.ts +236 -0
  61. package/dist/integration.d.ts.map +1 -0
  62. package/dist/integration.js +236 -0
  63. package/dist/integration.js.map +1 -0
  64. package/dist/intelligence.d.ts +94 -0
  65. package/dist/intelligence.d.ts.map +1 -0
  66. package/dist/intelligence.js +145 -0
  67. package/dist/intelligence.js.map +1 -0
  68. package/dist/isolation.d.ts +129 -0
  69. package/dist/isolation.d.ts.map +1 -0
  70. package/dist/isolation.js +96 -0
  71. package/dist/isolation.js.map +1 -0
  72. package/dist/media.d.ts +344 -0
  73. package/dist/media.d.ts.map +1 -0
  74. package/dist/media.js +986 -0
  75. package/dist/media.js.map +1 -0
  76. package/dist/modes.d.ts +359 -0
  77. package/dist/modes.d.ts.map +1 -0
  78. package/dist/modes.js +134 -0
  79. package/dist/modes.js.map +1 -0
  80. package/dist/{library/public.d.ts → operations.d.ts} +15 -1
  81. package/dist/operations.d.ts.map +1 -0
  82. package/dist/{library/public.js → operations.js} +18 -7
  83. package/dist/operations.js.map +1 -0
  84. package/dist/orchestrator.d.ts +1339 -0
  85. package/dist/orchestrator.d.ts.map +1 -0
  86. package/dist/orchestrator.js +4308 -0
  87. package/dist/orchestrator.js.map +1 -0
  88. package/dist/performance.d.ts +416 -0
  89. package/dist/performance.d.ts.map +1 -0
  90. package/dist/performance.js +827 -0
  91. package/dist/performance.js.map +1 -0
  92. package/dist/quantum.d.ts +810 -0
  93. package/dist/quantum.d.ts.map +1 -0
  94. package/dist/quantum.js +2073 -0
  95. package/dist/quantum.js.map +1 -0
  96. package/dist/render.d.ts +381 -0
  97. package/dist/render.d.ts.map +1 -0
  98. package/dist/render.js +1073 -0
  99. package/dist/render.js.map +1 -0
  100. package/dist/scheduler.d.ts +596 -0
  101. package/dist/scheduler.d.ts.map +1 -0
  102. package/dist/scheduler.js +1259 -0
  103. package/dist/scheduler.js.map +1 -0
  104. package/dist/security.d.ts +501 -0
  105. package/dist/security.d.ts.map +1 -0
  106. package/dist/security.js +1170 -0
  107. package/dist/security.js.map +1 -0
  108. package/dist/server.d.ts +21 -0
  109. package/dist/server.d.ts.map +1 -0
  110. package/dist/{server/node.js → server.js} +16 -2
  111. package/dist/server.js.map +1 -0
  112. package/dist/tiers.d.ts +1002 -0
  113. package/dist/tiers.d.ts.map +1 -0
  114. package/dist/tiers.js +2165 -0
  115. package/dist/tiers.js.map +1 -0
  116. package/dist/virtual.d.ts +641 -0
  117. package/dist/virtual.d.ts.map +1 -0
  118. package/dist/virtual.js +1285 -0
  119. package/dist/virtual.js.map +1 -0
  120. package/dist/virtualcpu.d.ts +316 -0
  121. package/dist/virtualcpu.d.ts.map +1 -0
  122. package/dist/virtualcpu.js +1180 -0
  123. package/dist/virtualcpu.js.map +1 -0
  124. package/dist/virtualgpu.d.ts +345 -0
  125. package/dist/virtualgpu.d.ts.map +1 -0
  126. package/dist/virtualgpu.js +1000 -0
  127. package/dist/virtualgpu.js.map +1 -0
  128. package/dist/virtualization.d.ts +607 -0
  129. package/dist/virtualization.d.ts.map +1 -0
  130. package/dist/virtualization.js +1187 -0
  131. package/dist/virtualization.js.map +1 -0
  132. package/dist/virtualmemory.d.ts +818 -0
  133. package/dist/virtualmemory.d.ts.map +1 -0
  134. package/dist/virtualmemory.js +1796 -0
  135. package/dist/virtualmemory.js.map +1 -0
  136. package/dist/webscrape.d.ts +720 -0
  137. package/dist/webscrape.d.ts.map +1 -0
  138. package/dist/webscrape.js +2697 -0
  139. package/dist/webscrape.js.map +1 -0
  140. package/docker.config +769 -0
  141. package/docs/CONVERSA.txt +1523 -0
  142. package/docs/alternatives.md +499 -0
  143. package/docs/architecture-1.8.19.md +76 -0
  144. package/docs/architecture-1.8.19.mmd +33 -0
  145. package/docs/architecture-1.8.19.png +0 -0
  146. package/docs/architecture.md +355 -0
  147. package/docs/artifactavailability.md +6 -0
  148. package/docs/brancharchive-2026-08-18.md +18 -0
  149. package/docs/browser.md +7 -0
  150. package/docs/consolidation.md +70 -0
  151. package/docs/e2ugh-engine.md +528 -0
  152. package/{dist/examples/localjob.js → docs/example-localjob.ts} +3 -3
  153. package/{dist/examples/publicapi.js → docs/example-publicapi.ts} +2 -2
  154. package/docs/hardware.md +342 -0
  155. package/docs/optimization.md +126 -0
  156. package/docs/performance.md +499 -0
  157. package/docs/planning.1.8.18.md +94 -0
  158. package/docs/planning.1.8.19.md +49 -0
  159. package/docs/releasenotes-1.8.17.md +6 -0
  160. package/docs/releasenotes-1.8.18.md +32 -0
  161. package/docs/releasenotes-1.8.19.md +35 -0
  162. package/docs/research-1.8.18-isolation.md +43 -0
  163. package/docs/research-1.8.19-virtual-browser.md +27 -0
  164. package/docs/saddle.archive.1.8.17.tar.gz.gpg +0 -0
  165. package/docs/security.md +454 -0
  166. package/docs/todo-1.8.16.md +56 -1
  167. package/docs/todo-1.8.18.md +164 -0
  168. package/docs/todo-1.8.19.md +241 -0
  169. package/docs/viability.md +777 -0
  170. package/docs/virtualization.md +335 -0
  171. package/docs/web-duplicatecleanup.md +9 -0
  172. package/docs/web-ideas.md +106 -0
  173. package/docs/workflowimprovements-2026-08-19.md +70 -0
  174. package/gpumonitor.cpp +1543 -0
  175. package/gpus.json +2408 -0
  176. package/index.ts +1478 -0
  177. package/media.ts +1337 -0
  178. package/mttg.config +1549 -0
  179. package/orchestrator.ts +5462 -0
  180. package/package.json +304 -55
  181. package/passage.config +1331 -0
  182. package/performance.ts +1049 -0
  183. package/processors.json +2217 -0
  184. package/qemu.config +1062 -0
  185. package/qemubridge.py +1340 -0
  186. package/quantum.ts +2393 -0
  187. package/render.ts +1325 -0
  188. package/scheduler.ts +1600 -0
  189. package/security.ts +1449 -0
  190. package/tiers.ts +2871 -0
  191. package/tsconfig.json +29 -0
  192. package/virtualcpu.ts +1273 -0
  193. package/virtualgpu.ts +1181 -0
  194. package/virtualhardware.c +1011 -0
  195. package/virtualhardware.json +739 -0
  196. package/virtualization.ts +1628 -0
  197. package/virtualizationcore.cpp +4635 -0
  198. package/virtualmemory.ts +2303 -0
  199. package/vm.config.json +1910 -0
  200. package/{extension → web/extension}/manifest.json +1 -1
  201. package/web/readme.md +382 -0
  202. package/web/tsconfig.json +25 -0
  203. package/dist/adapters/forge.d.ts +0 -17
  204. package/dist/adapters/forge.d.ts.map +0 -1
  205. package/dist/adapters/forge.js +0 -19
  206. package/dist/adapters/forge.js.map +0 -1
  207. package/dist/adapters/forgejo.d.ts +0 -49
  208. package/dist/adapters/forgejo.d.ts.map +0 -1
  209. package/dist/adapters/forgejo.js +0 -8
  210. package/dist/adapters/forgejo.js.map +0 -1
  211. package/dist/adapters/github.d.ts +0 -12
  212. package/dist/adapters/github.d.ts.map +0 -1
  213. package/dist/adapters/github.js +0 -20
  214. package/dist/adapters/github.js.map +0 -1
  215. package/dist/adapters/gitlab.d.ts +0 -17
  216. package/dist/adapters/gitlab.d.ts.map +0 -1
  217. package/dist/adapters/gitlab.js +0 -10
  218. package/dist/adapters/gitlab.js.map +0 -1
  219. package/dist/adapters/huggingface.d.ts +0 -17
  220. package/dist/adapters/huggingface.d.ts.map +0 -1
  221. package/dist/adapters/huggingface.js +0 -6
  222. package/dist/adapters/huggingface.js.map +0 -1
  223. package/dist/adapters/socket.d.ts +0 -11
  224. package/dist/adapters/socket.d.ts.map +0 -1
  225. package/dist/adapters/socket.js +0 -17
  226. package/dist/adapters/socket.js.map +0 -1
  227. package/dist/adapters/transport.d.ts +0 -7
  228. package/dist/adapters/transport.d.ts.map +0 -1
  229. package/dist/adapters/transport.js +0 -36
  230. package/dist/adapters/transport.js.map +0 -1
  231. package/dist/ai/chunk.d.ts +0 -2
  232. package/dist/ai/chunk.d.ts.map +0 -1
  233. package/dist/ai/chunk.js +0 -36
  234. package/dist/ai/chunk.js.map +0 -1
  235. package/dist/ai/llmstxt.d.ts +0 -6
  236. package/dist/ai/llmstxt.d.ts.map +0 -1
  237. package/dist/ai/llmstxt.js +0 -12
  238. package/dist/ai/llmstxt.js.map +0 -1
  239. package/dist/ai/provenance.d.ts +0 -22
  240. package/dist/ai/provenance.d.ts.map +0 -1
  241. package/dist/ai/provenance.js +0 -25
  242. package/dist/ai/provenance.js.map +0 -1
  243. package/dist/ai/rag.d.ts +0 -26
  244. package/dist/ai/rag.d.ts.map +0 -1
  245. package/dist/ai/rag.js +0 -22
  246. package/dist/ai/rag.js.map +0 -1
  247. package/dist/ai/tokens.d.ts +0 -15
  248. package/dist/ai/tokens.d.ts.map +0 -1
  249. package/dist/ai/tokens.js +0 -9
  250. package/dist/ai/tokens.js.map +0 -1
  251. package/dist/api/auth.d.ts +0 -16
  252. package/dist/api/auth.d.ts.map +0 -1
  253. package/dist/api/auth.js +0 -19
  254. package/dist/api/auth.js.map +0 -1
  255. package/dist/api/contracts.d.ts +0 -28
  256. package/dist/api/contracts.d.ts.map +0 -1
  257. package/dist/api/contracts.js +0 -14
  258. package/dist/api/contracts.js.map +0 -1
  259. package/dist/api/control.d.ts +0 -10
  260. package/dist/api/control.d.ts.map +0 -1
  261. package/dist/api/control.js +0 -35
  262. package/dist/api/control.js.map +0 -1
  263. package/dist/api/http.d.ts +0 -7
  264. package/dist/api/http.d.ts.map +0 -1
  265. package/dist/api/http.js +0 -12
  266. package/dist/api/http.js.map +0 -1
  267. package/dist/api/rate.d.ts +0 -28
  268. package/dist/api/rate.d.ts.map +0 -1
  269. package/dist/api/rate.js +0 -36
  270. package/dist/api/rate.js.map +0 -1
  271. package/dist/api/security.d.ts +0 -12
  272. package/dist/api/security.d.ts.map +0 -1
  273. package/dist/api/security.js +0 -56
  274. package/dist/api/security.js.map +0 -1
  275. package/dist/api/service.d.ts +0 -5
  276. package/dist/api/service.d.ts.map +0 -1
  277. package/dist/api/service.js +0 -70
  278. package/dist/api/service.js.map +0 -1
  279. package/dist/apps/registry.d.ts +0 -33
  280. package/dist/apps/registry.d.ts.map +0 -1
  281. package/dist/apps/registry.js +0 -24
  282. package/dist/apps/registry.js.map +0 -1
  283. package/dist/binary/archive.d.ts +0 -27
  284. package/dist/binary/archive.d.ts.map +0 -1
  285. package/dist/binary/archive.js +0 -46
  286. package/dist/binary/archive.js.map +0 -1
  287. package/dist/binary/build.d.ts +0 -29
  288. package/dist/binary/build.d.ts.map +0 -1
  289. package/dist/binary/build.js +0 -17
  290. package/dist/binary/build.js.map +0 -1
  291. package/dist/binary/transform.d.ts +0 -70
  292. package/dist/binary/transform.d.ts.map +0 -1
  293. package/dist/binary/transform.js +0 -105
  294. package/dist/binary/transform.js.map +0 -1
  295. package/dist/bot/adapter.d.ts +0 -5
  296. package/dist/bot/adapter.d.ts.map +0 -1
  297. package/dist/bot/adapter.js +0 -11
  298. package/dist/bot/adapter.js.map +0 -1
  299. package/dist/bot/bot.d.ts +0 -28
  300. package/dist/bot/bot.d.ts.map +0 -1
  301. package/dist/bot/bot.js +0 -52
  302. package/dist/bot/bot.js.map +0 -1
  303. package/dist/bot/commands.d.ts +0 -8
  304. package/dist/bot/commands.d.ts.map +0 -1
  305. package/dist/bot/commands.js +0 -19
  306. package/dist/bot/commands.js.map +0 -1
  307. package/dist/bot/permissions.d.ts +0 -13
  308. package/dist/bot/permissions.d.ts.map +0 -1
  309. package/dist/bot/permissions.js +0 -16
  310. package/dist/bot/permissions.js.map +0 -1
  311. package/dist/browser/actions.d.ts +0 -33
  312. package/dist/browser/actions.d.ts.map +0 -1
  313. package/dist/browser/actions.js +0 -44
  314. package/dist/browser/actions.js.map +0 -1
  315. package/dist/browser/agent.d.ts +0 -21
  316. package/dist/browser/agent.d.ts.map +0 -1
  317. package/dist/browser/agent.js +0 -13
  318. package/dist/browser/agent.js.map +0 -1
  319. package/dist/browser/context.d.ts +0 -43
  320. package/dist/browser/context.d.ts.map +0 -1
  321. package/dist/browser/context.js +0 -54
  322. package/dist/browser/context.js.map +0 -1
  323. package/dist/browser/fingerprint.d.ts +0 -4
  324. package/dist/browser/fingerprint.d.ts.map +0 -1
  325. package/dist/browser/fingerprint.js +0 -12
  326. package/dist/browser/fingerprint.js.map +0 -1
  327. package/dist/browser/index.d.ts +0 -11
  328. package/dist/browser/index.d.ts.map +0 -1
  329. package/dist/browser/index.js +0 -11
  330. package/dist/browser/index.js.map +0 -1
  331. package/dist/browser/playwright.d.ts +0 -11
  332. package/dist/browser/playwright.d.ts.map +0 -1
  333. package/dist/browser/playwright.js +0 -24
  334. package/dist/browser/playwright.js.map +0 -1
  335. package/dist/browser/recorder.d.ts +0 -21
  336. package/dist/browser/recorder.d.ts.map +0 -1
  337. package/dist/browser/recorder.js +0 -58
  338. package/dist/browser/recorder.js.map +0 -1
  339. package/dist/browser/session.d.ts +0 -14
  340. package/dist/browser/session.d.ts.map +0 -1
  341. package/dist/browser/session.js +0 -21
  342. package/dist/browser/session.js.map +0 -1
  343. package/dist/browser/snapshot.d.ts +0 -69
  344. package/dist/browser/snapshot.d.ts.map +0 -1
  345. package/dist/browser/snapshot.js +0 -133
  346. package/dist/browser/snapshot.js.map +0 -1
  347. package/dist/captcha/contract.d.ts +0 -35
  348. package/dist/captcha/contract.d.ts.map +0 -1
  349. package/dist/captcha/contract.js +0 -17
  350. package/dist/captcha/contract.js.map +0 -1
  351. package/dist/captcha/evidence.d.ts +0 -10
  352. package/dist/captcha/evidence.d.ts.map +0 -1
  353. package/dist/captcha/evidence.js +0 -9
  354. package/dist/captcha/evidence.js.map +0 -1
  355. package/dist/captcha/guard.d.ts +0 -23
  356. package/dist/captcha/guard.d.ts.map +0 -1
  357. package/dist/captcha/guard.js +0 -12
  358. package/dist/captcha/guard.js.map +0 -1
  359. package/dist/cli/main.d.ts +0 -4
  360. package/dist/cli/main.d.ts.map +0 -1
  361. package/dist/cli/main.js +0 -46
  362. package/dist/cli/main.js.map +0 -1
  363. package/dist/core/errors.d.ts +0 -84
  364. package/dist/core/errors.d.ts.map +0 -1
  365. package/dist/core/errors.js +0 -54
  366. package/dist/core/errors.js.map +0 -1
  367. package/dist/core/events.d.ts +0 -9
  368. package/dist/core/events.d.ts.map +0 -1
  369. package/dist/core/events.js +0 -21
  370. package/dist/core/events.js.map +0 -1
  371. package/dist/core/hash.d.ts +0 -10
  372. package/dist/core/hash.d.ts.map +0 -1
  373. package/dist/core/hash.js +0 -82
  374. package/dist/core/hash.js.map +0 -1
  375. package/dist/core/ids.d.ts +0 -10
  376. package/dist/core/ids.d.ts.map +0 -1
  377. package/dist/core/ids.js +0 -15
  378. package/dist/core/ids.js.map +0 -1
  379. package/dist/delivery/manifest.d.ts +0 -35
  380. package/dist/delivery/manifest.d.ts.map +0 -1
  381. package/dist/delivery/manifest.js +0 -68
  382. package/dist/delivery/manifest.js.map +0 -1
  383. package/dist/deploy/index.d.ts +0 -6
  384. package/dist/deploy/index.d.ts.map +0 -1
  385. package/dist/deploy/index.js +0 -6
  386. package/dist/deploy/index.js.map +0 -1
  387. package/dist/dispatch/resumable.d.ts +0 -102
  388. package/dist/dispatch/resumable.d.ts.map +0 -1
  389. package/dist/dispatch/resumable.js +0 -64
  390. package/dist/dispatch/resumable.js.map +0 -1
  391. package/dist/dispatch/workflow.d.ts +0 -6
  392. package/dist/dispatch/workflow.d.ts.map +0 -1
  393. package/dist/dispatch/workflow.js +0 -37
  394. package/dist/dispatch/workflow.js.map +0 -1
  395. package/dist/domain/artifacts.d.ts +0 -12
  396. package/dist/domain/artifacts.d.ts.map +0 -1
  397. package/dist/domain/artifacts.js +0 -14
  398. package/dist/domain/artifacts.js.map +0 -1
  399. package/dist/domain/jobs.d.ts +0 -12
  400. package/dist/domain/jobs.d.ts.map +0 -1
  401. package/dist/domain/jobs.js +0 -20
  402. package/dist/domain/jobs.js.map +0 -1
  403. package/dist/domain/providers.d.ts +0 -10
  404. package/dist/domain/providers.d.ts.map +0 -1
  405. package/dist/domain/providers.js +0 -8
  406. package/dist/domain/providers.js.map +0 -1
  407. package/dist/domain/runtime.d.ts +0 -14
  408. package/dist/domain/runtime.d.ts.map +0 -1
  409. package/dist/domain/runtime.js +0 -10
  410. package/dist/domain/runtime.js.map +0 -1
  411. package/dist/domain/sessions.d.ts +0 -12
  412. package/dist/domain/sessions.d.ts.map +0 -1
  413. package/dist/domain/sessions.js +0 -57
  414. package/dist/domain/sessions.js.map +0 -1
  415. package/dist/examples/localjob.d.ts +0 -2
  416. package/dist/examples/localjob.d.ts.map +0 -1
  417. package/dist/examples/localjob.js.map +0 -1
  418. package/dist/examples/publicapi.d.ts +0 -2
  419. package/dist/examples/publicapi.d.ts.map +0 -1
  420. package/dist/examples/publicapi.js.map +0 -1
  421. package/dist/extension/build.d.ts +0 -9
  422. package/dist/extension/build.d.ts.map +0 -1
  423. package/dist/extension/build.js +0 -85
  424. package/dist/extension/build.js.map +0 -1
  425. package/dist/extension/content.d.ts +0 -6
  426. package/dist/extension/content.d.ts.map +0 -1
  427. package/dist/extension/content.js +0 -135
  428. package/dist/extension/content.js.map +0 -1
  429. package/dist/extension/index.d.ts +0 -7
  430. package/dist/extension/index.d.ts.map +0 -1
  431. package/dist/extension/index.js +0 -7
  432. package/dist/extension/index.js.map +0 -1
  433. package/dist/extension/pagebridge.d.ts +0 -6
  434. package/dist/extension/pagebridge.d.ts.map +0 -1
  435. package/dist/extension/pagebridge.js +0 -42
  436. package/dist/extension/pagebridge.js.map +0 -1
  437. package/dist/extension/permissions.d.ts +0 -26
  438. package/dist/extension/permissions.d.ts.map +0 -1
  439. package/dist/extension/permissions.js +0 -28
  440. package/dist/extension/permissions.js.map +0 -1
  441. package/dist/extension/popup.d.ts +0 -5
  442. package/dist/extension/popup.d.ts.map +0 -1
  443. package/dist/extension/popup.js +0 -26
  444. package/dist/extension/popup.js.map +0 -1
  445. package/dist/extension/protocol.d.ts +0 -58
  446. package/dist/extension/protocol.d.ts.map +0 -1
  447. package/dist/extension/protocol.js +0 -101
  448. package/dist/extension/protocol.js.map +0 -1
  449. package/dist/extension/serviceworker.d.ts +0 -25
  450. package/dist/extension/serviceworker.d.ts.map +0 -1
  451. package/dist/extension/serviceworker.js +0 -102
  452. package/dist/extension/serviceworker.js.map +0 -1
  453. package/dist/extension/worker.d.ts +0 -28
  454. package/dist/extension/worker.d.ts.map +0 -1
  455. package/dist/extension/worker.js +0 -22
  456. package/dist/extension/worker.js.map +0 -1
  457. package/dist/format/check.d.ts +0 -8
  458. package/dist/format/check.d.ts.map +0 -1
  459. package/dist/format/check.js +0 -38
  460. package/dist/format/check.js.map +0 -1
  461. package/dist/library/public.d.ts.map +0 -1
  462. package/dist/library/public.js.map +0 -1
  463. package/dist/mcp/browser.d.ts +0 -9
  464. package/dist/mcp/browser.d.ts.map +0 -1
  465. package/dist/mcp/browser.js +0 -13
  466. package/dist/mcp/browser.js.map +0 -1
  467. package/dist/mcp/server.d.ts +0 -35
  468. package/dist/mcp/server.d.ts.map +0 -1
  469. package/dist/mcp/server.js +0 -40
  470. package/dist/mcp/server.js.map +0 -1
  471. package/dist/mcp/transport.d.ts +0 -7
  472. package/dist/mcp/transport.d.ts.map +0 -1
  473. package/dist/mcp/transport.js +0 -17
  474. package/dist/mcp/transport.js.map +0 -1
  475. package/dist/memory/bridge.d.ts +0 -14
  476. package/dist/memory/bridge.d.ts.map +0 -1
  477. package/dist/memory/bridge.js +0 -16
  478. package/dist/memory/bridge.js.map +0 -1
  479. package/dist/memory/engine.d.ts +0 -38
  480. package/dist/memory/engine.d.ts.map +0 -1
  481. package/dist/memory/engine.js +0 -109
  482. package/dist/memory/engine.js.map +0 -1
  483. package/dist/memory/modes.d.ts +0 -43
  484. package/dist/memory/modes.d.ts.map +0 -1
  485. package/dist/memory/modes.js +0 -66
  486. package/dist/memory/modes.js.map +0 -1
  487. package/dist/memory/objects.d.ts +0 -21
  488. package/dist/memory/objects.d.ts.map +0 -1
  489. package/dist/memory/objects.js +0 -20
  490. package/dist/memory/objects.js.map +0 -1
  491. package/dist/memory/planner.d.ts +0 -66
  492. package/dist/memory/planner.d.ts.map +0 -1
  493. package/dist/memory/planner.js +0 -108
  494. package/dist/memory/planner.js.map +0 -1
  495. package/dist/memory/targets.d.ts +0 -78
  496. package/dist/memory/targets.d.ts.map +0 -1
  497. package/dist/memory/targets.js +0 -29
  498. package/dist/memory/targets.js.map +0 -1
  499. package/dist/memory/transforms.d.ts +0 -19
  500. package/dist/memory/transforms.d.ts.map +0 -1
  501. package/dist/memory/transforms.js +0 -14
  502. package/dist/memory/transforms.js.map +0 -1
  503. package/dist/modes/matrix.d.ts +0 -23
  504. package/dist/modes/matrix.d.ts.map +0 -1
  505. package/dist/modes/matrix.js +0 -18
  506. package/dist/modes/matrix.js.map +0 -1
  507. package/dist/modes/modes.d.ts +0 -33
  508. package/dist/modes/modes.d.ts.map +0 -1
  509. package/dist/modes/modes.js +0 -15
  510. package/dist/modes/modes.js.map +0 -1
  511. package/dist/modes/resolve.d.ts +0 -96
  512. package/dist/modes/resolve.d.ts.map +0 -1
  513. package/dist/modes/resolve.js +0 -48
  514. package/dist/modes/resolve.js.map +0 -1
  515. package/dist/observability/metrics.d.ts +0 -14
  516. package/dist/observability/metrics.d.ts.map +0 -1
  517. package/dist/observability/metrics.js +0 -18
  518. package/dist/observability/metrics.js.map +0 -1
  519. package/dist/packager/manifest.d.ts +0 -66
  520. package/dist/packager/manifest.d.ts.map +0 -1
  521. package/dist/packager/manifest.js +0 -94
  522. package/dist/packager/manifest.js.map +0 -1
  523. package/dist/packager/publish.d.ts +0 -51
  524. package/dist/packager/publish.d.ts.map +0 -1
  525. package/dist/packager/publish.js +0 -18
  526. package/dist/packager/publish.js.map +0 -1
  527. package/dist/packager/targetcli.d.ts +0 -7
  528. package/dist/packager/targetcli.d.ts.map +0 -1
  529. package/dist/packager/targetcli.js +0 -22
  530. package/dist/packager/targetcli.js.map +0 -1
  531. package/dist/persistence/adapter.d.ts +0 -5
  532. package/dist/persistence/adapter.d.ts.map +0 -1
  533. package/dist/persistence/adapter.js +0 -11
  534. package/dist/persistence/adapter.js.map +0 -1
  535. package/dist/persistence/drizzle.d.ts +0 -2
  536. package/dist/persistence/drizzle.d.ts.map +0 -1
  537. package/dist/persistence/drizzle.js +0 -12
  538. package/dist/persistence/drizzle.js.map +0 -1
  539. package/dist/persistence/memory.d.ts +0 -2
  540. package/dist/persistence/memory.d.ts.map +0 -1
  541. package/dist/persistence/memory.js +0 -27
  542. package/dist/persistence/memory.js.map +0 -1
  543. package/dist/persistence/migrations.d.ts +0 -19
  544. package/dist/persistence/migrations.d.ts.map +0 -1
  545. package/dist/persistence/migrations.js +0 -13
  546. package/dist/persistence/migrations.js.map +0 -1
  547. package/dist/persistence/prisma.d.ts +0 -2
  548. package/dist/persistence/prisma.d.ts.map +0 -1
  549. package/dist/persistence/prisma.js +0 -25
  550. package/dist/persistence/prisma.js.map +0 -1
  551. package/dist/persistence/schema.d.ts +0 -63
  552. package/dist/persistence/schema.d.ts.map +0 -1
  553. package/dist/persistence/schema.js +0 -28
  554. package/dist/persistence/schema.js.map +0 -1
  555. package/dist/persistence/sql.d.ts +0 -3
  556. package/dist/persistence/sql.d.ts.map +0 -1
  557. package/dist/persistence/sql.js +0 -37
  558. package/dist/persistence/sql.js.map +0 -1
  559. package/dist/protocol/blocks.d.ts +0 -10
  560. package/dist/protocol/blocks.d.ts.map +0 -1
  561. package/dist/protocol/blocks.js +0 -26
  562. package/dist/protocol/blocks.js.map +0 -1
  563. package/dist/protocol/json.d.ts +0 -6
  564. package/dist/protocol/json.d.ts.map +0 -1
  565. package/dist/protocol/json.js +0 -6
  566. package/dist/protocol/json.js.map +0 -1
  567. package/dist/protocol/ndjson.d.ts +0 -3
  568. package/dist/protocol/ndjson.d.ts.map +0 -1
  569. package/dist/protocol/ndjson.js +0 -20
  570. package/dist/protocol/ndjson.js.map +0 -1
  571. package/dist/protocol/sse.d.ts +0 -7
  572. package/dist/protocol/sse.d.ts.map +0 -1
  573. package/dist/protocol/sse.js +0 -25
  574. package/dist/protocol/sse.js.map +0 -1
  575. package/dist/proxy/pool.d.ts +0 -9
  576. package/dist/proxy/pool.d.ts.map +0 -1
  577. package/dist/proxy/pool.js +0 -27
  578. package/dist/proxy/pool.js.map +0 -1
  579. package/dist/queue/idempotency.d.ts +0 -10
  580. package/dist/queue/idempotency.d.ts.map +0 -1
  581. package/dist/queue/idempotency.js +0 -13
  582. package/dist/queue/idempotency.js.map +0 -1
  583. package/dist/queue/persistent.d.ts +0 -14
  584. package/dist/queue/persistent.d.ts.map +0 -1
  585. package/dist/queue/persistent.js +0 -66
  586. package/dist/queue/persistent.js.map +0 -1
  587. package/dist/queue/queue.d.ts +0 -7
  588. package/dist/queue/queue.d.ts.map +0 -1
  589. package/dist/queue/queue.js +0 -55
  590. package/dist/queue/queue.js.map +0 -1
  591. package/dist/queue/saga.d.ts +0 -5
  592. package/dist/queue/saga.d.ts.map +0 -1
  593. package/dist/queue/saga.js +0 -20
  594. package/dist/queue/saga.js.map +0 -1
  595. package/dist/release/assets.d.ts +0 -144
  596. package/dist/release/assets.d.ts.map +0 -1
  597. package/dist/release/assets.js +0 -157
  598. package/dist/release/assets.js.map +0 -1
  599. package/dist/release/evidence.d.ts +0 -92
  600. package/dist/release/evidence.d.ts.map +0 -1
  601. package/dist/release/evidence.js +0 -148
  602. package/dist/release/evidence.js.map +0 -1
  603. package/dist/release/verify.d.ts +0 -12
  604. package/dist/release/verify.d.ts.map +0 -1
  605. package/dist/release/verify.js +0 -112
  606. package/dist/release/verify.js.map +0 -1
  607. package/dist/runners/chain.d.ts +0 -125
  608. package/dist/runners/chain.d.ts.map +0 -1
  609. package/dist/runners/chain.js +0 -95
  610. package/dist/runners/chain.js.map +0 -1
  611. package/dist/runners/health.d.ts +0 -36
  612. package/dist/runners/health.d.ts.map +0 -1
  613. package/dist/runners/health.js +0 -26
  614. package/dist/runners/health.js.map +0 -1
  615. package/dist/runners/heartbeat.d.ts +0 -23
  616. package/dist/runners/heartbeat.d.ts.map +0 -1
  617. package/dist/runners/heartbeat.js +0 -29
  618. package/dist/runners/heartbeat.js.map +0 -1
  619. package/dist/runners/inprocess.d.ts +0 -17
  620. package/dist/runners/inprocess.d.ts.map +0 -1
  621. package/dist/runners/inprocess.js +0 -20
  622. package/dist/runners/inprocess.js.map +0 -1
  623. package/dist/runners/scheduler.d.ts +0 -5
  624. package/dist/runners/scheduler.d.ts.map +0 -1
  625. package/dist/runners/scheduler.js +0 -19
  626. package/dist/runners/scheduler.js.map +0 -1
  627. package/dist/runtime/abort.d.ts +0 -8
  628. package/dist/runtime/abort.d.ts.map +0 -1
  629. package/dist/runtime/abort.js +0 -13
  630. package/dist/runtime/abort.js.map +0 -1
  631. package/dist/runtime/compatibility.d.ts +0 -37
  632. package/dist/runtime/compatibility.d.ts.map +0 -1
  633. package/dist/runtime/compatibility.js +0 -10
  634. package/dist/runtime/compatibility.js.map +0 -1
  635. package/dist/runtime/detect.d.ts +0 -13
  636. package/dist/runtime/detect.d.ts.map +0 -1
  637. package/dist/runtime/detect.js +0 -18
  638. package/dist/runtime/detect.js.map +0 -1
  639. package/dist/runtime/engine.d.ts +0 -20
  640. package/dist/runtime/engine.d.ts.map +0 -1
  641. package/dist/runtime/engine.js +0 -61
  642. package/dist/runtime/engine.js.map +0 -1
  643. package/dist/runtime/retry.d.ts +0 -19
  644. package/dist/runtime/retry.d.ts.map +0 -1
  645. package/dist/runtime/retry.js +0 -54
  646. package/dist/runtime/retry.js.map +0 -1
  647. package/dist/runtime/worker.d.ts +0 -10
  648. package/dist/runtime/worker.d.ts.map +0 -1
  649. package/dist/runtime/worker.js +0 -25
  650. package/dist/runtime/worker.js.map +0 -1
  651. package/dist/scrape/cache.d.ts +0 -45
  652. package/dist/scrape/cache.d.ts.map +0 -1
  653. package/dist/scrape/cache.js +0 -45
  654. package/dist/scrape/cache.js.map +0 -1
  655. package/dist/scrape/crawl.d.ts +0 -47
  656. package/dist/scrape/crawl.d.ts.map +0 -1
  657. package/dist/scrape/crawl.js +0 -114
  658. package/dist/scrape/crawl.js.map +0 -1
  659. package/dist/scrape/extract.d.ts +0 -63
  660. package/dist/scrape/extract.d.ts.map +0 -1
  661. package/dist/scrape/extract.js +0 -47
  662. package/dist/scrape/extract.js.map +0 -1
  663. package/dist/scrape/normalize.d.ts +0 -52
  664. package/dist/scrape/normalize.d.ts.map +0 -1
  665. package/dist/scrape/normalize.js +0 -102
  666. package/dist/scrape/normalize.js.map +0 -1
  667. package/dist/scrape/robots.d.ts +0 -37
  668. package/dist/scrape/robots.d.ts.map +0 -1
  669. package/dist/scrape/robots.js +0 -71
  670. package/dist/scrape/robots.js.map +0 -1
  671. package/dist/scrape/schema.d.ts +0 -23
  672. package/dist/scrape/schema.d.ts.map +0 -1
  673. package/dist/scrape/schema.js +0 -92
  674. package/dist/scrape/schema.js.map +0 -1
  675. package/dist/scrape/scraper.d.ts +0 -6
  676. package/dist/scrape/scraper.d.ts.map +0 -1
  677. package/dist/scrape/scraper.js +0 -46
  678. package/dist/scrape/scraper.js.map +0 -1
  679. package/dist/scrape/semantic.d.ts +0 -25
  680. package/dist/scrape/semantic.d.ts.map +0 -1
  681. package/dist/scrape/semantic.js +0 -27
  682. package/dist/scrape/semantic.js.map +0 -1
  683. package/dist/server/node.d.ts +0 -7
  684. package/dist/server/node.d.ts.map +0 -1
  685. package/dist/server/node.js.map +0 -1
  686. package/dist/sessions/file.d.ts +0 -25
  687. package/dist/sessions/file.d.ts.map +0 -1
  688. package/dist/sessions/file.js +0 -13
  689. package/dist/sessions/file.js.map +0 -1
  690. package/dist/sessions/replay.d.ts +0 -9
  691. package/dist/sessions/replay.d.ts.map +0 -1
  692. package/dist/sessions/replay.js +0 -70
  693. package/dist/sessions/replay.js.map +0 -1
  694. package/dist/sessions/store.d.ts +0 -25
  695. package/dist/sessions/store.d.ts.map +0 -1
  696. package/dist/sessions/store.js +0 -13
  697. package/dist/sessions/store.js.map +0 -1
  698. package/dist/storage/adapter.d.ts +0 -5
  699. package/dist/storage/adapter.d.ts.map +0 -1
  700. package/dist/storage/adapter.js +0 -11
  701. package/dist/storage/adapter.js.map +0 -1
  702. package/dist/storage/cache.d.ts +0 -23
  703. package/dist/storage/cache.d.ts.map +0 -1
  704. package/dist/storage/cache.js +0 -79
  705. package/dist/storage/cache.js.map +0 -1
  706. package/dist/storage/checksum.d.ts +0 -3
  707. package/dist/storage/checksum.d.ts.map +0 -1
  708. package/dist/storage/checksum.js +0 -19
  709. package/dist/storage/checksum.js.map +0 -1
  710. package/dist/storage/chunked.d.ts +0 -21
  711. package/dist/storage/chunked.d.ts.map +0 -1
  712. package/dist/storage/chunked.js +0 -67
  713. package/dist/storage/chunked.js.map +0 -1
  714. package/dist/storage/content.d.ts +0 -24
  715. package/dist/storage/content.d.ts.map +0 -1
  716. package/dist/storage/content.js +0 -44
  717. package/dist/storage/content.js.map +0 -1
  718. package/dist/storage/filehosting.d.ts +0 -2
  719. package/dist/storage/filehosting.d.ts.map +0 -1
  720. package/dist/storage/filehosting.js +0 -24
  721. package/dist/storage/filehosting.js.map +0 -1
  722. package/dist/storage/githubcontents.d.ts +0 -2
  723. package/dist/storage/githubcontents.d.ts.map +0 -1
  724. package/dist/storage/githubcontents.js +0 -29
  725. package/dist/storage/githubcontents.js.map +0 -1
  726. package/dist/storage/index.d.ts +0 -12
  727. package/dist/storage/index.d.ts.map +0 -1
  728. package/dist/storage/index.js +0 -12
  729. package/dist/storage/index.js.map +0 -1
  730. package/dist/storage/local.d.ts +0 -2
  731. package/dist/storage/local.d.ts.map +0 -1
  732. package/dist/storage/local.js +0 -72
  733. package/dist/storage/local.js.map +0 -1
  734. package/dist/storage/memory.d.ts +0 -6
  735. package/dist/storage/memory.d.ts.map +0 -1
  736. package/dist/storage/memory.js +0 -36
  737. package/dist/storage/memory.js.map +0 -1
  738. package/dist/storage/pool.d.ts +0 -95
  739. package/dist/storage/pool.d.ts.map +0 -1
  740. package/dist/storage/pool.js +0 -202
  741. package/dist/storage/pool.js.map +0 -1
  742. package/dist/storage/s3compatible.d.ts +0 -3
  743. package/dist/storage/s3compatible.d.ts.map +0 -1
  744. package/dist/storage/s3compatible.js +0 -70
  745. package/dist/storage/s3compatible.js.map +0 -1
  746. package/dist/storage/sync.d.ts +0 -47
  747. package/dist/storage/sync.d.ts.map +0 -1
  748. package/dist/storage/sync.js +0 -75
  749. package/dist/storage/sync.js.map +0 -1
  750. package/dist/surfaces/adapters.d.ts +0 -97
  751. package/dist/surfaces/adapters.d.ts.map +0 -1
  752. package/dist/surfaces/adapters.js +0 -50
  753. package/dist/surfaces/adapters.js.map +0 -1
  754. package/dist/surfaces/controls.d.ts +0 -18
  755. package/dist/surfaces/controls.d.ts.map +0 -1
  756. package/dist/surfaces/controls.js +0 -45
  757. package/dist/surfaces/controls.js.map +0 -1
  758. package/dist/surfaces/manifest.d.ts +0 -68
  759. package/dist/surfaces/manifest.d.ts.map +0 -1
  760. package/dist/surfaces/manifest.js +0 -42
  761. package/dist/surfaces/manifest.js.map +0 -1
  762. package/dist/surfaces/n8n.d.ts +0 -25
  763. package/dist/surfaces/n8n.d.ts.map +0 -1
  764. package/dist/surfaces/n8n.js +0 -31
  765. package/dist/surfaces/n8n.js.map +0 -1
  766. package/dist/surfaces/operations.d.ts +0 -32
  767. package/dist/surfaces/operations.d.ts.map +0 -1
  768. package/dist/surfaces/operations.js +0 -53
  769. package/dist/surfaces/operations.js.map +0 -1
  770. package/dist/surfaces/requirements.d.ts +0 -29
  771. package/dist/surfaces/requirements.d.ts.map +0 -1
  772. package/dist/surfaces/requirements.js +0 -50
  773. package/dist/surfaces/requirements.js.map +0 -1
  774. package/dist/surfaces/targets.d.ts +0 -196
  775. package/dist/surfaces/targets.d.ts.map +0 -1
  776. package/dist/surfaces/targets.js +0 -27
  777. package/dist/surfaces/targets.js.map +0 -1
  778. package/dist/webhook/delivery.d.ts +0 -18
  779. package/dist/webhook/delivery.d.ts.map +0 -1
  780. package/dist/webhook/delivery.js +0 -36
  781. package/dist/webhook/delivery.js.map +0 -1
  782. package/dist/webhook/receiver.d.ts +0 -26
  783. package/dist/webhook/receiver.d.ts.map +0 -1
  784. package/dist/webhook/receiver.js +0 -24
  785. package/dist/webhook/receiver.js.map +0 -1
  786. package/dist/webhook/signature.d.ts +0 -3
  787. package/dist/webhook/signature.d.ts.map +0 -1
  788. package/dist/webhook/signature.js +0 -7
  789. package/dist/webhook/signature.js.map +0 -1
  790. package/dist/workflow/manifest.d.ts +0 -22
  791. package/dist/workflow/manifest.d.ts.map +0 -1
  792. package/dist/workflow/manifest.js +0 -21
  793. package/dist/workflow/manifest.js.map +0 -1
  794. package/dist/workflow/registry.d.ts +0 -7
  795. package/dist/workflow/registry.d.ts.map +0 -1
  796. package/dist/workflow/registry.js +0 -18
  797. package/dist/workflow/registry.js.map +0 -1
  798. package/dist/workflow/templates.d.ts +0 -6
  799. package/dist/workflow/templates.d.ts.map +0 -1
  800. package/dist/workflow/templates.js +0 -18
  801. package/dist/workflow/templates.js.map +0 -1
  802. package/dist/workflow/triggers.d.ts +0 -64
  803. package/dist/workflow/triggers.d.ts.map +0 -1
  804. package/dist/workflow/triggers.js +0 -99
  805. package/dist/workflow/triggers.js.map +0 -1
  806. package/docs/logs/.gitkeep +0 -0
  807. package/docs/plans/00.index.md +0 -50
  808. package/docs/plans/01.architecture.md +0 -86
  809. package/docs/plans/02.research.computer.use.md +0 -58
  810. package/docs/plans/03.research.captcha.bypass.md +0 -68
  811. package/docs/plans/04.research.sandbox.ai.md +0 -52
  812. package/docs/plans/05.capture.platform.md +0 -57
  813. package/docs/plans/06.dependencies.md +0 -97
  814. package/docs/plans/07.captcha.test.page.md +0 -41
  815. package/docs/plans/08.production.infra.md +0 -70
  816. package/docs/plans/09.database.schema.md +0 -121
  817. package/docs/plans/10.cloudinary.storage.md +0 -57
  818. package/docs/plans/11.movement.logs.json.md +0 -72
  819. package/docs/plans/12.research.atlas.agent.browser.md +0 -79
  820. package/docs/plans/13.research.anti.detection.md +0 -898
  821. package/docs/plans/14.research.proxy.md +0 -1495
  822. package/docs/plans/15.research.retry.rate.limit.md +0 -1958
  823. package/docs/plans/16.research.crawling.md +0 -1417
  824. package/docs/plans/17.research.caching.md +0 -1610
  825. package/docs/plans/18.research.content.extraction.md +0 -1952
  826. package/docs/plans/19.research.errors.events.md +0 -1523
  827. package/docs/plans/20.research.zod.validation.md +0 -1350
  828. package/docs/plans/21.research.batch.concurrency.md +0 -1888
  829. package/docs/plans/22.research.universal.runtime.md +0 -944
  830. package/docs/plans/23.research.ai.integration.md +0 -1465
  831. package/docs/plans/24.research.memory.persistence.md +0 -1979
  832. package/docs/plans/25.research.server.api.md +0 -342
  833. package/docs/plans/26.research.compilation.md +0 -249
  834. package/docs/plans/27.research.html.parsing.md +0 -251
  835. package/docs/plans/28.action.plan.md +0 -50
  836. package/docs/plans/29.api.reference.md +0 -174
  837. package/docs/plans/30.architecture.plan.md +0 -94
  838. package/docs/plans/31.auditoria.dados.md +0 -163
  839. package/docs/plans/32.bots.automacao.computacional.md +0 -214
  840. package/docs/plans/33.bots.codigo.revisao.md +0 -220
  841. package/docs/plans/34.bots.seguranca.cicd.md +0 -366
  842. package/docs/plans/35.comparativo.concorrencia.md +0 -464
  843. package/docs/plans/36.computational.memory.md +0 -340
  844. package/docs/plans/37.deploystrategy.md +0 -394
  845. package/docs/plans/38.flow.md +0 -155
  846. package/docs/plans/39.multi.platform.bot.md +0 -252
  847. package/docs/plans/40.npm.publish.md +0 -250
  848. package/docs/plans/41.o.que.falta.md +0 -407
  849. package/docs/plans/42.pesquisa.concorrencia.md +0 -721
  850. package/docs/plans/43.plan.universal.architecture.md +0 -496
  851. package/docs/plans/44.reference.md +0 -100
  852. package/docs/plans/45.robotarchitecture.md +0 -237
  853. package/docs/plans/46.scdnintegration.md +0 -284
  854. package/docs/plans/47.multiforge.readme.md +0 -129
  855. package/docs/plans/48.theory.v4.repo.os.md +0 -152
  856. package/docs/plans/49.third.party.infra.md +0 -12
  857. package/docs/plans/50.file.as.compute.md +0 -39
  858. package/docs/plans/51.architecture.virtual.processor.md +0 -80
  859. package/docs/plans/52.manifesto.v8.md +0 -11
  860. package/docs/plans/58.cdn.list.md +0 -23
  861. package/docs/plans/59.sql.frameworks.md +0 -33
  862. package/docs/plans/60.sql.thirdparty.md +0 -26
  863. package/docs/plans/61.objective.multiforge.md +0 -63
  864. package/docs/plans/62.huggingface.upload.md +0 -26
  865. package/docs/plans/63.kaggle.upload.md +0 -24
  866. package/docs/plans/64.npm.storage.md +0 -30
  867. package/docs/plans/65.rclone.terabox.md +0 -32
  868. package/docs/plans/66.buckets.and.models.todo.md +0 -14
  869. package/docs/plans/67.database.todo.md +0 -13
  870. package/docs/plans/68.deploy.packages.todo.md +0 -12
  871. package/docs/plans/69.report.human.operator.md +0 -133
  872. package/docs/plans/70.report.brain2qwerty.ems.md +0 -135
  873. package/docs/plans/71.report.hd.infinito.vram.md +0 -155
  874. package/docs/plans/72.plan.hd.infinito.node.md +0 -146
  875. package/docs/plans/73.plan.scifi.repos.md +0 -125
  876. package/docs/plans/74.000.manifesto.v8.flat.2..md +0 -11
  877. package/docs/plans/README.md +0 -489
  878. package/docs/plans/aggregate_platforms.mjs +0 -146
  879. package/docs/plans/examplesession.json +0 -36
  880. package/docs/plans/missing-facts.md +0 -192
  881. package/docs/plans/models.md +0 -64
  882. package/docs/plans/organize.cjs +0 -270
  883. package/docs/plans/platforms.md +0 -2887
  884. package/docs/plans/sites.md +0 -31322
  885. package/docs/sources/farm.py +0 -117
  886. package/docs/sources/html/saddle1.html +0 -132
  887. package/docs/sources/html/saddle2.html +0 -157
  888. package/docs/sources/html/saddle3.html +0 -119
  889. package/docs/sources/html/saddle4.html +0 -144
  890. package/docs/sources/html/saddle5.html +0 -72
  891. package/docs/sources/html/saddle6.html +0 -171
  892. package/docs/sources/html/saddle7.html +0 -236
  893. package/docs/sources/saddle.ts +0 -74
  894. package/docs/sources/schema.prisma +0 -88
  895. package/docs/sources/script.sh +0 -64
  896. package/docs/sources/workflows.yml +0 -458
  897. package/docs/talks1/_body.txt +0 -14
  898. package/docs/talks1/_index.md +0 -15
  899. package/docs/talks1/_screenshot.png +0 -0
  900. package/docs/talks1/assistant-01.md +0 -5
  901. package/docs/talks1/assistant-02.md +0 -5
  902. package/docs/talks1/assistant-03.md +0 -531
  903. package/docs/talks1/assistant-04.md +0 -26
  904. package/docs/talks1/assistant-05.md +0 -774
  905. package/docs/talks1/assistant-06.md +0 -1718
  906. package/docs/talks1/scrape-share.cjs +0 -185
  907. package/docs/talks1/scrape-share.ts +0 -183
  908. package/docs/talks1/user-01.md +0 -3
  909. package/docs/talks1/user-02.md +0 -3
  910. package/docs/talks1/user-03.md +0 -88
  911. package/docs/talks1/user-04.md +0 -3
  912. package/docs/talks1/user-05.md +0 -3
  913. package/docs/talks1/user-06.md +0 -88
  914. package/docs/talks1/user-07.md +0 -88
  915. package/docs/talks2/_body.txt +0 -14
  916. package/docs/talks2/_index.md +0 -16
  917. package/docs/talks2/_screenshot.png +0 -0
  918. package/docs/talks2/assistant-01.md +0 -5
  919. package/docs/talks2/assistant-02.md +0 -5
  920. package/docs/talks2/assistant-03.md +0 -424
  921. package/docs/talks2/assistant-04.md +0 -598
  922. package/docs/talks2/assistant-05.md +0 -1280
  923. package/docs/talks2/assistant-06.md +0 -1227
  924. package/docs/talks2/assistant-07.md +0 -1252
  925. package/docs/talks2/user-01.md +0 -3
  926. package/docs/talks2/user-02.md +0 -3
  927. package/docs/talks2/user-03.md +0 -88
  928. package/docs/talks2/user-04.md +0 -88
  929. package/docs/talks2/user-05.md +0 -88
  930. package/docs/talks2/user-06.md +0 -88
  931. package/docs/talks2/user-07.md +0 -3
  932. package/docs/talks3/_body.txt +0 -467
  933. package/docs/talks3/_index.md +0 -10
  934. package/docs/talks3/_screenshot.png +0 -0
  935. package/docs/talks3/assistant-01.md +0 -417
  936. package/docs/talks3/assistant-02.md +0 -417
  937. package/docs/talks3/assistant-03.md +0 -29
  938. package/docs/talks3/assistant-04.md +0 -727
  939. package/docs/talks3/user-01.md +0 -88
  940. package/docs/talks3/user-02.md +0 -88
  941. package/docs/talks3/user-03.md +0 -3
  942. package/docs/talks3/user-04.md +0 -3
  943. package/docs/talks4/_body.txt +0 -14
  944. package/docs/talks4/_index.md +0 -12
  945. package/docs/talks4/_screenshot.png +0 -0
  946. package/docs/talks4/assistant-01.md +0 -5
  947. package/docs/talks4/assistant-02.md +0 -5
  948. package/docs/talks4/assistant-03.md +0 -35
  949. package/docs/talks4/assistant-04.md +0 -512
  950. package/docs/talks4/assistant-05.md +0 -599
  951. package/docs/talks4/user-01.md +0 -3
  952. package/docs/talks4/user-02.md +0 -3
  953. package/docs/talks4/user-03.md +0 -88
  954. package/docs/talks4/user-04.md +0 -88
  955. package/docs/talks4/user-05.md +0 -7
  956. package/docs/talks5/_body.txt +0 -14
  957. package/docs/talks5/_index.md +0 -13
  958. package/docs/talks5/_screenshot.png +0 -0
  959. package/docs/talks5/assistant-01.md +0 -5
  960. package/docs/talks5/assistant-02.md +0 -5
  961. package/docs/talks5/assistant-03.md +0 -690
  962. package/docs/talks5/assistant-04.md +0 -758
  963. package/docs/talks5/assistant-05.md +0 -974
  964. package/docs/talks5/user-01.md +0 -3
  965. package/docs/talks5/user-02.md +0 -3
  966. package/docs/talks5/user-03.md +0 -105
  967. package/docs/talks5/user-04.md +0 -105
  968. package/docs/talks5/user-05.md +0 -63
  969. package/docs/talks5/user-06.md +0 -105
  970. package/docs/talks6/_body.txt +0 -14
  971. package/docs/talks6/_index.md +0 -9
  972. package/docs/talks6/_screenshot.png +0 -0
  973. package/docs/talks6/assistant-01.md +0 -5
  974. package/docs/talks6/assistant-02.md +0 -5
  975. package/docs/talks6/assistant-03.md +0 -1499
  976. package/docs/talks6/user-01.md +0 -3
  977. package/docs/talks6/user-02.md +0 -3
  978. package/docs/talks6/user-03.md +0 -88
  979. package/docs/talks6/user-04.md +0 -88
  980. package/docs/talks7/_body.txt +0 -14
  981. package/docs/talks7/_index.md +0 -10
  982. package/docs/talks7/_screenshot.png +0 -0
  983. package/docs/talks7/assistant-01.md +0 -5
  984. package/docs/talks7/assistant-02.md +0 -5
  985. package/docs/talks7/assistant-03.md +0 -523
  986. package/docs/talks7/assistant-04.md +0 -617
  987. package/docs/talks7/user-01.md +0 -3
  988. package/docs/talks7/user-02.md +0 -3
  989. package/docs/talks7/user-03.md +0 -105
  990. package/docs/talks7/user-04.md +0 -67
  991. package/docs/talks8/conversa1.txt +0 -1322
  992. package/docs/talks8/conversa2.txt +0 -237
  993. package/docs/talks9/Beyond the Obvious_ 50 Plataformas Auto-Hospedadas de Forja de C/303/263digo para Al/303/251m de Gitea e GitLab.md" +0 -174
  994. package/docs/talks9/De NPM a Multi-Linguagem_ Uma Arquitetura T/303/251cnica para a Execu/303/247/303/243o Integrada de C/303/263digo no Ecossistema Node.js.md" +0 -59
  995. package/docs/talks9/De NPM a VMs Virtuais_ Uma An/303/241lise Arquitet/303/264nica para a Realiza/303/247/303/243o do Ciclo de Vida do Projeto SADDLE.md" +0 -91
  996. package/docs/talks9/Mapeamento da Engrenagem Computacional_ Uma Arquitetura para Execu/303/247/303/243o Isolada e Persist/303/252ncia em Ambientes Distribu/303/255dos.md" +0 -116
  997. package/docs/talks9/O Cen/303/241rio Pr/303/241tico do SADDLE_ Uma An/303/241lise de Viabilidade e Modelo de Ciclo de Vida Integrado.md" +0 -128
  998. package/docs/talks9/README (2).md +0 -489
  999. package/docs/talks9/README.md +0 -198
  1000. package/docs/talks9/Viabilidade do Saddle_ Uma An/303/241lise T/303/251cnica da Transforma/303/247/303/243o de Armazenamento Remoto em Mem/303/263ria Computacional.md" +0 -80
  1001. package/docs/talks9/conversa.txt +0 -544
  1002. package/docs/talks9/other (2).md +0 -39
  1003. package/docs/talks9/other.md +0 -57
  1004. package/docs/talks9/outro.txt +0 -24
  1005. /package/{extension/README.md → docs/extension.md} +0 -0
@@ -1,1888 +0,0 @@
1
- # Pesquisa: Batch Scraping e Concorrência (2026)
2
-
3
- ## Sumário
4
-
5
- 1. [Concurrent URL Scraping](#1-concurrent-url-scraping)
6
- 2. [Progress Tracking](#2-progress-tracking)
7
- 3. [Browser Pooling](#3-browser-pooling)
8
- 4. [Crawlee AutoscaledPool](#4-crawlee-autoscaledpool)
9
- 5. [Work Stealing/Distribution Patterns](#5-work-stealingdistribution-patterns)
10
- 6. [Graceful Degradation](#6-graceful-degradation)
11
- 7. [Layered Resilience](#7-layered-resilience)
12
- 8. [Adaptive Concurrency](#8-adaptive-concurrency)
13
- 9. [Complete Code Examples](#9-complete-code-examples)
14
-
15
- ---
16
-
17
- ## 1. Concurrent URL Scraping
18
-
19
- ### p-limit 7.3.0 (Simplest)
20
-
21
- The simplest way to limit concurrency. Returns a wrapper function that limits how many promises run simultaneously.
22
-
23
- ```typescript
24
- import pLimit from 'p-limit';
25
-
26
- // Create a limiter with max 5 concurrent operations
27
- const limit = pLimit(5);
28
-
29
- async function scrapeUrl(url: string): Promise<ScrapedItem> {
30
- const html = await fetch(url).then(r => r.text());
31
- return parseHtml(html);
32
- }
33
-
34
- // Usage
35
- const urls = [
36
- 'https://example.com/1',
37
- 'https://example.com/2',
38
- 'https://example.com/3',
39
- 'https://example.com/4',
40
- 'https://example.com/5',
41
- 'https://example.com/6',
42
- 'https://example.com/7',
43
- 'https://example.com/8',
44
- 'https://example.com/9',
45
- 'https://example.com/10',
46
- ];
47
-
48
- // limit() wraps each call — only 5 run at a time
49
- const results = await Promise.all(
50
- urls.map(url => limit(() => scrapeUrl(url)))
51
- );
52
-
53
- // You can also use limit.activeCount and limit.pendingCount
54
- console.log(`Active: ${limit.activeCount}, Pending: ${limit.pendingCount}`);
55
- ```
56
-
57
- ### p-map 7.0.4 (Best for Mapping)
58
-
59
- Like `Array.map()` but with concurrency control. Great for one-shot batch processing.
60
-
61
- ```typescript
62
- import pMap from 'p-map';
63
-
64
- const urls = [
65
- 'https://example.com/1',
66
- 'https://example.com/2',
67
- 'https://example.com/3',
68
- ];
69
-
70
- // Map with concurrency of 3
71
- const results = await pMap(
72
- urls,
73
- async (url) => {
74
- const html = await fetch(url).then(r => r.text());
75
- return parseHtml(html);
76
- },
77
- { concurrency: 3 }
78
- );
79
-
80
- // With signal (abort support)
81
- const controller = new AbortController();
82
-
83
- const results = await pMap(
84
- urls,
85
- async (url) => {
86
- const html = await fetch(url, { signal: controller.signal }).then(r => r.text());
87
- return parseHtml(html);
88
- },
89
- {
90
- concurrency: 5,
91
- signal: controller.signal,
92
- // Stop on first error (optional)
93
- stopOnError: true,
94
- }
95
- );
96
-
97
- // With individual error handling
98
- const results = await pMap(
99
- urls,
100
- async (url) => {
101
- try {
102
- return await scrapeUrl(url);
103
- } catch (error) {
104
- console.error(`Failed: ${url}`, error);
105
- return null; // Skip failed URLs
106
- }
107
- },
108
- { concurrency: 5 }
109
- );
110
-
111
- const successful = results.filter((r): r is ScrapedItem => r !== null);
112
- ```
113
-
114
- ### p-queue 9.1.2 (Best for Dynamic/Streaming)
115
-
116
- A priority queue with concurrency control. Best when URLs arrive dynamically or you need priorities.
117
-
118
- ```typescript
119
- import PQueue from 'p-queue';
120
-
121
- // Create queue with concurrency of 5
122
- const queue = new PQueue({
123
- concurrency: 5,
124
- intervalCap: 10, // Max operations per interval
125
- interval: 1000, // Interval in ms (rate limiting)
126
- timeout: 30_000, // Timeout for each item
127
- });
128
-
129
- // Add tasks with priority (lower = higher priority)
130
- queue.add(
131
- () => scrapeUrl('https://example.com/critical'),
132
- { priority: 1 } // High priority
133
- );
134
-
135
- queue.add(
136
- () => scrapeUrl('https://example.com/normal'),
137
- { priority: 5 } // Normal priority
138
- );
139
-
140
- queue.add(
141
- () => scrapeUrl('https://example.com/low'),
142
- { priority: 10 } // Low priority
143
- );
144
-
145
- // Dynamic URL submission
146
- async function processUrlStream(urlStream: AsyncIterable<string>) {
147
- for await (const url of urlStream) {
148
- queue.add(() => scrapeUrl(url));
149
- }
150
- }
151
-
152
- // Wait for completion
153
- await queue.onIdle();
154
- console.log(`Processed ${queue.size} items`);
155
-
156
- // Event listeners
157
- queue.on('active', () => {
158
- console.log(`Active: ${queue.active}, Pending: ${queue.pending}`);
159
- });
160
-
161
- queue.on('completed', (result) => {
162
- console.log('Task completed');
163
- });
164
-
165
- queue.on('error', (error) => {
166
- console.error('Task failed:', error);
167
- });
168
-
169
- queue.on('idle', () => {
170
- console.log('All tasks completed');
171
- });
172
-
173
- // Pause/resume
174
- queue.pause();
175
- // ... later
176
- queue.resume();
177
-
178
- // Clear pending tasks
179
- queue.clear();
180
- ```
181
-
182
- ### Comparison
183
-
184
- | Feature | p-limit | p-map | p-queue |
185
- |---------|---------|-------|---------|
186
- | Use case | Wrap individual calls | Map array with concurrency | Dynamic task queue |
187
- | Priority | ❌ | ❌ | ✅ |
188
- | Rate limiting | ❌ | ❌ | ✅ |
189
- | Abort signal | ❌ | ✅ | ✅ |
190
- | Events | ❌ | ❌ | ✅ |
191
- | Pause/resume | ❌ | ❌ | ✅ |
192
- | Size | ~1KB | ~2KB | ~4KB |
193
- | Complexity | Simple | Simple | Moderate |
194
-
195
- **Recommendation**: Use `p-map` for batch scraping (simplest), `p-queue` for complex workflows, `p-limit` when you need to wrap existing code.
196
-
197
- ---
198
-
199
- ## 2. Progress Tracking
200
-
201
- ### cli-progress 3.12.0 (Terminal Progress Bars)
202
-
203
- ```typescript
204
- import cliProgress from 'cli-progress';
205
-
206
- // Create progress bar
207
- const progressBar = new cliProgress.SingleBar(
208
- {
209
- format: 'Scraping |{bar}| {percentage}% | {value}/{total} | {url} | Speed: {speed} url/s | ETA: {eta_formatted}',
210
- barCompleteChar: '\u2588',
211
- barIncompleteChar: '\u2591',
212
- hideCursor: true,
213
- clearOnComplete: false,
214
- stopOnComplete: true,
215
- etaBuffer: 10, // Calculate ETA from last 10 updates
216
- },
217
- cliProgress.Presets.shades_classic
218
- );
219
-
220
- // Start with total
221
- progressBar.start(totalUrls, 0, { url: '', speed: 0 });
222
-
223
- // Update for each completed URL
224
- for (const url of urls) {
225
- try {
226
- const result = await scrapeUrl(url);
227
- progressBar.increment(1, {
228
- url: truncateUrl(url, 40),
229
- speed: (progressBar.getCurrent() / ((Date.now() - startTime) / 1000)).toFixed(1),
230
- });
231
- } catch (error) {
232
- progressBar.increment(1, { url: `FAILED: ${truncateUrl(url, 30)}` });
233
- }
234
- }
235
-
236
- progressBar.stop();
237
-
238
- function truncateUrl(url: string, maxLen: number): string {
239
- try {
240
- const parsed = new URL(url);
241
- const path = parsed.pathname + parsed.search;
242
- if (path.length > maxLen) return path.slice(0, maxLen - 3) + '...';
243
- return path;
244
- } catch {
245
- return url.slice(0, maxLen);
246
- }
247
- }
248
- ```
249
-
250
- ### Multi-Progress Bars
251
-
252
- ```typescript
253
- import MultiProgress from 'multi-progress';
254
- import cliProgress from 'cli-progress';
255
-
256
- const multi = new MultiProgress(process.stderr);
257
-
258
- // Multiple concurrent progress bars
259
- const bars = new Map<string, cliProgress.SingleBar>();
260
-
261
- function createBar(name: string, total: number): cliProgress.SingleBar {
262
- const bar = multi.newBar(
263
- `${name} |{bar}| {percentage}% | {value}/{total}`,
264
- {
265
- complete: '\u2588',
266
- incomplete: '\u2591',
267
- width: 30,
268
- }
269
- );
270
- bar.start(total, 0);
271
- bars.set(name, bar);
272
- return bar;
273
- }
274
-
275
- // Usage
276
- const mainBar = createBar('Total ', urls.length);
277
- const successBar = createBar('Success ', 0);
278
- const errorBar = createBar('Errors ', 0);
279
-
280
- // Process with p-limit
281
- const limit = pLimit(5);
282
-
283
- await Promise.all(
284
- urls.map(url =>
285
- limit(async () => {
286
- try {
287
- await scrapeUrl(url);
288
- mainBar.increment(1);
289
- successBar.increment(1);
290
- } catch {
291
- mainBar.increment(1);
292
- errorBar.increment(1);
293
- }
294
- })
295
- )
296
- );
297
-
298
- // Stop all bars
299
- bars.forEach(bar => bar.stop());
300
- ```
301
-
302
- ### ora (Spinner for Single Operations)
303
-
304
- ```typescript
305
- import ora from 'ora';
306
-
307
- const spinner = ora('Starting scraper...').start();
308
-
309
- try {
310
- // Phase 1
311
- spinner.text = 'Launching browser...';
312
- const browser = await launchBrowser();
313
- spinner.succeed('Browser launched');
314
-
315
- // Phase 2
316
- spinner.start('Scraping URLs...');
317
- let completed = 0;
318
- for (const url of urls) {
319
- await scrapeUrl(url);
320
- completed++;
321
- spinner.text = `Scraping... (${completed}/${urls.length})`;
322
- }
323
- spinner.succeed(`Scraped ${urls.length} URLs`);
324
-
325
- } catch (error) {
326
- spinner.fail(`Failed: ${error.message}`);
327
- }
328
- ```
329
-
330
- ### Combined Progress Pattern
331
-
332
- ```typescript
333
- import cliProgress from 'cli-progress';
334
- import ora from 'ora';
335
- import PQueue from 'p-queue';
336
-
337
- interface ProgressState {
338
- total: number;
339
- completed: number;
340
- succeeded: number;
341
- failed: number;
342
- activeRequests: number;
343
- startTime: number;
344
- }
345
-
346
- class ProgressTracker {
347
- private bar: cliProgress.SingleBar;
348
- private spinner: ora.Ora;
349
- private state: ProgressState;
350
-
351
- constructor(total: number) {
352
- this.state = {
353
- total,
354
- completed: 0,
355
- succeeded: 0,
356
- failed: 0,
357
- activeRequests: 0,
358
- startTime: Date.now(),
359
- };
360
-
361
- this.bar = new cliProgress.SingleBar({
362
- format: 'Progress |{bar}| {percentage}% | {value}/{total} | ✓ {succeeded} ✗ {failed} | {speed} url/s',
363
- barCompleteChar: '\u2588',
364
- barIncompleteChar: '\u2591',
365
- hideCursor: true,
366
- });
367
-
368
- this.spinner = ora({ spinner: 'dots' });
369
- }
370
-
371
- start(): void {
372
- this.bar.start(this.state.total, 0);
373
- }
374
-
375
- update(type: 'success' | 'error'): void {
376
- this.state.completed++;
377
- if (type === 'success') this.state.succeeded++;
378
- else this.state.failed++;
379
-
380
- const elapsed = (Date.now() - this.state.startTime) / 1000;
381
- const speed = (this.state.completed / elapsed).toFixed(1);
382
-
383
- this.bar.update(this.state.completed, {
384
- succeeded: this.state.succeeded,
385
- failed: this.state.failed,
386
- speed,
387
- });
388
- }
389
-
390
- setActiveRequests(count: number): void {
391
- this.state.activeRequests = count;
392
- this.spinner.text = `Active requests: ${count}`;
393
- }
394
-
395
- stop(): void {
396
- this.bar.stop();
397
- const elapsed = ((Date.now() - this.state.startTime) / 1000).toFixed(1);
398
- console.log(`\nCompleted in ${elapsed}s`);
399
- console.log(` Total: ${this.state.total}`);
400
- console.log(` Succeeded: ${this.state.succeeded}`);
401
- console.log(` Failed: ${this.state.failed}`);
402
- }
403
- }
404
-
405
- // Usage
406
- const progress = new ProgressTracker(urls.length);
407
- progress.start();
408
-
409
- const queue = new PQueue({ concurrency: 5 });
410
-
411
- for (const url of urls) {
412
- queue.add(async () => {
413
- try {
414
- await scrapeUrl(url);
415
- progress.update('success');
416
- } catch {
417
- progress.update('error');
418
- }
419
- });
420
- }
421
-
422
- await queue.onIdle();
423
- progress.stop();
424
- ```
425
-
426
- ---
427
-
428
- ## 3. Browser Pooling
429
-
430
- ### Core Pattern: Acquire/Release
431
-
432
- ```typescript
433
- import { chromium, type Browser, type BrowserContext } from 'playwright';
434
-
435
- interface BrowserPoolOptions {
436
- minBrowsers: number;
437
- maxBrowsers: number;
438
- maxPagesPerBrowser: number;
439
- idleTimeout: number;
440
- launchTimeout: number;
441
- }
442
-
443
- interface PooledBrowser {
444
- browser: Browser;
445
- contexts: BrowserContext[];
446
- lastUsed: number;
447
- busy: boolean;
448
- }
449
-
450
- class BrowserPool {
451
- private browsers: PooledBrowser[] = [];
452
- private waitQueue: Array<{
453
- resolve: (context: BrowserContext) => void;
454
- reject: (error: Error) => void;
455
- timeout: ReturnType<typeof setTimeout>;
456
- }> = [];
457
- private options: BrowserPoolOptions;
458
- private cleanupInterval: ReturnType<typeof setInterval>;
459
-
460
- constructor(options: Partial<BrowserPoolOptions> = {}) {
461
- this.options = {
462
- minBrowsers: 1,
463
- maxBrowsers: 5,
464
- maxPagesPerBrowser: 10,
465
- idleTimeout: 60_000,
466
- launchTimeout: 30_000,
467
- ...options,
468
- };
469
-
470
- // Pre-launch minimum browsers
471
- this.initialize();
472
-
473
- // Cleanup idle browsers periodically
474
- this.cleanupInterval = setInterval(
475
- () => this.cleanupIdleBrowsers(),
476
- this.options.idleTimeout / 2
477
- );
478
- }
479
-
480
- private async initialize(): Promise<void> {
481
- for (let i = 0; i < this.options.minBrowsers; i++) {
482
- await this.launchBrowser();
483
- }
484
- }
485
-
486
- private async launchBrowser(): Promise<PooledBrowser> {
487
- const browser = await chromium.launch({
488
- headless: true,
489
- args: ['--no-sandbox', '--disable-setuid-sandbox'],
490
- });
491
-
492
- const pooled: PooledBrowser = {
493
- browser,
494
- contexts: [],
495
- lastUsed: Date.now(),
496
- busy: false,
497
- };
498
-
499
- this.browsers.push(pooled);
500
- return pooled;
501
- }
502
-
503
- private findAvailableBrowser(): PooledBrowser | null {
504
- // Find a browser with available pages
505
- for (const pooled of this.browsers) {
506
- if (!pooled.busy && pooled.contexts.length < this.options.maxPagesPerBrowser) {
507
- return pooled;
508
- }
509
- }
510
- return null;
511
- }
512
-
513
- private async waitForBrowser(): Promise<PooledBrowser> {
514
- return new Promise(async (resolve, reject) => {
515
- const timeout = setTimeout(() => {
516
- const idx = this.waitQueue.findIndex(w => w.resolve === resolve);
517
- if (idx !== -1) this.waitQueue.splice(idx, 1);
518
- reject(new Error('Timeout waiting for available browser'));
519
- }, this.options.launchTimeout);
520
-
521
- this.waitQueue.push({ resolve, reject, timeout });
522
-
523
- // Try to launch a new browser if under limit
524
- if (this.browsers.length < this.options.maxBrowsers) {
525
- try {
526
- const pooled = await this.launchBrowser();
527
- // Resolve one waiter with this new browser
528
- const waiter = this.waitQueue.shift();
529
- if (waiter) {
530
- clearTimeout(waiter.timeout);
531
- waiter.resolve(pooled);
532
- }
533
- } catch (error) {
534
- console.error('Failed to launch browser:', error);
535
- }
536
- }
537
- });
538
- }
539
-
540
- /**
541
- * Acquire a browser context for scraping.
542
- * Returns a context that must be released when done.
543
- */
544
- async acquire(): Promise<{ context: BrowserContext; release: () => Promise<void> }> {
545
- let pooled = this.findAvailableBrowser();
546
-
547
- if (!pooled) {
548
- pooled = await this.waitForBrowser();
549
- }
550
-
551
- const context = await pooled.browser.newContext({
552
- userAgent: this.getRandomUserAgent(),
553
- viewport: {
554
- width: 1920 + Math.floor(Math.random() * 100),
555
- height: 1080 + Math.floor(Math.random() * 100),
556
- },
557
- });
558
-
559
- pooled.contexts.push(context);
560
- pooled.lastUsed = Date.now();
561
-
562
- let released = false;
563
- const release = async (): Promise<void> => {
564
- if (released) return;
565
- released = true;
566
-
567
- try {
568
- await context.close();
569
- } catch {
570
- // Context may already be closed
571
- }
572
-
573
- pooled.contexts = pooled.contexts.filter(c => c !== context);
574
- pooled.lastUsed = Date.now();
575
-
576
- // Check if any waiters need this browser
577
- if (this.waitQueue.length > 0 && pooled.contexts.length < this.options.maxPagesPerBrowser) {
578
- const waiter = this.waitQueue.shift();
579
- if (waiter) {
580
- clearTimeout(waiter.timeout);
581
- waiter.resolve(pooled);
582
- }
583
- }
584
- };
585
-
586
- return { context, release };
587
- }
588
-
589
- private async cleanupIdleBrowsers(): Promise<void> {
590
- const now = Date.now();
591
- const toRemove: PooledBrowser[] = [];
592
-
593
- for (const pooled of this.browsers) {
594
- if (
595
- pooled.contexts.length === 0 &&
596
- now - pooled.lastUsed > this.options.idleTimeout &&
597
- this.browsers.length - toRemove.length > this.options.minBrowsers
598
- ) {
599
- toRemove.push(pooled);
600
- }
601
- }
602
-
603
- for (const pooled of toRemove) {
604
- try {
605
- await pooled.browser.close();
606
- } catch {
607
- // Ignore close errors
608
- }
609
- this.browsers = this.browsers.filter(b => b !== pooled);
610
- }
611
- }
612
-
613
- /**
614
- * Graceful cleanup — close all browsers and reject pending waiters.
615
- */
616
- async destroy(): Promise<void> {
617
- clearInterval(this.cleanupInterval);
618
-
619
- // Reject all pending waiters
620
- for (const waiter of this.waitQueue) {
621
- clearTimeout(waiter.timeout);
622
- waiter.reject(new Error('Browser pool is shutting down'));
623
- }
624
- this.waitQueue = [];
625
-
626
- // Close all browsers
627
- for (const pooled of this.browsers) {
628
- for (const ctx of pooled.contexts) {
629
- try {
630
- await ctx.close();
631
- } catch { /* ignore */ }
632
- }
633
- try {
634
- await pooled.browser.close();
635
- } catch { /* ignore */ }
636
- }
637
- this.browsers = [];
638
- }
639
-
640
- private getRandomUserAgent(): string {
641
- const agents = [
642
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0',
643
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0',
644
- 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0',
645
- ];
646
- return agents[Math.floor(Math.random() * agents.length)];
647
- }
648
-
649
- get stats() {
650
- return {
651
- totalBrowsers: this.browsers.length,
652
- activeBrowsers: this.browsers.filter(b => b.contexts.length > 0).length,
653
- totalContexts: this.browsers.reduce((sum, b) => sum + b.contexts.length, 0),
654
- pendingRequests: this.waitQueue.length,
655
- };
656
- }
657
- }
658
-
659
- // Usage
660
- const pool = new BrowserPool({ maxBrowsers: 3, maxPagesPerBrowser: 5 });
661
-
662
- async function scrapeWithBrowser(url: string): Promise<string> {
663
- const { context, release } = await pool.acquire();
664
-
665
- try {
666
- const page = await context.newPage();
667
- await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
668
- const content = await page.content();
669
- return content;
670
- } finally {
671
- await release();
672
- }
673
- }
674
-
675
- // Process URLs with browser pool
676
- const limit = pLimit(5);
677
-
678
- await Promise.all(
679
- urls.map(url =>
680
- limit(async () => {
681
- const html = await scrapeWithBrowser(url);
682
- return parseHtml(html);
683
- })
684
- )
685
- );
686
-
687
- // Graceful shutdown
688
- await pool.destroy();
689
- ```
690
-
691
- ---
692
-
693
- ## 4. Crawlee AutoscaledPool
694
-
695
- Crawlee's `AutoscaledPool` dynamically adjusts concurrency based on system load and success rate. It's the gold standard for adaptive scraping.
696
-
697
- ### Core Concepts
698
-
699
- ```typescript
700
- import { AutoscaledPool } from 'crawlee';
701
-
702
- const pool = new AutoscaledPool({
703
- // Min/max concurrency bounds
704
- minConcurrency: 1,
705
- maxConcurrency: 20,
706
-
707
- // System metrics
708
- snapshotterOptions: {
709
- // CPU/memory thresholds
710
- cpuSnapshotInterval: 1000,
711
- memSnapshotInterval: 2000,
712
- },
713
-
714
- // Scaling behavior
715
- scaleUpInterval: 1000, // Check every 1s
716
- scaleDownInterval: 5000, // Check every 5s
717
- maxScaledUpRate: 2, // Max 2x scale-up per interval
718
- maxScaledDownRate: 0.5, // Max 0.5x scale-down per interval
719
-
720
- // Task function
721
- taskFunction: async () => {
722
- const url = getNextUrl();
723
- if (!url) return false; // No more tasks
724
-
725
- try {
726
- await scrapeUrl(url);
727
- return true; // Task completed
728
- } catch (error) {
729
- if (isRetryable(error)) {
730
- // Return the URL to the queue
731
- addUrlBack(url);
732
- return true; // Task "completed" (will be retried)
733
- }
734
- throw error; // Non-retryable error
735
- }
736
- },
737
- });
738
-
739
- // Start the pool
740
- await pool.run();
741
- ```
742
-
743
- ### How AutoscaledPool Works
744
-
745
- 1. **Snapshotter**: Continuously monitors CPU and memory usage
746
- 2. **Scaler**: Adjusts concurrency based on metrics:
747
- - If CPU < 50% and memory < 70% → scale up
748
- - If CPU > 80% or memory > 85% → scale down
749
- 3. **Task runner**: Runs tasks at current concurrency level
750
- 4. **Idle detection**: Stops when no more tasks are available
751
-
752
- ### Custom Scaling Logic
753
-
754
- ```typescript
755
- import { AutoscaledPool, AutoscaledPoolOptions } from 'crawlee';
756
-
757
- class SmartAutoscaledPool extends AutoscaledPool {
758
- private errorRate = 0;
759
- private recentErrors = 0;
760
- private recentSuccesses = 0;
761
-
762
- constructor(options: AutoscaledPoolOptions) {
763
- super({
764
- ...options,
765
- // Custom scaling logic
766
- desiredConcurrencyCallback: () => {
767
- // If error rate is high, reduce concurrency
768
- if (this.errorRate > 0.3) {
769
- return Math.max(1, this.desiredConcurrency - 2);
770
- }
771
- // If error rate is low and system is healthy, increase
772
- if (this.errorRate < 0.1 && this.isSystemHealthy()) {
773
- return Math.min(
774
- this.maxConcurrency,
775
- this.desiredConcurrency + 1
776
- );
777
- }
778
- return this.desiredConcurrency;
779
- },
780
- });
781
- }
782
-
783
- trackSuccess(): void {
784
- this.recentSuccesses++;
785
- this.updateErrorRate();
786
- }
787
-
788
- trackError(): void {
789
- this.recentErrors++;
790
- this.updateErrorRate();
791
- }
792
-
793
- private updateErrorRate(): void {
794
- const total = this.recentErrors + this.recentSuccesses;
795
- if (total >= 100) {
796
- this.errorRate = this.recentErrors / total;
797
- this.recentErrors = 0;
798
- this.recentSuccesses = 0;
799
- }
800
- }
801
-
802
- private isSystemHealthy(): boolean {
803
- // Check CPU and memory
804
- const memUsage = process.memoryUsage();
805
- const memPercent = memUsage.heapUsed / memUsage.heapTotal;
806
- return memPercent < 0.7;
807
- }
808
- }
809
- ```
810
-
811
- ---
812
-
813
- ## 5. Work Stealing/Distribution Patterns
814
-
815
- ### Producer-Consumer Pattern
816
-
817
- ```typescript
818
- import PQueue from 'p-queue';
819
- import { EventEmitter } from 'events';
820
-
821
- interface ScrapingTask {
822
- id: string;
823
- url: string;
824
- priority: number;
825
- retries: number;
826
- maxRetries: number;
827
- }
828
-
829
- class WorkDistributor extends EventEmitter {
830
- private queue: PQueue;
831
- private workers: Map<string, Worker> = new Map();
832
- private tasks: Map<string, ScrapingTask> = new Map();
833
-
834
- constructor(concurrency: number = 10) {
835
- super();
836
- this.queue = new PQueue({ concurrency });
837
- }
838
-
839
- addTask(task: ScrapingTask): void {
840
- this.tasks.set(task.id, task);
841
- this.queue.add(() => this.executeTask(task));
842
- }
843
-
844
- addTasks(tasks: ScrapingTask[]): void {
845
- tasks.forEach(t => this.addTask(t));
846
- }
847
-
848
- private async executeTask(task: ScrapingTask): Promise<void> {
849
- this.emit('task:start', task);
850
-
851
- try {
852
- const result = await scrapeUrl(task.url);
853
- this.tasks.delete(task.id);
854
- this.emit('task:complete', { task, result });
855
- } catch (error) {
856
- if (task.retries < task.maxRetries) {
857
- // Re-queue with incremented retry count
858
- task.retries++;
859
- this.emit('task:retry', { task, attempt: task.retries, error });
860
- this.queue.add(() => this.executeTask(task));
861
- } else {
862
- this.tasks.delete(task.id);
863
- this.emit('task:failed', { task, error });
864
- }
865
- }
866
- }
867
-
868
- get stats() {
869
- return {
870
- pending: this.queue.pending,
871
- active: this.queue.active,
872
- totalTasks: this.tasks.size,
873
- isIdle: this.queue.idle,
874
- };
875
- }
876
-
877
- async drain(): Promise<void> {
878
- await this.queue.onIdle();
879
- }
880
- }
881
-
882
- // Usage
883
- const distributor = new WorkDistributor(10);
884
-
885
- distributor.on('task:complete', ({ task, result }) => {
886
- console.log(`✓ ${task.url}`);
887
- });
888
-
889
- distributor.on('task:failed', ({ task, error }) => {
890
- console.error(`✗ ${task.url}: ${error.message}`);
891
- });
892
-
893
- distributor.addTasks(
894
- urls.map((url, i) => ({
895
- id: `task-${i}`,
896
- url,
897
- priority: 5,
898
- retries: 0,
899
- maxRetries: 3,
900
- }))
901
- );
902
-
903
- await distributor.drain();
904
- ```
905
-
906
- ### Domain-Based Distribution
907
-
908
- Distribute work by domain to respect per-domain rate limits.
909
-
910
- ```typescript
911
- import PQueue from 'p-queue';
912
-
913
- class DomainDistributor {
914
- private domainQueues = new Map<string, PQueue>();
915
- private globalQueue: PQueue;
916
-
917
- constructor(
918
- private globalConcurrency: number = 50,
919
- private perDomainConcurrency: number = 3
920
- ) {
921
- this.globalQueue = new PQueue({ concurrency: globalConcurrency });
922
- }
923
-
924
- private getDomainQueue(domain: string): PQueue {
925
- if (!this.domainQueues.has(domain)) {
926
- const queue = new PQueue({
927
- concurrency: this.perDomainConcurrency,
928
- interval: 1000, // Rate limit: 1 request per second
929
- intervalCap: 5, // Max 5 requests per interval
930
- });
931
- this.domainQueues.set(domain, queue);
932
- }
933
- return this.domainQueues.get(domain)!;
934
- }
935
-
936
- async add(url: string, task: () => Promise<unknown>): Promise<void> {
937
- const domain = new URL(url).hostname;
938
- const domainQueue = this.getDomainQueue(domain);
939
-
940
- // Wrap domain-specific task in global queue
941
- this.globalQueue.add(() => domainQueue.add(task));
942
- }
943
-
944
- async drain(): Promise<void> {
945
- await this.globalQueue.onIdle();
946
- // Wait for all domain queues too
947
- for (const queue of this.domainQueues.values()) {
948
- await queue.onIdle();
949
- }
950
- }
951
-
952
- getStats() {
953
- const domainStats: Record<string, { pending: number; active: number }> = {};
954
- for (const [domain, queue] of this.domainQueues) {
955
- domainStats[domain] = {
956
- pending: queue.pending,
957
- active: queue.active,
958
- };
959
- }
960
- return {
961
- global: {
962
- pending: this.globalQueue.pending,
963
- active: this.globalQueue.active,
964
- },
965
- domains: domainStats,
966
- };
967
- }
968
- }
969
-
970
- // Usage
971
- const distributor = new DomainDistributor(50, 3);
972
-
973
- await Promise.all(
974
- urls.map(url =>
975
- distributor.add(url, async () => {
976
- const html = await fetch(url).then(r => r.text());
977
- return parseHtml(html);
978
- })
979
- )
980
- );
981
- ```
982
-
983
- ---
984
-
985
- ## 6. Graceful Degradation
986
-
987
- ### Circuit Breaker (cockatiel 4.0.0)
988
-
989
- The circuit breaker pattern prevents cascading failures by stopping requests to a failing service.
990
-
991
- ```typescript
992
- import { CircuitBreaker, handle } from 'cockatiel';
993
-
994
- // Create a circuit breaker
995
- const breaker = CircuitBreaker.byPercentage({
996
- threshold: 50, // Open circuit when 50% of requests fail
997
- duration: 30_000, // Stay open for 30 seconds
998
- halfOpenAfter: 10_000, // Try again after 10 seconds
999
- });
1000
-
1001
- // Wrap fetch calls with the circuit breaker
1002
- async function resilientFetch(url: string): Promise<Response> {
1003
- return handle(
1004
- breaker,
1005
- () => fetch(url),
1006
- (error) => {
1007
- // Fallback when circuit is open
1008
- console.warn(`Circuit open for ${new URL(url).hostname}, using fallback`);
1009
- return new Response(JSON.stringify({ error: 'Service unavailable' }), {
1010
- status: 503,
1011
- headers: { 'Content-Type': 'application/json' },
1012
- });
1013
- }
1014
- ).promise();
1015
- }
1016
-
1017
- // Or with more control
1018
- const circuit = CircuitBreaker.byPercentage({
1019
- threshold: 50,
1020
- duration: 30_000,
1021
- halfOpenAfter: 10_000,
1022
- });
1023
-
1024
- // Listen for state changes
1025
- circuit.onBreak(() => console.log('Circuit OPEN — requests blocked'));
1026
- circuit.onReset(() => console.log('Circuit CLOSED — requests allowed'));
1027
- circuit.onHalfOpen(() => console.log('Circuit HALF-OPEN — testing'));
1028
-
1029
- // Manual circuit breaker
1030
- async function fetchWithCircuitBreaker(url: string): Promise<Response> {
1031
- const result = await handle(
1032
- circuit,
1033
- () => fetch(url),
1034
- () => {
1035
- // Fallback
1036
- return new Response('{}', { status: 503 });
1037
- }
1038
- ).promise();
1039
-
1040
- return result;
1041
- }
1042
- ```
1043
-
1044
- ### Bulkhead Isolation
1045
-
1046
- Bulkhead pattern isolates failures to specific domains or operation types.
1047
-
1048
- ```typescript
1049
- import PQueue from 'p-queue';
1050
-
1051
- class BulkheadPool {
1052
- private bulkheads = new Map<string, PQueue>();
1053
- private maxPerBulkhead: number;
1054
- private maxTotal: number;
1055
- private totalActive = 0;
1056
-
1057
- constructor(maxPerBulkhead: number = 5, maxTotal: number = 50) {
1058
- this.maxPerBulkhead = maxPerBulkhead;
1059
- this.maxTotal = maxTotal;
1060
- }
1061
-
1062
- private getBulkhead(name: string): PQueue {
1063
- if (!this.bulkheads.has(name)) {
1064
- this.bulkheads.set(name, new PQueue({
1065
- concurrency: this.maxPerBulkhead,
1066
- }));
1067
- }
1068
- return this.bulkheads.get(name)!;
1069
- }
1070
-
1071
- async execute<T>(bulkheadName: string, fn: () => Promise<T>): Promise<T> {
1072
- if (this.totalActive >= this.maxTotal) {
1073
- throw new Error('Total concurrency limit reached');
1074
- }
1075
-
1076
- const bulkhead = this.getBulkhead(bulkheadName);
1077
-
1078
- this.totalActive++;
1079
- try {
1080
- return await bulkhead.add(fn);
1081
- } finally {
1082
- this.totalActive--;
1083
- }
1084
- }
1085
-
1086
- getStats() {
1087
- const stats: Record<string, { pending: number; active: number }> = {};
1088
- for (const [name, queue] of this.bulkheads) {
1089
- stats[name] = {
1090
- pending: queue.pending,
1091
- active: queue.active,
1092
- };
1093
- }
1094
- return {
1095
- totalActive: this.totalActive,
1096
- bulkheads: stats,
1097
- };
1098
- }
1099
- }
1100
-
1101
- // Usage: isolate failures by domain
1102
- const bulkhead = new BulkheadPool(3, 30);
1103
-
1104
- await Promise.all(
1105
- urls.map(url => {
1106
- const domain = new URL(url).hostname;
1107
- return bulkhead.execute(domain, async () => {
1108
- const html = await fetch(url).then(r => r.text());
1109
- return parseHtml(html);
1110
- });
1111
- })
1112
- );
1113
- ```
1114
-
1115
- ### Fallback Strategies
1116
-
1117
- ```typescript
1118
- interface FallbackStrategy<T> {
1119
- name: string;
1120
- execute: () => Promise<T>;
1121
- condition?: (error: Error) => boolean;
1122
- }
1123
-
1124
- class FallbackChain<T> {
1125
- private strategies: FallbackStrategy<T>[] = [];
1126
-
1127
- add(strategy: FallbackStrategy<T>): this {
1128
- this.strategies.push(strategy);
1129
- return this;
1130
- }
1131
-
1132
- async execute(): Promise<T> {
1133
- let lastError: Error | undefined;
1134
-
1135
- for (const strategy of this.strategies) {
1136
- if (strategy.condition && lastError && !strategy.condition(lastError)) {
1137
- continue;
1138
- }
1139
-
1140
- try {
1141
- return await strategy.execute();
1142
- } catch (error) {
1143
- lastError = error instanceof Error ? error : new Error(String(error));
1144
- console.warn(`Fallback "${strategy.name}" failed: ${lastError.message}`);
1145
- }
1146
- }
1147
-
1148
- throw lastError ?? new Error('All fallback strategies failed');
1149
- }
1150
- }
1151
-
1152
- // Usage
1153
- const fetchWithFallback = new FallbackChain<string>()
1154
- .add({
1155
- name: 'primary-fetch',
1156
- execute: async () => {
1157
- const res = await fetch(url);
1158
- return res.text();
1159
- },
1160
- })
1161
- .add({
1162
- name: 'cache',
1163
- condition: (err) => err.message.includes('fetch'),
1164
- execute: async () => {
1165
- const cached = await cache.get(url);
1166
- if (!cached) throw new Error('No cache');
1167
- return cached;
1168
- },
1169
- })
1170
- .add({
1171
- name: 'archive.org',
1172
- condition: (err) => err.message.includes('404'),
1173
- execute: async () => {
1174
- const res = await fetch(`https://web.archive.org/web/2024/${url}`);
1175
- return res.text();
1176
- },
1177
- })
1178
- .add({
1179
- name: 'default',
1180
- execute: async () => '<html><body>Content unavailable</body></html>',
1181
- });
1182
-
1183
- const html = await fetchWithFallback.execute();
1184
- ```
1185
-
1186
- ---
1187
-
1188
- ## 7. Layered Resilience
1189
-
1190
- ### Retry + Circuit Breaker + Timeout Stack
1191
-
1192
- ```typescript
1193
- import { CircuitBreaker, handle, retry, timeout } from 'cockatiel';
1194
-
1195
- // Layer 1: Retry with exponential backoff
1196
- const retryPolicy = retry({
1197
- maxAttempts: 3,
1198
- backoff: {
1199
- initialDelay: 1000,
1200
- maxDelay: 10_000,
1201
- multiplier: 2,
1202
- jitter: true,
1203
- },
1204
- handle: (error) => {
1205
- // Only retry specific errors
1206
- return error instanceof TypeError ||
1207
- (error instanceof ScraperError && error.retryable);
1208
- },
1209
- });
1210
-
1211
- // Layer 2: Circuit breaker
1212
- const circuitBreaker = CircuitBreaker.byPercentage({
1213
- threshold: 50,
1214
- duration: 30_000,
1215
- halfOpenAfter: 10_000,
1216
- });
1217
-
1218
- // Layer 3: Timeout
1219
- const timeoutPolicy = timeout(30_000);
1220
-
1221
- // Combine all layers
1222
- async function resilientScrape(url: string): Promise<string> {
1223
- // Stack: timeout → circuit breaker → retry → fetch
1224
- const result = await handle(
1225
- circuitBreaker,
1226
- () =>
1227
- retryPolicy.execute(async () => {
1228
- const response = await timeoutPolicy.execute(async () => {
1229
- const res = await fetch(url, {
1230
- signal: AbortSignal.timeout(30_000),
1231
- });
1232
- return res;
1233
- });
1234
- return response.text();
1235
- }),
1236
- (error) => {
1237
- // Circuit breaker fallback
1238
- console.warn(`Circuit open, fallback for ${url}`);
1239
- return getCachedVersion(url) ?? '';
1240
- }
1241
- ).promise();
1242
-
1243
- return result;
1244
- }
1245
- ```
1246
-
1247
- ### Complete Resilient Scraper
1248
-
1249
- ```typescript
1250
- import { CircuitBreaker, handle, retry, timeout, bulkhead } from 'cockatiel';
1251
- import PQueue from 'p-queue';
1252
-
1253
- interface ResilientScraperOptions {
1254
- maxConcurrency: number;
1255
- maxRetries: number;
1256
- timeout: number;
1257
- circuitBreakerThreshold: number;
1258
- circuitBreakerResetTimeout: number;
1259
- perDomainConcurrency: number;
1260
- }
1261
-
1262
- class ResilientScraper {
1263
- private queue: PQueue;
1264
- private circuits = new Map<string, CircuitBreaker>();
1265
- private options: ResilientScraperOptions;
1266
-
1267
- constructor(options: Partial<ResilientScraperOptions> = {}) {
1268
- this.options = {
1269
- maxConcurrency: 10,
1270
- maxRetries: 3,
1271
- timeout: 30_000,
1272
- circuitBreakerThreshold: 50,
1273
- circuitBreakerResetTimeout: 30_000,
1274
- perDomainConcurrency: 3,
1275
- ...options,
1276
- };
1277
-
1278
- this.queue = new PQueue({ concurrency: this.options.maxConcurrency });
1279
- }
1280
-
1281
- private getCircuit(domain: string): CircuitBreaker {
1282
- if (!this.circuits.has(domain)) {
1283
- this.circuits.set(
1284
- domain,
1285
- CircuitBreaker.byPercentage({
1286
- threshold: this.options.circuitBreakerThreshold,
1287
- duration: this.options.circuitBreakerResetTimeout,
1288
- halfOpenAfter: this.options.circuitBreakerResetTimeout / 3,
1289
- })
1290
- );
1291
- }
1292
- return this.circuits.get(domain)!;
1293
- }
1294
-
1295
- async scrape(url: string): Promise<ScrapedItem> {
1296
- const domain = new URL(url).hostname;
1297
- const circuit = this.getCircuit(domain);
1298
-
1299
- return this.queue.add(async () => {
1300
- const result = await handle(
1301
- circuit,
1302
- () =>
1303
- retry({
1304
- maxAttempts: this.options.maxRetries,
1305
- backoff: {
1306
- initialDelay: 1000,
1307
- maxDelay: 15_000,
1308
- multiplier: 2,
1309
- jitter: true,
1310
- },
1311
- }).execute(async () => {
1312
- const response = await timeout(this.options.timeout).execute(async () => {
1313
- const res = await fetch(url, {
1314
- signal: AbortSignal.timeout(this.options.timeout),
1315
- });
1316
- if (!res.ok) {
1317
- throw new HttpError(`HTTP ${res.status}`, res.status);
1318
- }
1319
- return res;
1320
- });
1321
-
1322
- const html = await response.text();
1323
- return this.parseHtml(html, url);
1324
- }),
1325
- (error) => {
1326
- console.warn(`Circuit open for ${domain}, using cache`);
1327
- return this.getCachedResult(url);
1328
- }
1329
- ).promise();
1330
-
1331
- // Store in cache
1332
- this.cacheResult(url, result);
1333
-
1334
- return result;
1335
- }) as Promise<ScrapedItem>;
1336
- }
1337
-
1338
- async scrapeAll(urls: string[]): Promise<Map<string, ScrapedItem | null>> {
1339
- const results = new Map<string, ScrapedItem | null>();
1340
-
1341
- await Promise.all(
1342
- urls.map(async (url) => {
1343
- try {
1344
- const result = await this.scrape(url);
1345
- results.set(url, result);
1346
- } catch (error) {
1347
- console.error(`Failed: ${url}`, error);
1348
- results.set(url, null);
1349
- }
1350
- })
1351
- );
1352
-
1353
- return results;
1354
- }
1355
-
1356
- private async parseHtml(html: string, url: string): Promise<ScrapedItem> {
1357
- // ... parsing logic
1358
- return { url, title: '', content: html };
1359
- }
1360
-
1361
- private cacheResult(url: string, result: ScrapedItem): void {
1362
- // Implement caching
1363
- }
1364
-
1365
- private getCachedResult(url: string): ScrapedItem | null {
1366
- // Return cached result or null
1367
- return null;
1368
- }
1369
- }
1370
- ```
1371
-
1372
- ---
1373
-
1374
- ## 8. Adaptive Concurrency
1375
-
1376
- ### Dynamic Concurrency Based on Error Rate
1377
-
1378
- ```typescript
1379
- class AdaptiveConcurrency {
1380
- private currentConcurrency: number;
1381
- private minConcurrency: number;
1382
- private maxConcurrency: number;
1383
- private errorWindow: Array<{ timestamp: number; success: boolean }> = [];
1384
- private windowSize: number = 100;
1385
- private targetErrorRate: number = 0.1; // 10% target error rate
1386
- private adjustmentInterval: ReturnType<typeof setInterval>;
1387
-
1388
- constructor(options: {
1389
- initialConcurrency?: number;
1390
- minConcurrency?: number;
1391
- maxConcurrency?: number;
1392
- targetErrorRate?: number;
1393
- adjustmentInterval?: number;
1394
- } = {}) {
1395
- this.currentConcurrency = options.initialConcurrency ?? 5;
1396
- this.minConcurrency = options.minConcurrency ?? 1;
1397
- this.maxConcurrency = options.maxConcurrency ?? 50;
1398
- this.targetErrorRate = options.targetErrorRate ?? 0.1;
1399
-
1400
- this.adjustmentInterval = setInterval(
1401
- () => this.adjustConcurrency(),
1402
- options.adjustmentInterval ?? 10_000
1403
- );
1404
- }
1405
-
1406
- track(success: boolean): void {
1407
- this.errorWindow.push({
1408
- timestamp: Date.now(),
1409
- success,
1410
- });
1411
-
1412
- // Keep only recent results
1413
- if (this.errorWindow.length > this.windowSize) {
1414
- this.errorWindow.shift();
1415
- }
1416
- }
1417
-
1418
- private adjustConcurrency(): void {
1419
- if (this.errorWindow.length < 10) return; // Not enough data
1420
-
1421
- const errorRate = this.getErrorRate();
1422
-
1423
- if (errorRate > this.targetErrorRate * 1.5) {
1424
- // Too many errors — reduce concurrency
1425
- const reduction = Math.max(1, Math.floor(this.currentConcurrency * 0.3));
1426
- this.currentConcurrency = Math.max(
1427
- this.minConcurrency,
1428
- this.currentConcurrency - reduction
1429
- );
1430
- console.log(`↓ Concurrency: ${this.currentConcurrency} (error rate: ${(errorRate * 100).toFixed(1)}%)`);
1431
- } else if (errorRate < this.targetErrorRate * 0.5) {
1432
- // Low errors — increase concurrency
1433
- const increase = Math.max(1, Math.floor(this.currentConcurrency * 0.2));
1434
- this.currentConcurrency = Math.min(
1435
- this.maxConcurrency,
1436
- this.currentConcurrency + increase
1437
- );
1438
- console.log(`↑ Concurrency: ${this.currentConcurrency} (error rate: ${(errorRate * 100).toFixed(1)}%)`);
1439
- }
1440
- }
1441
-
1442
- private getErrorRate(): number {
1443
- const errors = this.errorWindow.filter(e => !e.success).length;
1444
- return errors / this.errorWindow.length;
1445
- }
1446
-
1447
- get concurrency(): number {
1448
- return this.currentConcurrency;
1449
- }
1450
-
1451
- get errorRate(): number {
1452
- return this.getErrorRate();
1453
- }
1454
-
1455
- destroy(): void {
1456
- clearInterval(this.adjustmentInterval);
1457
- }
1458
- }
1459
-
1460
- // Usage
1461
- const adaptive = new AdaptiveConcurrency({
1462
- initialConcurrency: 5,
1463
- minConcurrency: 1,
1464
- maxConcurrency: 30,
1465
- targetErrorRate: 0.1,
1466
- });
1467
-
1468
- const limit = pLimit(adaptive.concurrency);
1469
-
1470
- // Dynamic concurrency — re-evaluate periodically
1471
- async function scrapeWithAdaptiveConcurrency(urls: string[]): Promise<void> {
1472
- const results: Promise<void>[] = [];
1473
-
1474
- for (const url of urls) {
1475
- const promise = limit(async () => {
1476
- try {
1477
- await scrapeUrl(url);
1478
- adaptive.track(true);
1479
- } catch (error) {
1480
- adaptive.track(false);
1481
- }
1482
- });
1483
-
1484
- results.push(promise);
1485
-
1486
- // Re-create limiter when concurrency changes
1487
- if (results.length % 10 === 0) {
1488
- await Promise.race(results);
1489
- // Update limiter's concurrency (p-limit doesn't support this,
1490
- // so you'd need to recreate or use a different approach)
1491
- }
1492
- }
1493
-
1494
- await Promise.all(results);
1495
- }
1496
- ```
1497
-
1498
- ### Performance-Based Adaptive Concurrency
1499
-
1500
- ```typescript
1501
- class PerformanceAdaptiveConcurrency {
1502
- private latencyWindow: number[] = [];
1503
- private targetLatency: number;
1504
- private windowSize: number = 50;
1505
- private currentConcurrency: number;
1506
- private minConcurrency: number;
1507
- private maxConcurrency: number;
1508
-
1509
- constructor(options: {
1510
- initialConcurrency?: number;
1511
- minConcurrency?: number;
1512
- maxConcurrency?: number;
1513
- targetLatency?: number; // ms
1514
- } = {}) {
1515
- this.currentConcurrency = options.initialConcurrency ?? 5;
1516
- this.minConcurrency = options.minConcurrency ?? 1;
1517
- this.maxConcurrency = options.maxConcurrency ?? 50;
1518
- this.targetLatency = options.targetLatency ?? 2000;
1519
- }
1520
-
1521
- trackLatency(latencyMs: number): void {
1522
- this.latencyWindow.push(latencyMs);
1523
- if (this.latencyWindow.length > this.windowSize) {
1524
- this.latencyWindow.shift();
1525
- }
1526
- }
1527
-
1528
- getOptimalConcurrency(): number {
1529
- if (this.latencyWindow.length < 5) return this.currentConcurrency;
1530
-
1531
- const avgLatency = this.latencyWindow.reduce((a, b) => a + b, 0)
1532
- / this.latencyWindow.length;
1533
-
1534
- const p95Latency = this.getPercentile(95);
1535
- const p99Latency = this.getPercentile(99);
1536
-
1537
- // If p99 is 2x target, reduce concurrency
1538
- if (p99Latency > this.targetLatency * 2) {
1539
- this.currentConcurrency = Math.max(
1540
- this.minConcurrency,
1541
- Math.floor(this.currentConcurrency * 0.7)
1542
- );
1543
- }
1544
- // If avg latency is below target, increase concurrency
1545
- else if (avgLatency < this.targetLatency * 0.5) {
1546
- this.currentConcurrency = Math.min(
1547
- this.maxConcurrency,
1548
- Math.ceil(this.currentConcurrency * 1.3)
1549
- );
1550
- }
1551
-
1552
- return this.currentConcurrency;
1553
- }
1554
-
1555
- private getPercentile(percentile: number): number {
1556
- const sorted = [...this.latencyWindow].sort((a, b) => a - b);
1557
- const index = Math.ceil((percentile / 100) * sorted.length) - 1;
1558
- return sorted[Math.max(0, index)];
1559
- }
1560
-
1561
- get stats() {
1562
- const sorted = [...this.latencyWindow].sort((a, b) => a - b);
1563
- return {
1564
- currentConcurrency: this.currentConcurrency,
1565
- avgLatency: this.latencyWindow.length > 0
1566
- ? this.latencyWindow.reduce((a, b) => a + b, 0) / this.latencyWindow.length
1567
- : 0,
1568
- p50: this.getPercentile(50),
1569
- p95: this.getPercentile(95),
1570
- p99: this.getPercentile(99),
1571
- sampleSize: this.latencyWindow.length,
1572
- };
1573
- }
1574
- }
1575
- ```
1576
-
1577
- ---
1578
-
1579
- ## 9. Complete Code Examples
1580
-
1581
- ### Full Batch Scraper with All Patterns
1582
-
1583
- ```typescript
1584
- import PQueue from 'p-queue';
1585
- import { CircuitBreaker, handle, retry, timeout } from 'cockatiel';
1586
- import cliProgress from 'cli-progress';
1587
- import { z } from 'zod';
1588
-
1589
- // ============================================
1590
- // Schema
1591
- // ============================================
1592
-
1593
- const BatchJobSchema = z.object({
1594
- urls: z.array(z.string().url()).min(1).max(10_000),
1595
- concurrency: z.number().int().min(1).max(100).default(10),
1596
- maxRetries: z.number().int().min(0).max(10).default(3),
1597
- timeout: z.number().int().min(1000).max(300_000).default(30_000),
1598
- targetErrorRate: z.number().min(0).max(1).default(0.1),
1599
- outputDir: z.string().default('./output'),
1600
- });
1601
-
1602
- type BatchJob = z.infer<typeof BatchJobSchema>;
1603
-
1604
- // ============================================
1605
- // Progress Tracker
1606
- // ============================================
1607
-
1608
- class ProgressTracker {
1609
- private bar: cliProgress.SingleBar;
1610
- private succeeded = 0;
1611
- private failed = 0;
1612
- private startTime: number;
1613
-
1614
- constructor(total: number) {
1615
- this.startTime = Date.now();
1616
- this.bar = new cliProgress.SingleBar({
1617
- format: 'Scraping |{bar}| {percentage}% | {value}/{total} | ✓ {succeeded} ✗ {failed} | {speed} url/s',
1618
- barCompleteChar: '\u2588',
1619
- barIncompleteChar: '\u2591',
1620
- hideCursor: true,
1621
- });
1622
- this.bar.start(total, 0);
1623
- }
1624
-
1625
- success(): void {
1626
- this.succeeded++;
1627
- this.update();
1628
- }
1629
-
1630
- error(): void {
1631
- this.failed++;
1632
- this.update();
1633
- }
1634
-
1635
- private update(): void {
1636
- const elapsed = (Date.now() - this.startTime) / 1000;
1637
- const speed = ((this.succeeded + this.failed) / elapsed).toFixed(1);
1638
- this.bar.update(this.succeeded + this.failed, {
1639
- succeeded: this.succeeded,
1640
- failed: this.failed,
1641
- speed,
1642
- });
1643
- }
1644
-
1645
- stop(): void {
1646
- this.bar.stop();
1647
- const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
1648
- console.log(`\nCompleted in ${elapsed}s`);
1649
- console.log(` ✓ Succeeded: ${this.succeeded}`);
1650
- console.log(` ✗ Failed: ${this.failed}`);
1651
- }
1652
- }
1653
-
1654
- // ============================================
1655
- // Adaptive Concurrency Manager
1656
- // ============================================
1657
-
1658
- class AdaptiveConcurrencyManager {
1659
- private errorWindow: boolean[] = [];
1660
- private currentConcurrency: number;
1661
- private minConcurrency: number;
1662
- private maxConcurrency: number;
1663
- private targetErrorRate: number;
1664
-
1665
- constructor(options: {
1666
- initial: number;
1667
- min: number;
1668
- max: number;
1669
- targetErrorRate: number;
1670
- }) {
1671
- this.currentConcurrency = options.initial;
1672
- this.minConcurrency = options.min;
1673
- this.maxConcurrency = options.max;
1674
- this.targetErrorRate = options.targetErrorRate;
1675
- }
1676
-
1677
- track(success: boolean): void {
1678
- this.errorWindow.push(success);
1679
- if (this.errorWindow.length > 100) this.errorWindow.shift();
1680
- this.adjust();
1681
- }
1682
-
1683
- private adjust(): void {
1684
- if (this.errorWindow.length < 20) return;
1685
-
1686
- const errorRate = 1 - (this.errorWindow.filter(Boolean).length / this.errorWindow.length);
1687
-
1688
- if (errorRate > this.targetErrorRate * 2) {
1689
- this.currentConcurrency = Math.max(
1690
- this.minConcurrency,
1691
- Math.floor(this.currentConcurrency * 0.6)
1692
- );
1693
- } else if (errorRate < this.targetErrorRate * 0.3) {
1694
- this.currentConcurrency = Math.min(
1695
- this.maxConcurrency,
1696
- Math.ceil(this.currentConcurrency * 1.4)
1697
- );
1698
- }
1699
- }
1700
-
1701
- get concurrency(): number {
1702
- return this.currentConcurrency;
1703
- }
1704
-
1705
- get errorRate(): number {
1706
- if (this.errorWindow.length === 0) return 0;
1707
- return 1 - (this.errorWindow.filter(Boolean).length / this.errorWindow.length);
1708
- }
1709
- }
1710
-
1711
- // ============================================
1712
- // Circuit Breaker per Domain
1713
- // ============================================
1714
-
1715
- class CircuitBreakerPool {
1716
- private circuits = new Map<string, CircuitBreaker>();
1717
-
1718
- get(domain: string): CircuitBreaker {
1719
- if (!this.circuits.has(domain)) {
1720
- this.circuits.set(
1721
- domain,
1722
- CircuitBreaker.byPercentage({
1723
- threshold: 50,
1724
- duration: 30_000,
1725
- halfOpenAfter: 10_000,
1726
- })
1727
- );
1728
- }
1729
- return this.circuits.get(domain)!;
1730
- }
1731
- }
1732
-
1733
- // ============================================
1734
- // Main Batch Scraper
1735
- // ============================================
1736
-
1737
- class BatchScraper {
1738
- private job: BatchJob;
1739
- private adaptive: AdaptiveConcurrencyManager;
1740
- private circuits: CircuitBreakerPool;
1741
- private progress: ProgressTracker;
1742
- private queue: PQueue;
1743
- private results: Map<string, { success: boolean; data?: unknown; error?: string }> = new Map();
1744
-
1745
- constructor(job: BatchJob) {
1746
- this.job = job;
1747
- this.adaptive = new AdaptiveConcurrencyManager({
1748
- initial: job.concurrency,
1749
- min: 1,
1750
- max: job.concurrency * 2,
1751
- targetErrorRate: job.targetErrorRate,
1752
- });
1753
- this.circuits = new CircuitBreakerPool();
1754
- this.progress = new ProgressTracker(job.urls.length);
1755
- this.queue = new PQueue({ concurrency: job.concurrency });
1756
- }
1757
-
1758
- async run(): Promise<Map<string, { success: boolean; data?: unknown; error?: string }>> {
1759
- console.log(`Starting batch scrape of ${this.job.urls.length} URLs`);
1760
- console.log(`Initial concurrency: ${this.job.concurrency}`);
1761
- console.log(`Max retries: ${this.job.maxRetries}`);
1762
- console.log('');
1763
-
1764
- for (const url of this.job.urls) {
1765
- this.queue.add(() => this.scrapeUrl(url));
1766
- }
1767
-
1768
- await this.queue.onIdle();
1769
- this.progress.stop();
1770
-
1771
- // Print summary
1772
- const succeeded = [...this.results.values()].filter(r => r.success).length;
1773
- const failed = [...this.results.values()].filter(r => !r.success).length;
1774
- console.log(`\nResults: ${succeeded} succeeded, ${failed} failed`);
1775
- console.log(`Final concurrency: ${this.adaptive.concurrency}`);
1776
- console.log(`Error rate: ${(this.adaptive.errorRate * 100).toFixed(1)}%`);
1777
-
1778
- return this.results;
1779
- }
1780
-
1781
- private async scrapeUrl(url: string): Promise<void> {
1782
- const domain = new URL(url).hostname;
1783
- const circuit = this.circuits.get(domain);
1784
-
1785
- try {
1786
- const result = await handle(
1787
- circuit,
1788
- () =>
1789
- retry({
1790
- maxAttempts: this.job.maxRetries,
1791
- backoff: {
1792
- initialDelay: 1000,
1793
- maxDelay: 15_000,
1794
- multiplier: 2,
1795
- jitter: true,
1796
- },
1797
- }).execute(async () => {
1798
- const response = await timeout(this.job.timeout).execute(async () => {
1799
- const res = await fetch(url, {
1800
- headers: {
1801
- 'User-Agent': 'Mozilla/5.0 (compatible; BatchScraper/1.0)',
1802
- },
1803
- });
1804
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1805
- return res;
1806
- });
1807
-
1808
- const html = await response.text();
1809
- return this.parseHtml(html, url);
1810
- }),
1811
- () => {
1812
- // Circuit breaker fallback
1813
- return { cached: true, title: 'Content unavailable (circuit open)' };
1814
- }
1815
- ).promise();
1816
-
1817
- this.results.set(url, { success: true, data: result });
1818
- this.adaptive.track(true);
1819
- this.progress.success();
1820
- } catch (error) {
1821
- const errorMsg = error instanceof Error ? error.message : String(error);
1822
- this.results.set(url, { success: false, error: errorMsg });
1823
- this.adaptive.track(false);
1824
- this.progress.error();
1825
- }
1826
- }
1827
-
1828
- private async parseHtml(html: string, url: string): Promise<unknown> {
1829
- // Simple extraction
1830
- const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
1831
- return {
1832
- url,
1833
- title: titleMatch?.[1]?.trim() ?? '',
1834
- length: html.length,
1835
- scrapedAt: new Date().toISOString(),
1836
- };
1837
- }
1838
- }
1839
-
1840
- // ============================================
1841
- // Usage
1842
- // ============================================
1843
-
1844
- async function main() {
1845
- const job = BatchJobSchema.parse({
1846
- urls: [
1847
- 'https://example.com/1',
1848
- 'https://example.com/2',
1849
- 'https://example.com/3',
1850
- // ... more URLs
1851
- ],
1852
- concurrency: 10,
1853
- maxRetries: 3,
1854
- timeout: 30_000,
1855
- });
1856
-
1857
- const scraper = new BatchScraper(job);
1858
- const results = await scraper.run();
1859
-
1860
- // Save results
1861
- const fs = await import('fs/promises');
1862
- const outputPath = `${job.outputDir}/results-${Date.now()}.json`;
1863
- await fs.writeFile(outputPath, JSON.stringify([...results.entries()], null, 2));
1864
- console.log(`\nResults saved to ${outputPath}`);
1865
- }
1866
-
1867
- main().catch(console.error);
1868
- ```
1869
-
1870
- ---
1871
-
1872
- ## Summary: Quick Reference
1873
-
1874
- | Pattern | Package | Use Case |
1875
- |---------|---------|----------|
1876
- | Simple concurrency | p-limit | Wrap existing functions |
1877
- | Map with concurrency | p-map | Batch process arrays |
1878
- | Priority queue | p-queue | Dynamic task submission |
1879
- | Terminal progress | cli-progress | Single progress bar |
1880
- | Spinner | ora | Single operation feedback |
1881
- | Browser pooling | Custom / Crawlee | Playwright/Puppeteer |
1882
- | Autoscaled pool | Crawlee | Dynamic scaling |
1883
- | Circuit breaker | cockatiel | Prevent cascading failures |
1884
- | Retry + backoff | cockatiel | Transient error handling |
1885
- | Timeout | cockatiel / AbortSignal | Prevent hanging |
1886
- | Adaptive concurrency | Custom | Self-tuning systems |
1887
- | Domain distribution | Custom | Per-domain rate limits |
1888
- | Fallback chain | Custom | Graceful degradation |