@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.
- package/Dockerfile +1339 -0
- package/README.md +68 -22
- package/alternatives.ts +1218 -0
- package/biome.json +153 -0
- package/boards.json +1517 -0
- package/compute.ts +3144 -0
- package/cores.json +1801 -0
- package/dist/acquisition.d.ts +359 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +629 -0
- package/dist/acquisition.js.map +1 -0
- package/dist/alternatives.d.ts +509 -0
- package/dist/alternatives.d.ts.map +1 -0
- package/dist/alternatives.js +951 -0
- package/dist/alternatives.js.map +1 -0
- package/dist/automation.d.ts +426 -0
- package/dist/automation.d.ts.map +1 -0
- package/dist/automation.js +561 -0
- package/dist/automation.js.map +1 -0
- package/dist/browser.d.ts +562 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +1057 -0
- package/dist/browser.js.map +1 -0
- package/dist/capacitor.config.d.ts +13 -0
- package/dist/capacitor.config.d.ts.map +1 -0
- package/dist/capacitor.config.js +26 -0
- package/dist/capacitor.config.js.map +1 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +111 -0
- package/dist/cli.js.map +1 -0
- package/dist/communication.d.ts +192 -0
- package/dist/communication.d.ts.map +1 -0
- package/dist/communication.js +418 -0
- package/dist/communication.js.map +1 -0
- package/dist/compute.d.ts +1161 -0
- package/dist/compute.d.ts.map +1 -0
- package/dist/compute.js +2273 -0
- package/dist/compute.js.map +1 -0
- package/dist/distribution.d.ts +529 -0
- package/dist/distribution.d.ts.map +1 -0
- package/dist/distribution.js +811 -0
- package/dist/distribution.js.map +1 -0
- package/dist/execution.d.ts +530 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +761 -0
- package/dist/execution.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +60 -0
- package/dist/format.js.map +1 -0
- package/dist/foundation.d.ts +176 -0
- package/dist/foundation.d.ts.map +1 -0
- package/dist/foundation.js +309 -0
- package/dist/foundation.js.map +1 -0
- package/dist/index.d.ts +733 -122
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1018 -122
- package/dist/index.js.map +1 -1
- package/dist/integration.d.ts +236 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +236 -0
- package/dist/integration.js.map +1 -0
- package/dist/intelligence.d.ts +94 -0
- package/dist/intelligence.d.ts.map +1 -0
- package/dist/intelligence.js +145 -0
- package/dist/intelligence.js.map +1 -0
- package/dist/isolation.d.ts +129 -0
- package/dist/isolation.d.ts.map +1 -0
- package/dist/isolation.js +96 -0
- package/dist/isolation.js.map +1 -0
- package/dist/media.d.ts +344 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +986 -0
- package/dist/media.js.map +1 -0
- package/dist/modes.d.ts +359 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/modes.js +134 -0
- package/dist/modes.js.map +1 -0
- package/dist/{library/public.d.ts → operations.d.ts} +15 -1
- package/dist/operations.d.ts.map +1 -0
- package/dist/{library/public.js → operations.js} +18 -7
- package/dist/operations.js.map +1 -0
- package/dist/orchestrator.d.ts +1339 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +4308 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/performance.d.ts +416 -0
- package/dist/performance.d.ts.map +1 -0
- package/dist/performance.js +827 -0
- package/dist/performance.js.map +1 -0
- package/dist/quantum.d.ts +810 -0
- package/dist/quantum.d.ts.map +1 -0
- package/dist/quantum.js +2073 -0
- package/dist/quantum.js.map +1 -0
- package/dist/render.d.ts +381 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +1073 -0
- package/dist/render.js.map +1 -0
- package/dist/scheduler.d.ts +596 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +1259 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/security.d.ts +501 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +1170 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/{server/node.js → server.js} +16 -2
- package/dist/server.js.map +1 -0
- package/dist/tiers.d.ts +1002 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +2165 -0
- package/dist/tiers.js.map +1 -0
- package/dist/virtual.d.ts +641 -0
- package/dist/virtual.d.ts.map +1 -0
- package/dist/virtual.js +1285 -0
- package/dist/virtual.js.map +1 -0
- package/dist/virtualcpu.d.ts +316 -0
- package/dist/virtualcpu.d.ts.map +1 -0
- package/dist/virtualcpu.js +1180 -0
- package/dist/virtualcpu.js.map +1 -0
- package/dist/virtualgpu.d.ts +345 -0
- package/dist/virtualgpu.d.ts.map +1 -0
- package/dist/virtualgpu.js +1000 -0
- package/dist/virtualgpu.js.map +1 -0
- package/dist/virtualization.d.ts +607 -0
- package/dist/virtualization.d.ts.map +1 -0
- package/dist/virtualization.js +1187 -0
- package/dist/virtualization.js.map +1 -0
- package/dist/virtualmemory.d.ts +818 -0
- package/dist/virtualmemory.d.ts.map +1 -0
- package/dist/virtualmemory.js +1796 -0
- package/dist/virtualmemory.js.map +1 -0
- package/dist/webscrape.d.ts +720 -0
- package/dist/webscrape.d.ts.map +1 -0
- package/dist/webscrape.js +2697 -0
- package/dist/webscrape.js.map +1 -0
- package/docker.config +769 -0
- package/docs/CONVERSA.txt +1523 -0
- package/docs/alternatives.md +499 -0
- package/docs/architecture-1.8.19.md +76 -0
- package/docs/architecture-1.8.19.mmd +33 -0
- package/docs/architecture-1.8.19.png +0 -0
- package/docs/architecture.md +355 -0
- package/docs/artifactavailability.md +6 -0
- package/docs/brancharchive-2026-08-18.md +18 -0
- package/docs/browser.md +7 -0
- package/docs/consolidation.md +70 -0
- package/docs/e2ugh-engine.md +528 -0
- package/{dist/examples/localjob.js → docs/example-localjob.ts} +3 -3
- package/{dist/examples/publicapi.js → docs/example-publicapi.ts} +2 -2
- package/docs/hardware.md +342 -0
- package/docs/optimization.md +126 -0
- package/docs/performance.md +499 -0
- package/docs/planning.1.8.18.md +94 -0
- package/docs/planning.1.8.19.md +49 -0
- package/docs/releasenotes-1.8.17.md +6 -0
- package/docs/releasenotes-1.8.18.md +32 -0
- package/docs/releasenotes-1.8.19.md +35 -0
- package/docs/research-1.8.18-isolation.md +43 -0
- package/docs/research-1.8.19-virtual-browser.md +27 -0
- package/docs/saddle.archive.1.8.17.tar.gz.gpg +0 -0
- package/docs/security.md +454 -0
- package/docs/todo-1.8.16.md +56 -1
- package/docs/todo-1.8.18.md +164 -0
- package/docs/todo-1.8.19.md +241 -0
- package/docs/viability.md +777 -0
- package/docs/virtualization.md +335 -0
- package/docs/web-duplicatecleanup.md +9 -0
- package/docs/web-ideas.md +106 -0
- package/docs/workflowimprovements-2026-08-19.md +70 -0
- package/gpumonitor.cpp +1543 -0
- package/gpus.json +2408 -0
- package/index.ts +1478 -0
- package/media.ts +1337 -0
- package/mttg.config +1549 -0
- package/orchestrator.ts +5462 -0
- package/package.json +304 -55
- package/passage.config +1331 -0
- package/performance.ts +1049 -0
- package/processors.json +2217 -0
- package/qemu.config +1062 -0
- package/qemubridge.py +1340 -0
- package/quantum.ts +2393 -0
- package/render.ts +1325 -0
- package/scheduler.ts +1600 -0
- package/security.ts +1449 -0
- package/tiers.ts +2871 -0
- package/tsconfig.json +29 -0
- package/virtualcpu.ts +1273 -0
- package/virtualgpu.ts +1181 -0
- package/virtualhardware.c +1011 -0
- package/virtualhardware.json +739 -0
- package/virtualization.ts +1628 -0
- package/virtualizationcore.cpp +4635 -0
- package/virtualmemory.ts +2303 -0
- package/vm.config.json +1910 -0
- package/{extension → web/extension}/manifest.json +1 -1
- package/web/readme.md +382 -0
- package/web/tsconfig.json +25 -0
- package/dist/adapters/forge.d.ts +0 -17
- package/dist/adapters/forge.d.ts.map +0 -1
- package/dist/adapters/forge.js +0 -19
- package/dist/adapters/forge.js.map +0 -1
- package/dist/adapters/forgejo.d.ts +0 -49
- package/dist/adapters/forgejo.d.ts.map +0 -1
- package/dist/adapters/forgejo.js +0 -8
- package/dist/adapters/forgejo.js.map +0 -1
- package/dist/adapters/github.d.ts +0 -12
- package/dist/adapters/github.d.ts.map +0 -1
- package/dist/adapters/github.js +0 -20
- package/dist/adapters/github.js.map +0 -1
- package/dist/adapters/gitlab.d.ts +0 -17
- package/dist/adapters/gitlab.d.ts.map +0 -1
- package/dist/adapters/gitlab.js +0 -10
- package/dist/adapters/gitlab.js.map +0 -1
- package/dist/adapters/huggingface.d.ts +0 -17
- package/dist/adapters/huggingface.d.ts.map +0 -1
- package/dist/adapters/huggingface.js +0 -6
- package/dist/adapters/huggingface.js.map +0 -1
- package/dist/adapters/socket.d.ts +0 -11
- package/dist/adapters/socket.d.ts.map +0 -1
- package/dist/adapters/socket.js +0 -17
- package/dist/adapters/socket.js.map +0 -1
- package/dist/adapters/transport.d.ts +0 -7
- package/dist/adapters/transport.d.ts.map +0 -1
- package/dist/adapters/transport.js +0 -36
- package/dist/adapters/transport.js.map +0 -1
- package/dist/ai/chunk.d.ts +0 -2
- package/dist/ai/chunk.d.ts.map +0 -1
- package/dist/ai/chunk.js +0 -36
- package/dist/ai/chunk.js.map +0 -1
- package/dist/ai/llmstxt.d.ts +0 -6
- package/dist/ai/llmstxt.d.ts.map +0 -1
- package/dist/ai/llmstxt.js +0 -12
- package/dist/ai/llmstxt.js.map +0 -1
- package/dist/ai/provenance.d.ts +0 -22
- package/dist/ai/provenance.d.ts.map +0 -1
- package/dist/ai/provenance.js +0 -25
- package/dist/ai/provenance.js.map +0 -1
- package/dist/ai/rag.d.ts +0 -26
- package/dist/ai/rag.d.ts.map +0 -1
- package/dist/ai/rag.js +0 -22
- package/dist/ai/rag.js.map +0 -1
- package/dist/ai/tokens.d.ts +0 -15
- package/dist/ai/tokens.d.ts.map +0 -1
- package/dist/ai/tokens.js +0 -9
- package/dist/ai/tokens.js.map +0 -1
- package/dist/api/auth.d.ts +0 -16
- package/dist/api/auth.d.ts.map +0 -1
- package/dist/api/auth.js +0 -19
- package/dist/api/auth.js.map +0 -1
- package/dist/api/contracts.d.ts +0 -28
- package/dist/api/contracts.d.ts.map +0 -1
- package/dist/api/contracts.js +0 -14
- package/dist/api/contracts.js.map +0 -1
- package/dist/api/control.d.ts +0 -10
- package/dist/api/control.d.ts.map +0 -1
- package/dist/api/control.js +0 -35
- package/dist/api/control.js.map +0 -1
- package/dist/api/http.d.ts +0 -7
- package/dist/api/http.d.ts.map +0 -1
- package/dist/api/http.js +0 -12
- package/dist/api/http.js.map +0 -1
- package/dist/api/rate.d.ts +0 -28
- package/dist/api/rate.d.ts.map +0 -1
- package/dist/api/rate.js +0 -36
- package/dist/api/rate.js.map +0 -1
- package/dist/api/security.d.ts +0 -12
- package/dist/api/security.d.ts.map +0 -1
- package/dist/api/security.js +0 -56
- package/dist/api/security.js.map +0 -1
- package/dist/api/service.d.ts +0 -5
- package/dist/api/service.d.ts.map +0 -1
- package/dist/api/service.js +0 -70
- package/dist/api/service.js.map +0 -1
- package/dist/apps/registry.d.ts +0 -33
- package/dist/apps/registry.d.ts.map +0 -1
- package/dist/apps/registry.js +0 -24
- package/dist/apps/registry.js.map +0 -1
- package/dist/binary/archive.d.ts +0 -27
- package/dist/binary/archive.d.ts.map +0 -1
- package/dist/binary/archive.js +0 -46
- package/dist/binary/archive.js.map +0 -1
- package/dist/binary/build.d.ts +0 -29
- package/dist/binary/build.d.ts.map +0 -1
- package/dist/binary/build.js +0 -17
- package/dist/binary/build.js.map +0 -1
- package/dist/binary/transform.d.ts +0 -70
- package/dist/binary/transform.d.ts.map +0 -1
- package/dist/binary/transform.js +0 -105
- package/dist/binary/transform.js.map +0 -1
- package/dist/bot/adapter.d.ts +0 -5
- package/dist/bot/adapter.d.ts.map +0 -1
- package/dist/bot/adapter.js +0 -11
- package/dist/bot/adapter.js.map +0 -1
- package/dist/bot/bot.d.ts +0 -28
- package/dist/bot/bot.d.ts.map +0 -1
- package/dist/bot/bot.js +0 -52
- package/dist/bot/bot.js.map +0 -1
- package/dist/bot/commands.d.ts +0 -8
- package/dist/bot/commands.d.ts.map +0 -1
- package/dist/bot/commands.js +0 -19
- package/dist/bot/commands.js.map +0 -1
- package/dist/bot/permissions.d.ts +0 -13
- package/dist/bot/permissions.d.ts.map +0 -1
- package/dist/bot/permissions.js +0 -16
- package/dist/bot/permissions.js.map +0 -1
- package/dist/browser/actions.d.ts +0 -33
- package/dist/browser/actions.d.ts.map +0 -1
- package/dist/browser/actions.js +0 -44
- package/dist/browser/actions.js.map +0 -1
- package/dist/browser/agent.d.ts +0 -21
- package/dist/browser/agent.d.ts.map +0 -1
- package/dist/browser/agent.js +0 -13
- package/dist/browser/agent.js.map +0 -1
- package/dist/browser/context.d.ts +0 -43
- package/dist/browser/context.d.ts.map +0 -1
- package/dist/browser/context.js +0 -54
- package/dist/browser/context.js.map +0 -1
- package/dist/browser/fingerprint.d.ts +0 -4
- package/dist/browser/fingerprint.d.ts.map +0 -1
- package/dist/browser/fingerprint.js +0 -12
- package/dist/browser/fingerprint.js.map +0 -1
- package/dist/browser/index.d.ts +0 -11
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js +0 -11
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/playwright.d.ts +0 -11
- package/dist/browser/playwright.d.ts.map +0 -1
- package/dist/browser/playwright.js +0 -24
- package/dist/browser/playwright.js.map +0 -1
- package/dist/browser/recorder.d.ts +0 -21
- package/dist/browser/recorder.d.ts.map +0 -1
- package/dist/browser/recorder.js +0 -58
- package/dist/browser/recorder.js.map +0 -1
- package/dist/browser/session.d.ts +0 -14
- package/dist/browser/session.d.ts.map +0 -1
- package/dist/browser/session.js +0 -21
- package/dist/browser/session.js.map +0 -1
- package/dist/browser/snapshot.d.ts +0 -69
- package/dist/browser/snapshot.d.ts.map +0 -1
- package/dist/browser/snapshot.js +0 -133
- package/dist/browser/snapshot.js.map +0 -1
- package/dist/captcha/contract.d.ts +0 -35
- package/dist/captcha/contract.d.ts.map +0 -1
- package/dist/captcha/contract.js +0 -17
- package/dist/captcha/contract.js.map +0 -1
- package/dist/captcha/evidence.d.ts +0 -10
- package/dist/captcha/evidence.d.ts.map +0 -1
- package/dist/captcha/evidence.js +0 -9
- package/dist/captcha/evidence.js.map +0 -1
- package/dist/captcha/guard.d.ts +0 -23
- package/dist/captcha/guard.d.ts.map +0 -1
- package/dist/captcha/guard.js +0 -12
- package/dist/captcha/guard.js.map +0 -1
- package/dist/cli/main.d.ts +0 -4
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/main.js +0 -46
- package/dist/cli/main.js.map +0 -1
- package/dist/core/errors.d.ts +0 -84
- package/dist/core/errors.d.ts.map +0 -1
- package/dist/core/errors.js +0 -54
- package/dist/core/errors.js.map +0 -1
- package/dist/core/events.d.ts +0 -9
- package/dist/core/events.d.ts.map +0 -1
- package/dist/core/events.js +0 -21
- package/dist/core/events.js.map +0 -1
- package/dist/core/hash.d.ts +0 -10
- package/dist/core/hash.d.ts.map +0 -1
- package/dist/core/hash.js +0 -82
- package/dist/core/hash.js.map +0 -1
- package/dist/core/ids.d.ts +0 -10
- package/dist/core/ids.d.ts.map +0 -1
- package/dist/core/ids.js +0 -15
- package/dist/core/ids.js.map +0 -1
- package/dist/delivery/manifest.d.ts +0 -35
- package/dist/delivery/manifest.d.ts.map +0 -1
- package/dist/delivery/manifest.js +0 -68
- package/dist/delivery/manifest.js.map +0 -1
- package/dist/deploy/index.d.ts +0 -6
- package/dist/deploy/index.d.ts.map +0 -1
- package/dist/deploy/index.js +0 -6
- package/dist/deploy/index.js.map +0 -1
- package/dist/dispatch/resumable.d.ts +0 -102
- package/dist/dispatch/resumable.d.ts.map +0 -1
- package/dist/dispatch/resumable.js +0 -64
- package/dist/dispatch/resumable.js.map +0 -1
- package/dist/dispatch/workflow.d.ts +0 -6
- package/dist/dispatch/workflow.d.ts.map +0 -1
- package/dist/dispatch/workflow.js +0 -37
- package/dist/dispatch/workflow.js.map +0 -1
- package/dist/domain/artifacts.d.ts +0 -12
- package/dist/domain/artifacts.d.ts.map +0 -1
- package/dist/domain/artifacts.js +0 -14
- package/dist/domain/artifacts.js.map +0 -1
- package/dist/domain/jobs.d.ts +0 -12
- package/dist/domain/jobs.d.ts.map +0 -1
- package/dist/domain/jobs.js +0 -20
- package/dist/domain/jobs.js.map +0 -1
- package/dist/domain/providers.d.ts +0 -10
- package/dist/domain/providers.d.ts.map +0 -1
- package/dist/domain/providers.js +0 -8
- package/dist/domain/providers.js.map +0 -1
- package/dist/domain/runtime.d.ts +0 -14
- package/dist/domain/runtime.d.ts.map +0 -1
- package/dist/domain/runtime.js +0 -10
- package/dist/domain/runtime.js.map +0 -1
- package/dist/domain/sessions.d.ts +0 -12
- package/dist/domain/sessions.d.ts.map +0 -1
- package/dist/domain/sessions.js +0 -57
- package/dist/domain/sessions.js.map +0 -1
- package/dist/examples/localjob.d.ts +0 -2
- package/dist/examples/localjob.d.ts.map +0 -1
- package/dist/examples/localjob.js.map +0 -1
- package/dist/examples/publicapi.d.ts +0 -2
- package/dist/examples/publicapi.d.ts.map +0 -1
- package/dist/examples/publicapi.js.map +0 -1
- package/dist/extension/build.d.ts +0 -9
- package/dist/extension/build.d.ts.map +0 -1
- package/dist/extension/build.js +0 -85
- package/dist/extension/build.js.map +0 -1
- package/dist/extension/content.d.ts +0 -6
- package/dist/extension/content.d.ts.map +0 -1
- package/dist/extension/content.js +0 -135
- package/dist/extension/content.js.map +0 -1
- package/dist/extension/index.d.ts +0 -7
- package/dist/extension/index.d.ts.map +0 -1
- package/dist/extension/index.js +0 -7
- package/dist/extension/index.js.map +0 -1
- package/dist/extension/pagebridge.d.ts +0 -6
- package/dist/extension/pagebridge.d.ts.map +0 -1
- package/dist/extension/pagebridge.js +0 -42
- package/dist/extension/pagebridge.js.map +0 -1
- package/dist/extension/permissions.d.ts +0 -26
- package/dist/extension/permissions.d.ts.map +0 -1
- package/dist/extension/permissions.js +0 -28
- package/dist/extension/permissions.js.map +0 -1
- package/dist/extension/popup.d.ts +0 -5
- package/dist/extension/popup.d.ts.map +0 -1
- package/dist/extension/popup.js +0 -26
- package/dist/extension/popup.js.map +0 -1
- package/dist/extension/protocol.d.ts +0 -58
- package/dist/extension/protocol.d.ts.map +0 -1
- package/dist/extension/protocol.js +0 -101
- package/dist/extension/protocol.js.map +0 -1
- package/dist/extension/serviceworker.d.ts +0 -25
- package/dist/extension/serviceworker.d.ts.map +0 -1
- package/dist/extension/serviceworker.js +0 -102
- package/dist/extension/serviceworker.js.map +0 -1
- package/dist/extension/worker.d.ts +0 -28
- package/dist/extension/worker.d.ts.map +0 -1
- package/dist/extension/worker.js +0 -22
- package/dist/extension/worker.js.map +0 -1
- package/dist/format/check.d.ts +0 -8
- package/dist/format/check.d.ts.map +0 -1
- package/dist/format/check.js +0 -38
- package/dist/format/check.js.map +0 -1
- package/dist/library/public.d.ts.map +0 -1
- package/dist/library/public.js.map +0 -1
- package/dist/mcp/browser.d.ts +0 -9
- package/dist/mcp/browser.d.ts.map +0 -1
- package/dist/mcp/browser.js +0 -13
- package/dist/mcp/browser.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -35
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -40
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/transport.d.ts +0 -7
- package/dist/mcp/transport.d.ts.map +0 -1
- package/dist/mcp/transport.js +0 -17
- package/dist/mcp/transport.js.map +0 -1
- package/dist/memory/bridge.d.ts +0 -14
- package/dist/memory/bridge.d.ts.map +0 -1
- package/dist/memory/bridge.js +0 -16
- package/dist/memory/bridge.js.map +0 -1
- package/dist/memory/engine.d.ts +0 -38
- package/dist/memory/engine.d.ts.map +0 -1
- package/dist/memory/engine.js +0 -109
- package/dist/memory/engine.js.map +0 -1
- package/dist/memory/modes.d.ts +0 -43
- package/dist/memory/modes.d.ts.map +0 -1
- package/dist/memory/modes.js +0 -66
- package/dist/memory/modes.js.map +0 -1
- package/dist/memory/objects.d.ts +0 -21
- package/dist/memory/objects.d.ts.map +0 -1
- package/dist/memory/objects.js +0 -20
- package/dist/memory/objects.js.map +0 -1
- package/dist/memory/planner.d.ts +0 -66
- package/dist/memory/planner.d.ts.map +0 -1
- package/dist/memory/planner.js +0 -108
- package/dist/memory/planner.js.map +0 -1
- package/dist/memory/targets.d.ts +0 -78
- package/dist/memory/targets.d.ts.map +0 -1
- package/dist/memory/targets.js +0 -29
- package/dist/memory/targets.js.map +0 -1
- package/dist/memory/transforms.d.ts +0 -19
- package/dist/memory/transforms.d.ts.map +0 -1
- package/dist/memory/transforms.js +0 -14
- package/dist/memory/transforms.js.map +0 -1
- package/dist/modes/matrix.d.ts +0 -23
- package/dist/modes/matrix.d.ts.map +0 -1
- package/dist/modes/matrix.js +0 -18
- package/dist/modes/matrix.js.map +0 -1
- package/dist/modes/modes.d.ts +0 -33
- package/dist/modes/modes.d.ts.map +0 -1
- package/dist/modes/modes.js +0 -15
- package/dist/modes/modes.js.map +0 -1
- package/dist/modes/resolve.d.ts +0 -96
- package/dist/modes/resolve.d.ts.map +0 -1
- package/dist/modes/resolve.js +0 -48
- package/dist/modes/resolve.js.map +0 -1
- package/dist/observability/metrics.d.ts +0 -14
- package/dist/observability/metrics.d.ts.map +0 -1
- package/dist/observability/metrics.js +0 -18
- package/dist/observability/metrics.js.map +0 -1
- package/dist/packager/manifest.d.ts +0 -66
- package/dist/packager/manifest.d.ts.map +0 -1
- package/dist/packager/manifest.js +0 -94
- package/dist/packager/manifest.js.map +0 -1
- package/dist/packager/publish.d.ts +0 -51
- package/dist/packager/publish.d.ts.map +0 -1
- package/dist/packager/publish.js +0 -18
- package/dist/packager/publish.js.map +0 -1
- package/dist/packager/targetcli.d.ts +0 -7
- package/dist/packager/targetcli.d.ts.map +0 -1
- package/dist/packager/targetcli.js +0 -22
- package/dist/packager/targetcli.js.map +0 -1
- package/dist/persistence/adapter.d.ts +0 -5
- package/dist/persistence/adapter.d.ts.map +0 -1
- package/dist/persistence/adapter.js +0 -11
- package/dist/persistence/adapter.js.map +0 -1
- package/dist/persistence/drizzle.d.ts +0 -2
- package/dist/persistence/drizzle.d.ts.map +0 -1
- package/dist/persistence/drizzle.js +0 -12
- package/dist/persistence/drizzle.js.map +0 -1
- package/dist/persistence/memory.d.ts +0 -2
- package/dist/persistence/memory.d.ts.map +0 -1
- package/dist/persistence/memory.js +0 -27
- package/dist/persistence/memory.js.map +0 -1
- package/dist/persistence/migrations.d.ts +0 -19
- package/dist/persistence/migrations.d.ts.map +0 -1
- package/dist/persistence/migrations.js +0 -13
- package/dist/persistence/migrations.js.map +0 -1
- package/dist/persistence/prisma.d.ts +0 -2
- package/dist/persistence/prisma.d.ts.map +0 -1
- package/dist/persistence/prisma.js +0 -25
- package/dist/persistence/prisma.js.map +0 -1
- package/dist/persistence/schema.d.ts +0 -63
- package/dist/persistence/schema.d.ts.map +0 -1
- package/dist/persistence/schema.js +0 -28
- package/dist/persistence/schema.js.map +0 -1
- package/dist/persistence/sql.d.ts +0 -3
- package/dist/persistence/sql.d.ts.map +0 -1
- package/dist/persistence/sql.js +0 -37
- package/dist/persistence/sql.js.map +0 -1
- package/dist/protocol/blocks.d.ts +0 -10
- package/dist/protocol/blocks.d.ts.map +0 -1
- package/dist/protocol/blocks.js +0 -26
- package/dist/protocol/blocks.js.map +0 -1
- package/dist/protocol/json.d.ts +0 -6
- package/dist/protocol/json.d.ts.map +0 -1
- package/dist/protocol/json.js +0 -6
- package/dist/protocol/json.js.map +0 -1
- package/dist/protocol/ndjson.d.ts +0 -3
- package/dist/protocol/ndjson.d.ts.map +0 -1
- package/dist/protocol/ndjson.js +0 -20
- package/dist/protocol/ndjson.js.map +0 -1
- package/dist/protocol/sse.d.ts +0 -7
- package/dist/protocol/sse.d.ts.map +0 -1
- package/dist/protocol/sse.js +0 -25
- package/dist/protocol/sse.js.map +0 -1
- package/dist/proxy/pool.d.ts +0 -9
- package/dist/proxy/pool.d.ts.map +0 -1
- package/dist/proxy/pool.js +0 -27
- package/dist/proxy/pool.js.map +0 -1
- package/dist/queue/idempotency.d.ts +0 -10
- package/dist/queue/idempotency.d.ts.map +0 -1
- package/dist/queue/idempotency.js +0 -13
- package/dist/queue/idempotency.js.map +0 -1
- package/dist/queue/persistent.d.ts +0 -14
- package/dist/queue/persistent.d.ts.map +0 -1
- package/dist/queue/persistent.js +0 -66
- package/dist/queue/persistent.js.map +0 -1
- package/dist/queue/queue.d.ts +0 -7
- package/dist/queue/queue.d.ts.map +0 -1
- package/dist/queue/queue.js +0 -55
- package/dist/queue/queue.js.map +0 -1
- package/dist/queue/saga.d.ts +0 -5
- package/dist/queue/saga.d.ts.map +0 -1
- package/dist/queue/saga.js +0 -20
- package/dist/queue/saga.js.map +0 -1
- package/dist/release/assets.d.ts +0 -144
- package/dist/release/assets.d.ts.map +0 -1
- package/dist/release/assets.js +0 -157
- package/dist/release/assets.js.map +0 -1
- package/dist/release/evidence.d.ts +0 -92
- package/dist/release/evidence.d.ts.map +0 -1
- package/dist/release/evidence.js +0 -148
- package/dist/release/evidence.js.map +0 -1
- package/dist/release/verify.d.ts +0 -12
- package/dist/release/verify.d.ts.map +0 -1
- package/dist/release/verify.js +0 -112
- package/dist/release/verify.js.map +0 -1
- package/dist/runners/chain.d.ts +0 -125
- package/dist/runners/chain.d.ts.map +0 -1
- package/dist/runners/chain.js +0 -95
- package/dist/runners/chain.js.map +0 -1
- package/dist/runners/health.d.ts +0 -36
- package/dist/runners/health.d.ts.map +0 -1
- package/dist/runners/health.js +0 -26
- package/dist/runners/health.js.map +0 -1
- package/dist/runners/heartbeat.d.ts +0 -23
- package/dist/runners/heartbeat.d.ts.map +0 -1
- package/dist/runners/heartbeat.js +0 -29
- package/dist/runners/heartbeat.js.map +0 -1
- package/dist/runners/inprocess.d.ts +0 -17
- package/dist/runners/inprocess.d.ts.map +0 -1
- package/dist/runners/inprocess.js +0 -20
- package/dist/runners/inprocess.js.map +0 -1
- package/dist/runners/scheduler.d.ts +0 -5
- package/dist/runners/scheduler.d.ts.map +0 -1
- package/dist/runners/scheduler.js +0 -19
- package/dist/runners/scheduler.js.map +0 -1
- package/dist/runtime/abort.d.ts +0 -8
- package/dist/runtime/abort.d.ts.map +0 -1
- package/dist/runtime/abort.js +0 -13
- package/dist/runtime/abort.js.map +0 -1
- package/dist/runtime/compatibility.d.ts +0 -37
- package/dist/runtime/compatibility.d.ts.map +0 -1
- package/dist/runtime/compatibility.js +0 -10
- package/dist/runtime/compatibility.js.map +0 -1
- package/dist/runtime/detect.d.ts +0 -13
- package/dist/runtime/detect.d.ts.map +0 -1
- package/dist/runtime/detect.js +0 -18
- package/dist/runtime/detect.js.map +0 -1
- package/dist/runtime/engine.d.ts +0 -20
- package/dist/runtime/engine.d.ts.map +0 -1
- package/dist/runtime/engine.js +0 -61
- package/dist/runtime/engine.js.map +0 -1
- package/dist/runtime/retry.d.ts +0 -19
- package/dist/runtime/retry.d.ts.map +0 -1
- package/dist/runtime/retry.js +0 -54
- package/dist/runtime/retry.js.map +0 -1
- package/dist/runtime/worker.d.ts +0 -10
- package/dist/runtime/worker.d.ts.map +0 -1
- package/dist/runtime/worker.js +0 -25
- package/dist/runtime/worker.js.map +0 -1
- package/dist/scrape/cache.d.ts +0 -45
- package/dist/scrape/cache.d.ts.map +0 -1
- package/dist/scrape/cache.js +0 -45
- package/dist/scrape/cache.js.map +0 -1
- package/dist/scrape/crawl.d.ts +0 -47
- package/dist/scrape/crawl.d.ts.map +0 -1
- package/dist/scrape/crawl.js +0 -114
- package/dist/scrape/crawl.js.map +0 -1
- package/dist/scrape/extract.d.ts +0 -63
- package/dist/scrape/extract.d.ts.map +0 -1
- package/dist/scrape/extract.js +0 -47
- package/dist/scrape/extract.js.map +0 -1
- package/dist/scrape/normalize.d.ts +0 -52
- package/dist/scrape/normalize.d.ts.map +0 -1
- package/dist/scrape/normalize.js +0 -102
- package/dist/scrape/normalize.js.map +0 -1
- package/dist/scrape/robots.d.ts +0 -37
- package/dist/scrape/robots.d.ts.map +0 -1
- package/dist/scrape/robots.js +0 -71
- package/dist/scrape/robots.js.map +0 -1
- package/dist/scrape/schema.d.ts +0 -23
- package/dist/scrape/schema.d.ts.map +0 -1
- package/dist/scrape/schema.js +0 -92
- package/dist/scrape/schema.js.map +0 -1
- package/dist/scrape/scraper.d.ts +0 -6
- package/dist/scrape/scraper.d.ts.map +0 -1
- package/dist/scrape/scraper.js +0 -46
- package/dist/scrape/scraper.js.map +0 -1
- package/dist/scrape/semantic.d.ts +0 -25
- package/dist/scrape/semantic.d.ts.map +0 -1
- package/dist/scrape/semantic.js +0 -27
- package/dist/scrape/semantic.js.map +0 -1
- package/dist/server/node.d.ts +0 -7
- package/dist/server/node.d.ts.map +0 -1
- package/dist/server/node.js.map +0 -1
- package/dist/sessions/file.d.ts +0 -25
- package/dist/sessions/file.d.ts.map +0 -1
- package/dist/sessions/file.js +0 -13
- package/dist/sessions/file.js.map +0 -1
- package/dist/sessions/replay.d.ts +0 -9
- package/dist/sessions/replay.d.ts.map +0 -1
- package/dist/sessions/replay.js +0 -70
- package/dist/sessions/replay.js.map +0 -1
- package/dist/sessions/store.d.ts +0 -25
- package/dist/sessions/store.d.ts.map +0 -1
- package/dist/sessions/store.js +0 -13
- package/dist/sessions/store.js.map +0 -1
- package/dist/storage/adapter.d.ts +0 -5
- package/dist/storage/adapter.d.ts.map +0 -1
- package/dist/storage/adapter.js +0 -11
- package/dist/storage/adapter.js.map +0 -1
- package/dist/storage/cache.d.ts +0 -23
- package/dist/storage/cache.d.ts.map +0 -1
- package/dist/storage/cache.js +0 -79
- package/dist/storage/cache.js.map +0 -1
- package/dist/storage/checksum.d.ts +0 -3
- package/dist/storage/checksum.d.ts.map +0 -1
- package/dist/storage/checksum.js +0 -19
- package/dist/storage/checksum.js.map +0 -1
- package/dist/storage/chunked.d.ts +0 -21
- package/dist/storage/chunked.d.ts.map +0 -1
- package/dist/storage/chunked.js +0 -67
- package/dist/storage/chunked.js.map +0 -1
- package/dist/storage/content.d.ts +0 -24
- package/dist/storage/content.d.ts.map +0 -1
- package/dist/storage/content.js +0 -44
- package/dist/storage/content.js.map +0 -1
- package/dist/storage/filehosting.d.ts +0 -2
- package/dist/storage/filehosting.d.ts.map +0 -1
- package/dist/storage/filehosting.js +0 -24
- package/dist/storage/filehosting.js.map +0 -1
- package/dist/storage/githubcontents.d.ts +0 -2
- package/dist/storage/githubcontents.d.ts.map +0 -1
- package/dist/storage/githubcontents.js +0 -29
- package/dist/storage/githubcontents.js.map +0 -1
- package/dist/storage/index.d.ts +0 -12
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -12
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/local.d.ts +0 -2
- package/dist/storage/local.d.ts.map +0 -1
- package/dist/storage/local.js +0 -72
- package/dist/storage/local.js.map +0 -1
- package/dist/storage/memory.d.ts +0 -6
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js +0 -36
- package/dist/storage/memory.js.map +0 -1
- package/dist/storage/pool.d.ts +0 -95
- package/dist/storage/pool.d.ts.map +0 -1
- package/dist/storage/pool.js +0 -202
- package/dist/storage/pool.js.map +0 -1
- package/dist/storage/s3compatible.d.ts +0 -3
- package/dist/storage/s3compatible.d.ts.map +0 -1
- package/dist/storage/s3compatible.js +0 -70
- package/dist/storage/s3compatible.js.map +0 -1
- package/dist/storage/sync.d.ts +0 -47
- package/dist/storage/sync.d.ts.map +0 -1
- package/dist/storage/sync.js +0 -75
- package/dist/storage/sync.js.map +0 -1
- package/dist/surfaces/adapters.d.ts +0 -97
- package/dist/surfaces/adapters.d.ts.map +0 -1
- package/dist/surfaces/adapters.js +0 -50
- package/dist/surfaces/adapters.js.map +0 -1
- package/dist/surfaces/controls.d.ts +0 -18
- package/dist/surfaces/controls.d.ts.map +0 -1
- package/dist/surfaces/controls.js +0 -45
- package/dist/surfaces/controls.js.map +0 -1
- package/dist/surfaces/manifest.d.ts +0 -68
- package/dist/surfaces/manifest.d.ts.map +0 -1
- package/dist/surfaces/manifest.js +0 -42
- package/dist/surfaces/manifest.js.map +0 -1
- package/dist/surfaces/n8n.d.ts +0 -25
- package/dist/surfaces/n8n.d.ts.map +0 -1
- package/dist/surfaces/n8n.js +0 -31
- package/dist/surfaces/n8n.js.map +0 -1
- package/dist/surfaces/operations.d.ts +0 -32
- package/dist/surfaces/operations.d.ts.map +0 -1
- package/dist/surfaces/operations.js +0 -53
- package/dist/surfaces/operations.js.map +0 -1
- package/dist/surfaces/requirements.d.ts +0 -29
- package/dist/surfaces/requirements.d.ts.map +0 -1
- package/dist/surfaces/requirements.js +0 -50
- package/dist/surfaces/requirements.js.map +0 -1
- package/dist/surfaces/targets.d.ts +0 -196
- package/dist/surfaces/targets.d.ts.map +0 -1
- package/dist/surfaces/targets.js +0 -27
- package/dist/surfaces/targets.js.map +0 -1
- package/dist/webhook/delivery.d.ts +0 -18
- package/dist/webhook/delivery.d.ts.map +0 -1
- package/dist/webhook/delivery.js +0 -36
- package/dist/webhook/delivery.js.map +0 -1
- package/dist/webhook/receiver.d.ts +0 -26
- package/dist/webhook/receiver.d.ts.map +0 -1
- package/dist/webhook/receiver.js +0 -24
- package/dist/webhook/receiver.js.map +0 -1
- package/dist/webhook/signature.d.ts +0 -3
- package/dist/webhook/signature.d.ts.map +0 -1
- package/dist/webhook/signature.js +0 -7
- package/dist/webhook/signature.js.map +0 -1
- package/dist/workflow/manifest.d.ts +0 -22
- package/dist/workflow/manifest.d.ts.map +0 -1
- package/dist/workflow/manifest.js +0 -21
- package/dist/workflow/manifest.js.map +0 -1
- package/dist/workflow/registry.d.ts +0 -7
- package/dist/workflow/registry.d.ts.map +0 -1
- package/dist/workflow/registry.js +0 -18
- package/dist/workflow/registry.js.map +0 -1
- package/dist/workflow/templates.d.ts +0 -6
- package/dist/workflow/templates.d.ts.map +0 -1
- package/dist/workflow/templates.js +0 -18
- package/dist/workflow/templates.js.map +0 -1
- package/dist/workflow/triggers.d.ts +0 -64
- package/dist/workflow/triggers.d.ts.map +0 -1
- package/dist/workflow/triggers.js +0 -99
- package/dist/workflow/triggers.js.map +0 -1
- package/docs/logs/.gitkeep +0 -0
- package/docs/plans/00.index.md +0 -50
- package/docs/plans/01.architecture.md +0 -86
- package/docs/plans/02.research.computer.use.md +0 -58
- package/docs/plans/03.research.captcha.bypass.md +0 -68
- package/docs/plans/04.research.sandbox.ai.md +0 -52
- package/docs/plans/05.capture.platform.md +0 -57
- package/docs/plans/06.dependencies.md +0 -97
- package/docs/plans/07.captcha.test.page.md +0 -41
- package/docs/plans/08.production.infra.md +0 -70
- package/docs/plans/09.database.schema.md +0 -121
- package/docs/plans/10.cloudinary.storage.md +0 -57
- package/docs/plans/11.movement.logs.json.md +0 -72
- package/docs/plans/12.research.atlas.agent.browser.md +0 -79
- package/docs/plans/13.research.anti.detection.md +0 -898
- package/docs/plans/14.research.proxy.md +0 -1495
- package/docs/plans/15.research.retry.rate.limit.md +0 -1958
- package/docs/plans/16.research.crawling.md +0 -1417
- package/docs/plans/17.research.caching.md +0 -1610
- package/docs/plans/18.research.content.extraction.md +0 -1952
- package/docs/plans/19.research.errors.events.md +0 -1523
- package/docs/plans/20.research.zod.validation.md +0 -1350
- package/docs/plans/21.research.batch.concurrency.md +0 -1888
- package/docs/plans/22.research.universal.runtime.md +0 -944
- package/docs/plans/23.research.ai.integration.md +0 -1465
- package/docs/plans/24.research.memory.persistence.md +0 -1979
- package/docs/plans/25.research.server.api.md +0 -342
- package/docs/plans/26.research.compilation.md +0 -249
- package/docs/plans/27.research.html.parsing.md +0 -251
- package/docs/plans/28.action.plan.md +0 -50
- package/docs/plans/29.api.reference.md +0 -174
- package/docs/plans/30.architecture.plan.md +0 -94
- package/docs/plans/31.auditoria.dados.md +0 -163
- package/docs/plans/32.bots.automacao.computacional.md +0 -214
- package/docs/plans/33.bots.codigo.revisao.md +0 -220
- package/docs/plans/34.bots.seguranca.cicd.md +0 -366
- package/docs/plans/35.comparativo.concorrencia.md +0 -464
- package/docs/plans/36.computational.memory.md +0 -340
- package/docs/plans/37.deploystrategy.md +0 -394
- package/docs/plans/38.flow.md +0 -155
- package/docs/plans/39.multi.platform.bot.md +0 -252
- package/docs/plans/40.npm.publish.md +0 -250
- package/docs/plans/41.o.que.falta.md +0 -407
- package/docs/plans/42.pesquisa.concorrencia.md +0 -721
- package/docs/plans/43.plan.universal.architecture.md +0 -496
- package/docs/plans/44.reference.md +0 -100
- package/docs/plans/45.robotarchitecture.md +0 -237
- package/docs/plans/46.scdnintegration.md +0 -284
- package/docs/plans/47.multiforge.readme.md +0 -129
- package/docs/plans/48.theory.v4.repo.os.md +0 -152
- package/docs/plans/49.third.party.infra.md +0 -12
- package/docs/plans/50.file.as.compute.md +0 -39
- package/docs/plans/51.architecture.virtual.processor.md +0 -80
- package/docs/plans/52.manifesto.v8.md +0 -11
- package/docs/plans/58.cdn.list.md +0 -23
- package/docs/plans/59.sql.frameworks.md +0 -33
- package/docs/plans/60.sql.thirdparty.md +0 -26
- package/docs/plans/61.objective.multiforge.md +0 -63
- package/docs/plans/62.huggingface.upload.md +0 -26
- package/docs/plans/63.kaggle.upload.md +0 -24
- package/docs/plans/64.npm.storage.md +0 -30
- package/docs/plans/65.rclone.terabox.md +0 -32
- package/docs/plans/66.buckets.and.models.todo.md +0 -14
- package/docs/plans/67.database.todo.md +0 -13
- package/docs/plans/68.deploy.packages.todo.md +0 -12
- package/docs/plans/69.report.human.operator.md +0 -133
- package/docs/plans/70.report.brain2qwerty.ems.md +0 -135
- package/docs/plans/71.report.hd.infinito.vram.md +0 -155
- package/docs/plans/72.plan.hd.infinito.node.md +0 -146
- package/docs/plans/73.plan.scifi.repos.md +0 -125
- package/docs/plans/74.000.manifesto.v8.flat.2..md +0 -11
- package/docs/plans/README.md +0 -489
- package/docs/plans/aggregate_platforms.mjs +0 -146
- package/docs/plans/examplesession.json +0 -36
- package/docs/plans/missing-facts.md +0 -192
- package/docs/plans/models.md +0 -64
- package/docs/plans/organize.cjs +0 -270
- package/docs/plans/platforms.md +0 -2887
- package/docs/plans/sites.md +0 -31322
- package/docs/sources/farm.py +0 -117
- package/docs/sources/html/saddle1.html +0 -132
- package/docs/sources/html/saddle2.html +0 -157
- package/docs/sources/html/saddle3.html +0 -119
- package/docs/sources/html/saddle4.html +0 -144
- package/docs/sources/html/saddle5.html +0 -72
- package/docs/sources/html/saddle6.html +0 -171
- package/docs/sources/html/saddle7.html +0 -236
- package/docs/sources/saddle.ts +0 -74
- package/docs/sources/schema.prisma +0 -88
- package/docs/sources/script.sh +0 -64
- package/docs/sources/workflows.yml +0 -458
- package/docs/talks1/_body.txt +0 -14
- package/docs/talks1/_index.md +0 -15
- package/docs/talks1/_screenshot.png +0 -0
- package/docs/talks1/assistant-01.md +0 -5
- package/docs/talks1/assistant-02.md +0 -5
- package/docs/talks1/assistant-03.md +0 -531
- package/docs/talks1/assistant-04.md +0 -26
- package/docs/talks1/assistant-05.md +0 -774
- package/docs/talks1/assistant-06.md +0 -1718
- package/docs/talks1/scrape-share.cjs +0 -185
- package/docs/talks1/scrape-share.ts +0 -183
- package/docs/talks1/user-01.md +0 -3
- package/docs/talks1/user-02.md +0 -3
- package/docs/talks1/user-03.md +0 -88
- package/docs/talks1/user-04.md +0 -3
- package/docs/talks1/user-05.md +0 -3
- package/docs/talks1/user-06.md +0 -88
- package/docs/talks1/user-07.md +0 -88
- package/docs/talks2/_body.txt +0 -14
- package/docs/talks2/_index.md +0 -16
- package/docs/talks2/_screenshot.png +0 -0
- package/docs/talks2/assistant-01.md +0 -5
- package/docs/talks2/assistant-02.md +0 -5
- package/docs/talks2/assistant-03.md +0 -424
- package/docs/talks2/assistant-04.md +0 -598
- package/docs/talks2/assistant-05.md +0 -1280
- package/docs/talks2/assistant-06.md +0 -1227
- package/docs/talks2/assistant-07.md +0 -1252
- package/docs/talks2/user-01.md +0 -3
- package/docs/talks2/user-02.md +0 -3
- package/docs/talks2/user-03.md +0 -88
- package/docs/talks2/user-04.md +0 -88
- package/docs/talks2/user-05.md +0 -88
- package/docs/talks2/user-06.md +0 -88
- package/docs/talks2/user-07.md +0 -3
- package/docs/talks3/_body.txt +0 -467
- package/docs/talks3/_index.md +0 -10
- package/docs/talks3/_screenshot.png +0 -0
- package/docs/talks3/assistant-01.md +0 -417
- package/docs/talks3/assistant-02.md +0 -417
- package/docs/talks3/assistant-03.md +0 -29
- package/docs/talks3/assistant-04.md +0 -727
- package/docs/talks3/user-01.md +0 -88
- package/docs/talks3/user-02.md +0 -88
- package/docs/talks3/user-03.md +0 -3
- package/docs/talks3/user-04.md +0 -3
- package/docs/talks4/_body.txt +0 -14
- package/docs/talks4/_index.md +0 -12
- package/docs/talks4/_screenshot.png +0 -0
- package/docs/talks4/assistant-01.md +0 -5
- package/docs/talks4/assistant-02.md +0 -5
- package/docs/talks4/assistant-03.md +0 -35
- package/docs/talks4/assistant-04.md +0 -512
- package/docs/talks4/assistant-05.md +0 -599
- package/docs/talks4/user-01.md +0 -3
- package/docs/talks4/user-02.md +0 -3
- package/docs/talks4/user-03.md +0 -88
- package/docs/talks4/user-04.md +0 -88
- package/docs/talks4/user-05.md +0 -7
- package/docs/talks5/_body.txt +0 -14
- package/docs/talks5/_index.md +0 -13
- package/docs/talks5/_screenshot.png +0 -0
- package/docs/talks5/assistant-01.md +0 -5
- package/docs/talks5/assistant-02.md +0 -5
- package/docs/talks5/assistant-03.md +0 -690
- package/docs/talks5/assistant-04.md +0 -758
- package/docs/talks5/assistant-05.md +0 -974
- package/docs/talks5/user-01.md +0 -3
- package/docs/talks5/user-02.md +0 -3
- package/docs/talks5/user-03.md +0 -105
- package/docs/talks5/user-04.md +0 -105
- package/docs/talks5/user-05.md +0 -63
- package/docs/talks5/user-06.md +0 -105
- package/docs/talks6/_body.txt +0 -14
- package/docs/talks6/_index.md +0 -9
- package/docs/talks6/_screenshot.png +0 -0
- package/docs/talks6/assistant-01.md +0 -5
- package/docs/talks6/assistant-02.md +0 -5
- package/docs/talks6/assistant-03.md +0 -1499
- package/docs/talks6/user-01.md +0 -3
- package/docs/talks6/user-02.md +0 -3
- package/docs/talks6/user-03.md +0 -88
- package/docs/talks6/user-04.md +0 -88
- package/docs/talks7/_body.txt +0 -14
- package/docs/talks7/_index.md +0 -10
- package/docs/talks7/_screenshot.png +0 -0
- package/docs/talks7/assistant-01.md +0 -5
- package/docs/talks7/assistant-02.md +0 -5
- package/docs/talks7/assistant-03.md +0 -523
- package/docs/talks7/assistant-04.md +0 -617
- package/docs/talks7/user-01.md +0 -3
- package/docs/talks7/user-02.md +0 -3
- package/docs/talks7/user-03.md +0 -105
- package/docs/talks7/user-04.md +0 -67
- package/docs/talks8/conversa1.txt +0 -1322
- package/docs/talks8/conversa2.txt +0 -237
- 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
- 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
- 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
- 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
- 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
- package/docs/talks9/README (2).md +0 -489
- package/docs/talks9/README.md +0 -198
- 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
- package/docs/talks9/conversa.txt +0 -544
- package/docs/talks9/other (2).md +0 -39
- package/docs/talks9/other.md +0 -57
- package/docs/talks9/outro.txt +0 -24
- /package/{extension/README.md → docs/extension.md} +0 -0
|
@@ -1,1958 +0,0 @@
|
|
|
1
|
-
# Pesquisa: Retry, Backoff e Rate Limiting (2026)
|
|
2
|
-
|
|
3
|
-
## Sumário
|
|
4
|
-
|
|
5
|
-
1. [Pacotes de Retry](#1-pacotes-de-retry)
|
|
6
|
-
2. [Comparação p-retry vs async-retry](#2-comparação-p-retry-vs-async-retry)
|
|
7
|
-
3. [Algoritmos de Rate Limiting](#3-algoritmos-de-rate-limiting)
|
|
8
|
-
4. [Pacotes de Rate Limiting](#4-pacotes-de-rate-limiting)
|
|
9
|
-
5. [Rate Limiting por Domínio](#5-rate-limiting-por-dominio)
|
|
10
|
-
6. [Status Codes para Retry](#6-status-para-retry)
|
|
11
|
-
7. [Respeitando Retry-After](#7-respeitando-retry-after)
|
|
12
|
-
8. [Retry com Proxy Rotation](#8-retry-com-proxy-rotation)
|
|
13
|
-
9. [Exemplos Completos](#9-exemplos-completos)
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 1. Pacotes de Retry
|
|
18
|
-
|
|
19
|
-
### 1.1 p-retry (Recomendado)
|
|
20
|
-
|
|
21
|
-
**Versão:** 8.0.0
|
|
22
|
-
**Descrição:** Retry com suporte a AbortError e callbacks
|
|
23
|
-
**Última atualização:** 2024
|
|
24
|
-
|
|
25
|
-
```javascript
|
|
26
|
-
import pRetry from 'p-retry';
|
|
27
|
-
|
|
28
|
-
// Uso básico
|
|
29
|
-
const result = await pRetry(async () => {
|
|
30
|
-
const response = await fetch('https://example.com');
|
|
31
|
-
if (!response.ok) {
|
|
32
|
-
throw new Error(`HTTP ${response.status}`);
|
|
33
|
-
}
|
|
34
|
-
return response.json();
|
|
35
|
-
}, {
|
|
36
|
-
retries: 3,
|
|
37
|
-
factor: 2,
|
|
38
|
-
minTimeout: 1000,
|
|
39
|
-
maxTimeout: 10000,
|
|
40
|
-
randomize: true,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Com AbortController
|
|
44
|
-
const controller = new AbortController();
|
|
45
|
-
setTimeout(() => controller.abort(), 5000);
|
|
46
|
-
|
|
47
|
-
const result = await pRetry(async (attemptNumber) => {
|
|
48
|
-
console.log(`Attempt ${attemptNumber}`);
|
|
49
|
-
|
|
50
|
-
const response = await fetch('https://example.com', {
|
|
51
|
-
signal: controller.signal,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
throw new pRetry.AbortError(new Error(`Abort on ${response.status}`));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return response.json();
|
|
59
|
-
}, {
|
|
60
|
-
retries: 5,
|
|
61
|
-
onFailedAttempt: async (error) => {
|
|
62
|
-
console.log(`Attempt ${error.attemptNumber} failed`);
|
|
63
|
-
console.log(`Failures: ${error.retriesLeft}`);
|
|
64
|
-
|
|
65
|
-
// Abort em caso de erro 401/403
|
|
66
|
-
if (error.message.includes('401') || error.message.includes('403')) {
|
|
67
|
-
throw new pRetry.AbortError(error);
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
**Configuração avançada:**
|
|
74
|
-
```javascript
|
|
75
|
-
import pRetry from 'p-retry';
|
|
76
|
-
|
|
77
|
-
const result = await pRetry(async () => {
|
|
78
|
-
// Lógica de retry
|
|
79
|
-
}, {
|
|
80
|
-
retries: 5, // Número máximo de retries
|
|
81
|
-
factor: 2, // Fator de backoff exponencial
|
|
82
|
-
minTimeout: 1000, // Timeout mínimo (1s)
|
|
83
|
-
maxTimeout: 60000, // Timeout máximo (60s)
|
|
84
|
-
randomize: true, // Adicionar aleatoriedade
|
|
85
|
-
signal: controller.signal, // AbortSignal
|
|
86
|
-
onFailedAttempt: async (error) => {
|
|
87
|
-
// Callback após cada falha
|
|
88
|
-
console.log(`Attempt ${error.attemptNumber} failed`);
|
|
89
|
-
console.log(`Retries left: ${error.retriesLeft}`);
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### 1.2 async-retry
|
|
95
|
-
|
|
96
|
-
**Versão:** 1.3.3
|
|
97
|
-
**Descrição:** Retry com suporte a bail function
|
|
98
|
-
**Última atualização:** 2023
|
|
99
|
-
|
|
100
|
-
```javascript
|
|
101
|
-
import retry from 'async-retry';
|
|
102
|
-
|
|
103
|
-
// Uso básico
|
|
104
|
-
const result = await retry(async (bail) => {
|
|
105
|
-
const response = await fetch('https://example.com');
|
|
106
|
-
|
|
107
|
-
// Bail em caso de erro 401
|
|
108
|
-
if (response.status === 401) {
|
|
109
|
-
bail(new Error('Unauthorized'));
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!response.ok) {
|
|
114
|
-
throw new Error(`HTTP ${response.status}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return response.json();
|
|
118
|
-
}, {
|
|
119
|
-
retries: 3,
|
|
120
|
-
factor: 2,
|
|
121
|
-
minTimeout: 1000,
|
|
122
|
-
maxTimeout: 10000,
|
|
123
|
-
randomize: true,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Com retry callback
|
|
127
|
-
const result = await retry(async (bail) => {
|
|
128
|
-
// Lógica de retry
|
|
129
|
-
}, {
|
|
130
|
-
retries: 5,
|
|
131
|
-
onRetry: (error, attempt) => {
|
|
132
|
-
console.log(`Attempt ${attempt} failed, retrying...`);
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
**Configuração avançada:**
|
|
138
|
-
```javascript
|
|
139
|
-
import retry from 'async-retry';
|
|
140
|
-
|
|
141
|
-
const result = await retry(async (bail) => {
|
|
142
|
-
// Lógica de retry
|
|
143
|
-
}, {
|
|
144
|
-
retries: 5, // Número máximo de retries
|
|
145
|
-
factor: 2, // Fator de backoff exponencial
|
|
146
|
-
minTimeout: 1000, // Timeout mínimo (1s)
|
|
147
|
-
maxTimeout: 60000, // Timeout máximo (60s)
|
|
148
|
-
randomize: true, // Adicionar aleatoriedade
|
|
149
|
-
bail: (error) => {
|
|
150
|
-
// Função para parar retries
|
|
151
|
-
return error.message.includes('401');
|
|
152
|
-
},
|
|
153
|
-
onRetry: (error, attempt) => {
|
|
154
|
-
// Callback após cada falha
|
|
155
|
-
console.log(`Attempt ${attempt} failed`);
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### 1.3 retry
|
|
161
|
-
|
|
162
|
-
**Versão:** 0.13.1
|
|
163
|
-
**Descrição:** Biblioteca fundacional de retry
|
|
164
|
-
**Última atualização:** 2022
|
|
165
|
-
|
|
166
|
-
```javascript
|
|
167
|
-
import retry from 'retry';
|
|
168
|
-
|
|
169
|
-
// Uso com callbacks
|
|
170
|
-
const operation = retry.operation({
|
|
171
|
-
retries: 5,
|
|
172
|
-
factor: 2,
|
|
173
|
-
minTimeout: 1000,
|
|
174
|
-
maxTimeout: 60000,
|
|
175
|
-
randomize: true,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
operation.attempt(async (currentAttempt) => {
|
|
179
|
-
try {
|
|
180
|
-
const response = await fetch('https://example.com');
|
|
181
|
-
if (!response.ok) {
|
|
182
|
-
throw new Error(`HTTP ${response.status}`);
|
|
183
|
-
}
|
|
184
|
-
const data = await response.json();
|
|
185
|
-
operation.stop();
|
|
186
|
-
return data;
|
|
187
|
-
} catch (error) {
|
|
188
|
-
if (operation.retry(error)) {
|
|
189
|
-
console.log(`Attempt ${currentAttempt} failed, retrying...`);
|
|
190
|
-
} else {
|
|
191
|
-
console.log(`All retries exhausted`);
|
|
192
|
-
throw error;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// Uso com Promises
|
|
198
|
-
import { promisify } from 'util';
|
|
199
|
-
const retryAsync = promisify(retry);
|
|
200
|
-
|
|
201
|
-
const result = await retryAsync(async () => {
|
|
202
|
-
const response = await fetch('https://example.com');
|
|
203
|
-
return response.json();
|
|
204
|
-
}, {
|
|
205
|
-
retries: 5,
|
|
206
|
-
factor: 2,
|
|
207
|
-
minTimeout: 1000,
|
|
208
|
-
maxTimeout: 60000,
|
|
209
|
-
});
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### 1.4 exponential-backoff
|
|
213
|
-
|
|
214
|
-
**Versão:** 3.1.3
|
|
215
|
-
**Descrição:** Geração de delays com backoff exponencial
|
|
216
|
-
**Última atualização:** 2023
|
|
217
|
-
|
|
218
|
-
```javascript
|
|
219
|
-
import { ExponentialBackoff } from 'exponential-backoff';
|
|
220
|
-
|
|
221
|
-
// Uso básico
|
|
222
|
-
const backoff = new ExponentialBackoff({
|
|
223
|
-
maxRetries: 5,
|
|
224
|
-
initialDelay: 1000,
|
|
225
|
-
maxDelay: 60000,
|
|
226
|
-
factor: 2,
|
|
227
|
-
jitter: true,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const result = await backoff.execute(async () => {
|
|
231
|
-
const response = await fetch('https://example.com');
|
|
232
|
-
if (!response.ok) {
|
|
233
|
-
throw new Error(`HTTP ${response.status}`);
|
|
234
|
-
}
|
|
235
|
-
return response.json();
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// Uso com delay manual
|
|
239
|
-
import { getDelay } from 'exponential-backoff';
|
|
240
|
-
|
|
241
|
-
for (let i = 0; i < 5; i++) {
|
|
242
|
-
const delay = getDelay(i, {
|
|
243
|
-
initialDelay: 1000,
|
|
244
|
-
maxDelay: 60000,
|
|
245
|
-
factor: 2,
|
|
246
|
-
jitter: true,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
console.log(`Attempt ${i + 1} will wait ${delay}ms`);
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## 2. Comparação p-retry vs async-retry
|
|
256
|
-
|
|
257
|
-
### Tabela Comparativa
|
|
258
|
-
|
|
259
|
-
| Característica | p-retry 8.0.0 | async-retry 1.3.3 |
|
|
260
|
-
|----------------|---------------|-------------------|
|
|
261
|
-
| **Tipo** | ESM/CommonJS | CommonJS |
|
|
262
|
-
| **AbortError** | Nativo | Via bail |
|
|
263
|
-
| **Callbacks** | onFailedAttempt | onRetry |
|
|
264
|
-
| **Bail** | AbortError | bail function |
|
|
265
|
-
| **Signal** | AbortSignal | Não suporta |
|
|
266
|
-
| **Fator de backoff** | Configurável | Configurável |
|
|
267
|
-
| **Jitter** | Configurável | Configurável |
|
|
268
|
-
| **Última atualização** | 2024 | 2023 |
|
|
269
|
-
| **Manutenção** | Ativa | Estável |
|
|
270
|
-
| **Bundle size** | ~2KB | ~1.5KB |
|
|
271
|
-
|
|
272
|
-
### Quando Usar Cada Um
|
|
273
|
-
|
|
274
|
-
**Use p-retry quando:**
|
|
275
|
-
- Precisa de AbortController/AbortSignal
|
|
276
|
-
- Quer callbacks detalhados (onFailedAttempt)
|
|
277
|
-
- Usa ESM nativo
|
|
278
|
-
- Precisa de granularidade no controle de retry
|
|
279
|
-
|
|
280
|
-
**Use async-retry quando:**
|
|
281
|
-
- Prefere API com bail function
|
|
282
|
-
- Usa CommonJS
|
|
283
|
-
- Projeto já usa async-retry
|
|
284
|
-
- Não precisa de AbortSignal
|
|
285
|
-
|
|
286
|
-
### Exemplo Comparativo
|
|
287
|
-
|
|
288
|
-
```javascript
|
|
289
|
-
// p-retry
|
|
290
|
-
import pRetry from 'p-retry';
|
|
291
|
-
|
|
292
|
-
const result1 = await pRetry(async (attempt) => {
|
|
293
|
-
const response = await fetch('https://example.com');
|
|
294
|
-
if (response.status === 429) {
|
|
295
|
-
throw new Error('Rate limited');
|
|
296
|
-
}
|
|
297
|
-
return response.json();
|
|
298
|
-
}, {
|
|
299
|
-
retries: 3,
|
|
300
|
-
onFailedAttempt: async (error) => {
|
|
301
|
-
console.log(`Attempt ${error.attemptNumber} failed`);
|
|
302
|
-
},
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
// async-retry
|
|
306
|
-
import retry from 'async-retry';
|
|
307
|
-
|
|
308
|
-
const result2 = await retry(async (bail) => {
|
|
309
|
-
const response = await fetch('https://example.com');
|
|
310
|
-
if (response.status === 429) {
|
|
311
|
-
bail(new Error('Rate limited'));
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
return response.json();
|
|
315
|
-
}, {
|
|
316
|
-
retries: 3,
|
|
317
|
-
onRetry: (error, attempt) => {
|
|
318
|
-
console.log(`Attempt ${attempt} failed`);
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
---
|
|
324
|
-
|
|
325
|
-
## 3. Algoritmos de Rate Limiting
|
|
326
|
-
|
|
327
|
-
### 3.1 Token Bucket
|
|
328
|
-
|
|
329
|
-
**Como funciona:**
|
|
330
|
-
- Bucket contém N tokens
|
|
331
|
-
- Cada request consome 1 token
|
|
332
|
-
- Tokens são adicionados a uma taxa fixa
|
|
333
|
-
- Se bucket vazio, request é rejeitado
|
|
334
|
-
|
|
335
|
-
**Vantagens:**
|
|
336
|
-
- Permite bursts curtos
|
|
337
|
-
- Simples de implementar
|
|
338
|
-
- Baixa memória
|
|
339
|
-
|
|
340
|
-
**Desvantagens:**
|
|
341
|
-
- Não garante taxa suave
|
|
342
|
-
- Burst pode sobrecarregar sistema
|
|
343
|
-
|
|
344
|
-
```javascript
|
|
345
|
-
class TokenBucket {
|
|
346
|
-
constructor(capacity, refillRate) {
|
|
347
|
-
this.capacity = capacity;
|
|
348
|
-
this.tokens = capacity;
|
|
349
|
-
this.refillRate = refillRate; // tokens por segundo
|
|
350
|
-
this.lastRefill = Date.now();
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
consume() {
|
|
354
|
-
this.refill();
|
|
355
|
-
if (this.tokens > 0) {
|
|
356
|
-
this.tokens--;
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
refill() {
|
|
363
|
-
const now = Date.now();
|
|
364
|
-
const elapsed = (now - this.lastRefill) / 1000;
|
|
365
|
-
const tokensToAdd = elapsed * this.refillRate;
|
|
366
|
-
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
|
|
367
|
-
this.lastRefill = now;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
getTokens() {
|
|
371
|
-
this.refill();
|
|
372
|
-
return Math.floor(this.tokens);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Uso
|
|
377
|
-
const bucket = new TokenBucket(10, 1); // 10 tokens, 1 por segundo
|
|
378
|
-
|
|
379
|
-
async function makeRequest() {
|
|
380
|
-
if (bucket.consume()) {
|
|
381
|
-
return await fetch('https://example.com');
|
|
382
|
-
} else {
|
|
383
|
-
throw new Error('Rate limited');
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### 3.2 Sliding Window Log
|
|
389
|
-
|
|
390
|
-
**Como funciona:**
|
|
391
|
-
- Mantém log de todos os requests no último período
|
|
392
|
-
- Conta requests no período atual
|
|
393
|
-
- Se count > limit, rejeita
|
|
394
|
-
|
|
395
|
-
**Vantagens:**
|
|
396
|
-
- Preciso
|
|
397
|
-
- Sem edge cases
|
|
398
|
-
|
|
399
|
-
**Desvantagens:**
|
|
400
|
-
- Alta memória (armazena todos os timestamps)
|
|
401
|
-
- Computacionalmente caro
|
|
402
|
-
|
|
403
|
-
```javascript
|
|
404
|
-
class SlidingWindowLog {
|
|
405
|
-
constructor(windowSize, maxRequests) {
|
|
406
|
-
this.windowSize = windowSize; // em ms
|
|
407
|
-
this.maxRequests = maxRequests;
|
|
408
|
-
this.logs = [];
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
isAllowed() {
|
|
412
|
-
const now = Date.now();
|
|
413
|
-
const windowStart = now - this.windowSize;
|
|
414
|
-
|
|
415
|
-
// Remover logs antigos
|
|
416
|
-
this.logs = this.logs.filter(time => time > windowStart);
|
|
417
|
-
|
|
418
|
-
if (this.logs.length < this.maxRequests) {
|
|
419
|
-
this.logs.push(now);
|
|
420
|
-
return true;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
getCount() {
|
|
427
|
-
const now = Date.now();
|
|
428
|
-
const windowStart = now - this.windowSize;
|
|
429
|
-
return this.logs.filter(time => time > windowStart).length;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Uso
|
|
434
|
-
const limiter = new SlidingWindowLog(60000, 100); // 100 requests por minuto
|
|
435
|
-
|
|
436
|
-
async function makeRequest() {
|
|
437
|
-
if (limiter.isAllowed()) {
|
|
438
|
-
return await fetch('https://example.com');
|
|
439
|
-
} else {
|
|
440
|
-
throw new Error('Rate limited');
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### 3.3 Sliding Window Counter
|
|
446
|
-
|
|
447
|
-
**Como funciona:**
|
|
448
|
-
- Divide tempo em janelas fixas
|
|
449
|
-
- Conta requests em cada janela
|
|
450
|
-
- Estima contagem atual baseado na janela anterior
|
|
451
|
-
|
|
452
|
-
**Vantagens:**
|
|
453
|
-
- Balance entre precisão e performance
|
|
454
|
-
- Baixa memória
|
|
455
|
-
- Simples
|
|
456
|
-
|
|
457
|
-
**Desvantagens:**
|
|
458
|
-
- Não é 100% preciso
|
|
459
|
-
- Pode ter edge cases
|
|
460
|
-
|
|
461
|
-
```javascript
|
|
462
|
-
class SlidingWindowCounter {
|
|
463
|
-
constructor(windowSize, maxRequests) {
|
|
464
|
-
this.windowSize = windowSize;
|
|
465
|
-
this.maxRequests = maxRequests;
|
|
466
|
-
this.currentWindow = Math.floor(Date.now() / windowSize);
|
|
467
|
-
this.currentCount = 0;
|
|
468
|
-
this.previousCount = 0;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
isAllowed() {
|
|
472
|
-
const now = Date.now();
|
|
473
|
-
const currentWindow = Math.floor(now / this.windowSize);
|
|
474
|
-
|
|
475
|
-
// Nova janela
|
|
476
|
-
if (currentWindow !== this.currentWindow) {
|
|
477
|
-
this.previousCount = this.currentCount;
|
|
478
|
-
this.currentCount = 0;
|
|
479
|
-
this.currentWindow = currentWindow;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// Calcular contagem estimada
|
|
483
|
-
const elapsed = now % this.windowSize;
|
|
484
|
-
const weight = elapsed / this.windowSize;
|
|
485
|
-
const estimatedCount = this.previousCount * (1 - weight) + this.currentCount;
|
|
486
|
-
|
|
487
|
-
if (estimatedCount < this.maxRequests) {
|
|
488
|
-
this.currentCount++;
|
|
489
|
-
return true;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return false;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
getCount() {
|
|
496
|
-
const now = Date.now();
|
|
497
|
-
const currentWindow = Math.floor(now / this.windowSize);
|
|
498
|
-
|
|
499
|
-
if (currentWindow !== this.currentWindow) {
|
|
500
|
-
return 0;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return this.currentCount;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Uso
|
|
508
|
-
const limiter = new SlidingWindowCounter(60000, 100); // 100 requests por minuto
|
|
509
|
-
|
|
510
|
-
async function makeRequest() {
|
|
511
|
-
if (limiter.isAllowed()) {
|
|
512
|
-
return await fetch('https://example.com');
|
|
513
|
-
} else {
|
|
514
|
-
throw new Error('Rate limited');
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
### 3.4 Fixed Window
|
|
520
|
-
|
|
521
|
-
**Como funciona:**
|
|
522
|
-
- Divide tempo em janelas fixas
|
|
523
|
-
- Conta requests em cada janela
|
|
524
|
-
- Reseta contador no início da janela
|
|
525
|
-
|
|
526
|
-
**Vantagens:**
|
|
527
|
-
- Simples
|
|
528
|
-
- Baixa memória
|
|
529
|
-
- Rápido
|
|
530
|
-
|
|
531
|
-
**Desvantagens:**
|
|
532
|
-
- Edge cases no início/fim da janela
|
|
533
|
-
- Pode permitir bursts
|
|
534
|
-
|
|
535
|
-
```javascript
|
|
536
|
-
class FixedWindow {
|
|
537
|
-
constructor(windowSize, maxRequests) {
|
|
538
|
-
this.windowSize = windowSize;
|
|
539
|
-
this.maxRequests = maxRequests;
|
|
540
|
-
this.windowStart = Date.now();
|
|
541
|
-
this.count = 0;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
isAllowed() {
|
|
545
|
-
const now = Date.now();
|
|
546
|
-
|
|
547
|
-
// Nova janela
|
|
548
|
-
if (now - this.windowStart >= this.windowSize) {
|
|
549
|
-
this.windowStart = now;
|
|
550
|
-
this.count = 0;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (this.count < this.maxRequests) {
|
|
554
|
-
this.count++;
|
|
555
|
-
return true;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
return false;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
getCount() {
|
|
562
|
-
const now = Date.now();
|
|
563
|
-
if (now - this.windowStart >= this.windowSize) {
|
|
564
|
-
return 0;
|
|
565
|
-
}
|
|
566
|
-
return this.count;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Uso
|
|
571
|
-
const limiter = new FixedWindow(60000, 100); // 100 requests por minuto
|
|
572
|
-
|
|
573
|
-
async function makeRequest() {
|
|
574
|
-
if (limiter.isAllowed()) {
|
|
575
|
-
return await fetch('https://example.com');
|
|
576
|
-
} else {
|
|
577
|
-
throw new Error('Rate limited');
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
### 3.5 Leaky Bucket
|
|
583
|
-
|
|
584
|
-
**Como funciona:**
|
|
585
|
-
- Requests entram em uma fila
|
|
586
|
-
- Requests saem a uma taxa fixa
|
|
587
|
-
- Se fila cheia, request é rejeitado
|
|
588
|
-
|
|
589
|
-
**Vantagens:**
|
|
590
|
-
- Taxa suave e constante
|
|
591
|
-
- Sem bursts
|
|
592
|
-
|
|
593
|
-
**Desvantagens:**
|
|
594
|
-
- Alta latência
|
|
595
|
-
- Não permite bursts
|
|
596
|
-
- Complexidade
|
|
597
|
-
|
|
598
|
-
```javascript
|
|
599
|
-
class LeakyBucket {
|
|
600
|
-
constructor(capacity, leakRate) {
|
|
601
|
-
this.capacity = capacity;
|
|
602
|
-
this.leakRate = leakRate; // requests por segundo
|
|
603
|
-
this.queue = [];
|
|
604
|
-
this.lastLeak = Date.now();
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
isAllowed() {
|
|
608
|
-
this.leak();
|
|
609
|
-
|
|
610
|
-
if (this.queue.length < this.capacity) {
|
|
611
|
-
this.queue.push(Date.now());
|
|
612
|
-
return true;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
leak() {
|
|
619
|
-
const now = Date.now();
|
|
620
|
-
const elapsed = (now - this.lastLeak) / 1000;
|
|
621
|
-
const leakCount = Math.floor(elapsed * this.leakRate);
|
|
622
|
-
|
|
623
|
-
if (leakCount > 0) {
|
|
624
|
-
this.queue.splice(0, leakCount);
|
|
625
|
-
this.lastLeak = now;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
getQueueSize() {
|
|
630
|
-
this.leak();
|
|
631
|
-
return this.queue.length;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Uso
|
|
636
|
-
const bucket = new LeakyBucket(10, 1); // 10 capacity, 1 por segundo
|
|
637
|
-
|
|
638
|
-
async function makeRequest() {
|
|
639
|
-
if (bucket.isAllowed()) {
|
|
640
|
-
return await fetch('https://example.com');
|
|
641
|
-
} else {
|
|
642
|
-
throw new Error('Rate limited');
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
### Tabela Comparativa de Algoritmos
|
|
648
|
-
|
|
649
|
-
| Algoritmo | Precisão | Performance | Memória | Burst | Complexidade |
|
|
650
|
-
|-----------|----------|-------------|---------|-------|--------------|
|
|
651
|
-
| Token Bucket | Média | Alta | Baixa | Sim | Baixa |
|
|
652
|
-
| Sliding Window Log | Alta | Baixa | Alta | Não | Média |
|
|
653
|
-
| Sliding Window Counter | Média-Alta | Alta | Baixa | Parcial | Média |
|
|
654
|
-
| Fixed Window | Baixa | Alta | Baixa | Sim | Baixa |
|
|
655
|
-
| Leaky Bucket | Alta | Média | Média | Não | Média |
|
|
656
|
-
|
|
657
|
-
---
|
|
658
|
-
|
|
659
|
-
## 4. Pacotes de Rate Limiting
|
|
660
|
-
|
|
661
|
-
### 4.1 bottleneck
|
|
662
|
-
|
|
663
|
-
**Versão:** 2.19.5
|
|
664
|
-
**Descrição:** Rate limiting avançado com clusters e jobs
|
|
665
|
-
**Última atualização:** 2023
|
|
666
|
-
|
|
667
|
-
```javascript
|
|
668
|
-
import Bottleneck from 'bottleneck';
|
|
669
|
-
|
|
670
|
-
// Rate limiter básico
|
|
671
|
-
const limiter = new Bottleneck({
|
|
672
|
-
minTime: 100, // Mínimo 100ms entre requests
|
|
673
|
-
maxConcurrent: 5, // Máximo 5 requests simultâneos
|
|
674
|
-
reservoir: 10, // 10 requests por período
|
|
675
|
-
reservoirRefreshAmount: 10,
|
|
676
|
-
reservoirRefreshInterval: 60000, // 1 minuto
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
// Usar com fetch
|
|
680
|
-
async function makeRequest(url) {
|
|
681
|
-
return limiter.schedule(async () => {
|
|
682
|
-
const response = await fetch(url);
|
|
683
|
-
return response.json();
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Múltiplos limiters (clusters)
|
|
688
|
-
const cluster = new Bottleneck.Cluster({
|
|
689
|
-
datastore: 'redis',
|
|
690
|
-
redis: { host: 'localhost', port: 6379 },
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
const limiter1 = cluster.cluster('api1');
|
|
694
|
-
const limiter2 = cluster.cluster('api2');
|
|
695
|
-
|
|
696
|
-
// Rate limits diferentes por endpoint
|
|
697
|
-
const apiLimiter = new Bottleneck({
|
|
698
|
-
reservoir: 100,
|
|
699
|
-
reservoirRefreshAmount: 100,
|
|
700
|
-
reservoirRefreshInterval: 60000,
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
const searchLimiter = new Bottleneck({
|
|
704
|
-
minTime: 500,
|
|
705
|
-
maxConcurrent: 2,
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
// Encadear limiters
|
|
709
|
-
const combinedLimiter = Bottleneck.chain(apiLimiter, searchLimiter);
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
### 4.2 p-throttle
|
|
713
|
-
|
|
714
|
-
**Versão:** 6.2.0
|
|
715
|
-
**Descrição:** Throttling de funções com suporte a ESM
|
|
716
|
-
**Última atualização:** 2023
|
|
717
|
-
|
|
718
|
-
```javascript
|
|
719
|
-
import pThrottle from 'p-throttle';
|
|
720
|
-
|
|
721
|
-
// Limitar a 10 chamadas por segundo
|
|
722
|
-
const throttle = pThrottle({
|
|
723
|
-
limit: 10,
|
|
724
|
-
interval: 1000,
|
|
725
|
-
strict: true,
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
const throttledFetch = throttle(async (url) => {
|
|
729
|
-
const response = await fetch(url);
|
|
730
|
-
return response.json();
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
// Usar
|
|
734
|
-
for (const url of urls) {
|
|
735
|
-
const data = await throttledFetch(url);
|
|
736
|
-
console.log(data);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// Throttled function com abort
|
|
740
|
-
const controller = new AbortController();
|
|
741
|
-
const throttledFetchWithAbort = throttle(async (url) => {
|
|
742
|
-
const response = await fetch(url, { signal: controller.signal });
|
|
743
|
-
return response.json();
|
|
744
|
-
});
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
### 4.3 p-limit
|
|
748
|
-
|
|
749
|
-
**Versão:** 7.3.0
|
|
750
|
-
**Descrição:** Limitação de concorrência
|
|
751
|
-
**Última atualização:** 2023
|
|
752
|
-
|
|
753
|
-
```javascript
|
|
754
|
-
import pLimit from 'p-limit';
|
|
755
|
-
|
|
756
|
-
// Limitar a 5 concorrências
|
|
757
|
-
const limit = pLimit(5);
|
|
758
|
-
|
|
759
|
-
// Criar tasks com limite
|
|
760
|
-
const tasks = urls.map(url => {
|
|
761
|
-
return limit(async () => {
|
|
762
|
-
const response = await fetch(url);
|
|
763
|
-
return response.json();
|
|
764
|
-
});
|
|
765
|
-
});
|
|
766
|
-
|
|
767
|
-
const results = await Promise.all(tasks);
|
|
768
|
-
|
|
769
|
-
// Verificar estado
|
|
770
|
-
console.log(limit.activeCount); // Requests ativos
|
|
771
|
-
console.log(limit.pendingCount); // Requests pendentes
|
|
772
|
-
|
|
773
|
-
// Limitar por domínio
|
|
774
|
-
const domainLimits = new Map();
|
|
775
|
-
|
|
776
|
-
function getLimiterForDomain(domain) {
|
|
777
|
-
if (!domainLimits.has(domain)) {
|
|
778
|
-
domainLimits.set(domain, pLimit(5));
|
|
779
|
-
}
|
|
780
|
-
return domainLimits.get(domain);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// Usar
|
|
784
|
-
const limiter = getLimiterForDomain('example.com');
|
|
785
|
-
const result = await limiter(async () => {
|
|
786
|
-
return await fetch('https://example.com/api');
|
|
787
|
-
});
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
### 4.4 p-queue
|
|
791
|
-
|
|
792
|
-
**Versão:** 9.2.0
|
|
793
|
-
**Descrição:** Fila de tarefas com prioridade
|
|
794
|
-
**Última atualização:** 2024
|
|
795
|
-
|
|
796
|
-
```javascript
|
|
797
|
-
import PQueue from 'p-queue';
|
|
798
|
-
|
|
799
|
-
// Fila com limite de concorrência
|
|
800
|
-
const queue = new PQueue({
|
|
801
|
-
concurrency: 5,
|
|
802
|
-
intervalCap: 10,
|
|
803
|
-
interval: 1000,
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
// Adicionar tarefas com prioridade
|
|
807
|
-
queue.add(async () => {
|
|
808
|
-
return await fetch('https://example.com/low');
|
|
809
|
-
}, { priority: 1 });
|
|
810
|
-
|
|
811
|
-
queue.add(async () => {
|
|
812
|
-
return await fetch('https://example.com/high');
|
|
813
|
-
}, { priority: 10 });
|
|
814
|
-
|
|
815
|
-
// Verificar estado
|
|
816
|
-
console.log(queue.size); // Tamanho da fila
|
|
817
|
-
console.log(queue.pending); // Tarefas pendentes
|
|
818
|
-
console.log(queue.isPaused); // Se está pausada
|
|
819
|
-
|
|
820
|
-
// Pausar/Retomar
|
|
821
|
-
queue.pause();
|
|
822
|
-
queue.start();
|
|
823
|
-
|
|
824
|
-
// Adicionar com timeout
|
|
825
|
-
queue.add(async () => {
|
|
826
|
-
return await fetch('https://example.com');
|
|
827
|
-
}, {
|
|
828
|
-
timeout: 5000,
|
|
829
|
-
signal: AbortSignal.timeout(5000),
|
|
830
|
-
});
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
### 4.5 limiter
|
|
834
|
-
|
|
835
|
-
**Versão:** 3.0.0
|
|
836
|
-
**Descrição:** Rate limiting simples baseado em Redis
|
|
837
|
-
**Última atualização:** 2022
|
|
838
|
-
|
|
839
|
-
```javascript
|
|
840
|
-
import { RateLimiterRedis } from 'limiter';
|
|
841
|
-
import Redis from 'ioredis';
|
|
842
|
-
|
|
843
|
-
const redis = new Redis();
|
|
844
|
-
|
|
845
|
-
const limiter = new RateLimiterRedis({
|
|
846
|
-
storeClient: redis,
|
|
847
|
-
keyPrefix: 'rl',
|
|
848
|
-
points: 100, // 100 requests
|
|
849
|
-
duration: 60, // por 60 segundos
|
|
850
|
-
blockDuration: 60, // bloquear por 60 segundos se exceder
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
// Consumir pontos
|
|
854
|
-
try {
|
|
855
|
-
await limiter.consume('user123');
|
|
856
|
-
const response = await fetch('https://example.com');
|
|
857
|
-
return response.json();
|
|
858
|
-
} catch (rateLimiterRes) {
|
|
859
|
-
const retryAfter = Math.ceil(rateLimiterRes.msBeforeNext / 1000);
|
|
860
|
-
console.log(`Rate limited. Retry after ${retryAfter}s`);
|
|
861
|
-
throw new Error(`Rate limited. Retry after ${retryAfter}s`);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// Verificar pontos restantes
|
|
865
|
-
const resRateLimiter = await limiter.get('user123');
|
|
866
|
-
console.log(`Points remaining: ${resRateLimiter.remainingPoints}`);
|
|
867
|
-
```
|
|
868
|
-
|
|
869
|
-
### 4.6 rate-limiter-flexible
|
|
870
|
-
|
|
871
|
-
**Versão:** 11.1.0
|
|
872
|
-
**Descrição:** Rate limiting flexível e robusto
|
|
873
|
-
**Última atualização:** 2024
|
|
874
|
-
|
|
875
|
-
```javascript
|
|
876
|
-
import { RateLimiterRedis, RateLimiterMemory } from 'rate-limiter-flexible';
|
|
877
|
-
|
|
878
|
-
// Rate limiter em memória
|
|
879
|
-
const rateLimiter = new RateLimiterMemory({
|
|
880
|
-
points: 100, // 100 requests
|
|
881
|
-
duration: 60, // por 60 segundos
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
// Rate limiter com Redis
|
|
885
|
-
const redisRateLimiter = new RateLimiterRedis({
|
|
886
|
-
points: 100,
|
|
887
|
-
duration: 60,
|
|
888
|
-
storeClient: redisClient,
|
|
889
|
-
keyPrefix: 'rl',
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
// Consumir pontos
|
|
893
|
-
try {
|
|
894
|
-
const res = await rateLimiter.consume('user123');
|
|
895
|
-
console.log(`Points remaining: ${res.remainingPoints}`);
|
|
896
|
-
console.log(`Retry after: ${res.msBeforeNext}ms`);
|
|
897
|
-
} catch (rejRes) {
|
|
898
|
-
console.log(`Rate limited. Retry after ${rejRes.msBeforeNext}ms`);
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Bloquear IP após múltiplas falhas
|
|
902
|
-
const rateLimiterBlock = new RateLimiterMemory({
|
|
903
|
-
points: 1,
|
|
904
|
-
duration: 1,
|
|
905
|
-
blockDuration: 3600, // Bloquear por 1 hora
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
// Rate limiting com sliding window
|
|
909
|
-
const slidingWindowLimiter = new RateLimiterMemory({
|
|
910
|
-
points: 100,
|
|
911
|
-
duration: 60,
|
|
912
|
-
execEvenly: true, // Executar均匀mente
|
|
913
|
-
execEvenlyMinDelayMs: 100, // Mínimo 100ms entre requests
|
|
914
|
-
});
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
---
|
|
918
|
-
|
|
919
|
-
## 5. Rate Limiting por Domínio
|
|
920
|
-
|
|
921
|
-
### 5.1 Implementação com Map
|
|
922
|
-
|
|
923
|
-
```javascript
|
|
924
|
-
import { RateLimiterMemory } from 'rate-limiter-flexible';
|
|
925
|
-
|
|
926
|
-
class DomainRateLimiter {
|
|
927
|
-
constructor(defaults = {}) {
|
|
928
|
-
this.limiters = new Map();
|
|
929
|
-
this.defaults = {
|
|
930
|
-
points: defaults.points || 100,
|
|
931
|
-
duration: defaults.duration || 60,
|
|
932
|
-
blockDuration: defaults.blockDuration || 0,
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
getLimiter(domain) {
|
|
937
|
-
if (!this.limiters.has(domain)) {
|
|
938
|
-
this.limiters.set(domain, new RateLimiterMemory({
|
|
939
|
-
points: this.defaults.points,
|
|
940
|
-
duration: this.defaults.duration,
|
|
941
|
-
blockDuration: this.defaults.blockDuration,
|
|
942
|
-
}));
|
|
943
|
-
}
|
|
944
|
-
return this.limiters.get(domain);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
async consume(domain, key = 'default', points = 1) {
|
|
948
|
-
const limiter = this.getLimiter(domain);
|
|
949
|
-
try {
|
|
950
|
-
const res = await limiter.consume(key, points);
|
|
951
|
-
return { allowed: true, remaining: res.remainingPoints };
|
|
952
|
-
} catch (rejRes) {
|
|
953
|
-
return {
|
|
954
|
-
allowed: false,
|
|
955
|
-
retryAfter: Math.ceil(rejRes.msBeforeNext / 1000),
|
|
956
|
-
remaining: 0,
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
async reset(domain, key = 'default') {
|
|
962
|
-
const limiter = this.getLimiter(domain);
|
|
963
|
-
await limiter.delete(key);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// Uso
|
|
968
|
-
const limiter = new DomainRateLimiter({
|
|
969
|
-
points: 100,
|
|
970
|
-
duration: 60,
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
async function fetchWithRateLimit(url) {
|
|
974
|
-
const domain = new URL(url).hostname;
|
|
975
|
-
const result = await limiter.consume(domain);
|
|
976
|
-
|
|
977
|
-
if (!result.allowed) {
|
|
978
|
-
console.log(`Rate limited by ${domain}. Retry after ${result.retryAfter}s`);
|
|
979
|
-
throw new Error(`Rate limited. Retry after ${result.retryAfter}s`);
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
return await fetch(url);
|
|
983
|
-
}
|
|
984
|
-
```
|
|
985
|
-
|
|
986
|
-
### 5.2 Implementação com Crawlee
|
|
987
|
-
|
|
988
|
-
```javascript
|
|
989
|
-
import { PlaywrightCrawler } from 'crawlee';
|
|
990
|
-
|
|
991
|
-
class CrawleeDomainRateLimiter {
|
|
992
|
-
constructor(limits = {}) {
|
|
993
|
-
this.limits = limits;
|
|
994
|
-
this.domainRequests = new Map();
|
|
995
|
-
this.domainWindows = new Map();
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
getLimitForDomain(domain) {
|
|
999
|
-
return this.limits[domain] || { requestsPerMinute: 60 };
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
async checkRateLimit(domain) {
|
|
1003
|
-
const limit = this.getLimitForDomain(domain);
|
|
1004
|
-
const now = Date.now();
|
|
1005
|
-
const windowStart = now - 60000;
|
|
1006
|
-
|
|
1007
|
-
if (!this.domainWindows.has(domain)) {
|
|
1008
|
-
this.domainWindows.set(domain, []);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const requests = this.domainWindows.get(domain)
|
|
1012
|
-
.filter(time => time > windowStart);
|
|
1013
|
-
|
|
1014
|
-
this.domainWindows.set(domain, requests);
|
|
1015
|
-
|
|
1016
|
-
if (requests.length >= limit.requestsPerMinute) {
|
|
1017
|
-
const oldestRequest = requests[0];
|
|
1018
|
-
const retryAfter = Math.ceil((oldestRequest + 60000 - now) / 1000);
|
|
1019
|
-
return { allowed: false, retryAfter };
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
requests.push(now);
|
|
1023
|
-
return { allowed: true, remaining: limit.requestsPerMinute - requests.length };
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// Uso com Crawlee
|
|
1028
|
-
const rateLimiter = new CrawleeDomainRateLimiter({
|
|
1029
|
-
'example.com': { requestsPerMinute: 30 },
|
|
1030
|
-
'api.example.com': { requestsPerMinute: 10 },
|
|
1031
|
-
'default': { requestsPerMinute: 60 },
|
|
1032
|
-
});
|
|
1033
|
-
|
|
1034
|
-
const crawler = new PlaywrightCrawler({
|
|
1035
|
-
requestHandler: async ({ request, page }) => {
|
|
1036
|
-
const domain = new URL(request.url).hostname;
|
|
1037
|
-
const rateCheck = await rateLimiter.checkRateLimit(domain);
|
|
1038
|
-
|
|
1039
|
-
if (!rateCheck.allowed) {
|
|
1040
|
-
console.log(`Rate limited. Retry after ${rateCheck.retryAfter}s`);
|
|
1041
|
-
await page.waitForTimeout(rateCheck.retryAfter * 1000);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
await page.goto(request.url);
|
|
1045
|
-
return { title: await page.title() };
|
|
1046
|
-
},
|
|
1047
|
-
});
|
|
1048
|
-
```
|
|
1049
|
-
|
|
1050
|
-
### 5.3 Configuração por Endpoint
|
|
1051
|
-
|
|
1052
|
-
```javascript
|
|
1053
|
-
import { RateLimiterMemory } from 'rate-limiter-flexible';
|
|
1054
|
-
|
|
1055
|
-
const endpointLimits = {
|
|
1056
|
-
'api.example.com': {
|
|
1057
|
-
'/search': { points: 10, duration: 60 },
|
|
1058
|
-
'/users': { points: 100, duration: 60 },
|
|
1059
|
-
'/posts': { points: 50, duration: 60 },
|
|
1060
|
-
},
|
|
1061
|
-
'cdn.example.com': {
|
|
1062
|
-
'/images': { points: 1000, duration: 60 },
|
|
1063
|
-
'/videos': { points: 100, duration: 60 },
|
|
1064
|
-
},
|
|
1065
|
-
};
|
|
1066
|
-
|
|
1067
|
-
class EndpointRateLimiter {
|
|
1068
|
-
constructor(limits) {
|
|
1069
|
-
this.limits = limits;
|
|
1070
|
-
this.limiters = new Map();
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
getLimiter(domain, path) {
|
|
1074
|
-
const key = `${domain}:${path}`;
|
|
1075
|
-
if (!this.limiters.has(key)) {
|
|
1076
|
-
const limit = this.limits[domain]?.[path] || { points: 60, duration: 60 };
|
|
1077
|
-
this.limiters.set(key, new RateLimiterMemory(limit));
|
|
1078
|
-
}
|
|
1079
|
-
return this.limiters.get(key);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
async consume(domain, path) {
|
|
1083
|
-
const limiter = this.getLimiter(domain, path);
|
|
1084
|
-
try {
|
|
1085
|
-
const res = await limiter.consume();
|
|
1086
|
-
return { allowed: true, remaining: res.remainingPoints };
|
|
1087
|
-
} catch (rejRes) {
|
|
1088
|
-
return {
|
|
1089
|
-
allowed: false,
|
|
1090
|
-
retryAfter: Math.ceil(rejRes.msBeforeNext / 1000),
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
// Uso
|
|
1097
|
-
const limiter = new EndpointRateLimiter(endpointLimits);
|
|
1098
|
-
|
|
1099
|
-
async function apiCall(url) {
|
|
1100
|
-
const urlObj = new URL(url);
|
|
1101
|
-
const result = await limiter.consume(urlObj.hostname, urlObj.pathname);
|
|
1102
|
-
|
|
1103
|
-
if (!result.allowed) {
|
|
1104
|
-
throw new Error(`Rate limited. Retry after ${result.retryAfter}s`);
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
return await fetch(url);
|
|
1108
|
-
}
|
|
1109
|
-
```
|
|
1110
|
-
|
|
1111
|
-
---
|
|
1112
|
-
|
|
1113
|
-
## 6. Status Codes para Retry
|
|
1114
|
-
|
|
1115
|
-
### Status Codes para RETRY
|
|
1116
|
-
|
|
1117
|
-
| Status | Descrição | Estratégia |
|
|
1118
|
-
|--------|-----------|------------|
|
|
1119
|
-
| **429** | Too Many Requests | Retry com Retry-After |
|
|
1120
|
-
| **500** | Internal Server Error | Retry com backoff |
|
|
1121
|
-
| **502** | Bad Gateway | Retry com backoff |
|
|
1122
|
-
| **503** | Service Unavailable | Retry com Retry-After |
|
|
1123
|
-
| **504** | Gateway Timeout | Retry com backoff |
|
|
1124
|
-
| **408** | Request Timeout | Retry com backoff |
|
|
1125
|
-
| **409** | Conflict | Retry (pode ser temporário) |
|
|
1126
|
-
| **520-530** | Cloudflare Errors | Retry com backoff |
|
|
1127
|
-
|
|
1128
|
-
### Status Codes NÃO Retry
|
|
1129
|
-
|
|
1130
|
-
| Status | Descrição | Ação |
|
|
1131
|
-
|--------|-----------|------|
|
|
1132
|
-
| **400** | Bad Request | Corrigir request |
|
|
1133
|
-
| **401** | Unauthorized | Autenticar |
|
|
1134
|
-
| **403** | Forbidden | Verificar permissões |
|
|
1135
|
-
| **404** | Not Found | Verificar URL |
|
|
1136
|
-
| **410** | Gone | Não existe mais |
|
|
1137
|
-
| **422** | Unprocessable Entity | Corrigir dados |
|
|
1138
|
-
| **451** | Unavailable For Legal Reasons | Não retry |
|
|
1139
|
-
| **407** | Proxy Authentication Required | Autenticar proxy |
|
|
1140
|
-
| **426** | Upgrade Required | Atualizar protocolo |
|
|
1141
|
-
|
|
1142
|
-
### Implementação de Decisão de Retry
|
|
1143
|
-
|
|
1144
|
-
```javascript
|
|
1145
|
-
function shouldRetry(error) {
|
|
1146
|
-
// Retry em erros de rede
|
|
1147
|
-
if (error.code === 'ECONNRESET' ||
|
|
1148
|
-
error.code === 'ECONNREFUSED' ||
|
|
1149
|
-
error.code === 'ETIMEDOUT' ||
|
|
1150
|
-
error.code === 'ENOTFOUND') {
|
|
1151
|
-
return true;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
// Retry em status codes específicos
|
|
1155
|
-
if (error.status) {
|
|
1156
|
-
const retryableStatuses = [429, 500, 502, 503, 504, 408, 409];
|
|
1157
|
-
if (retryableStatuses.includes(error.status)) {
|
|
1158
|
-
return true;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// Não retry em erros de autenticação
|
|
1163
|
-
if (error.status === 401 || error.status === 403) {
|
|
1164
|
-
return false;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
// Não retry em erros de validação
|
|
1168
|
-
if (error.status === 400 || error.status === 422) {
|
|
1169
|
-
return false;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
return false;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
// Usar com retry
|
|
1176
|
-
import pRetry from 'p-retry';
|
|
1177
|
-
|
|
1178
|
-
const result = await pRetry(async () => {
|
|
1179
|
-
const response = await fetch('https://example.com');
|
|
1180
|
-
|
|
1181
|
-
if (!response.ok) {
|
|
1182
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
1183
|
-
error.status = response.status;
|
|
1184
|
-
throw error;
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
return response.json();
|
|
1188
|
-
}, {
|
|
1189
|
-
retries: 5,
|
|
1190
|
-
onFailedAttempt: async (error) => {
|
|
1191
|
-
if (!shouldRetry(error)) {
|
|
1192
|
-
throw new pRetry.AbortError(error);
|
|
1193
|
-
}
|
|
1194
|
-
},
|
|
1195
|
-
});
|
|
1196
|
-
```
|
|
1197
|
-
|
|
1198
|
-
---
|
|
1199
|
-
|
|
1200
|
-
## 7. Respeitando Retry-After
|
|
1201
|
-
|
|
1202
|
-
### Implementação Básica
|
|
1203
|
-
|
|
1204
|
-
```javascript
|
|
1205
|
-
import pRetry from 'p-retry';
|
|
1206
|
-
|
|
1207
|
-
async function fetchWithRetryAfter(url) {
|
|
1208
|
-
return pRetry(async (attempt) => {
|
|
1209
|
-
const response = await fetch(url);
|
|
1210
|
-
|
|
1211
|
-
if (response.status === 429) {
|
|
1212
|
-
const retryAfter = response.headers.get('Retry-After');
|
|
1213
|
-
|
|
1214
|
-
if (retryAfter) {
|
|
1215
|
-
// Retry-After pode ser:
|
|
1216
|
-
// 1. Número de segundos: "120"
|
|
1217
|
-
// 2. HTTP-date: "Wed, 21 Oct 2015 07:28:00 GMT"
|
|
1218
|
-
let delay;
|
|
1219
|
-
|
|
1220
|
-
if (isNaN(retryAfter)) {
|
|
1221
|
-
// É uma data HTTP
|
|
1222
|
-
const retryDate = new Date(retryAfter);
|
|
1223
|
-
delay = retryDate.getTime() - Date.now();
|
|
1224
|
-
} else {
|
|
1225
|
-
// É um número de segundos
|
|
1226
|
-
delay = parseInt(retryAfter) * 1000;
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
console.log(`Rate limited. Waiting ${delay}ms`);
|
|
1230
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
throw new Error(`Rate limited (429)`);
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
if (!response.ok) {
|
|
1237
|
-
throw new Error(`HTTP ${response.status}`);
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
return response.json();
|
|
1241
|
-
}, {
|
|
1242
|
-
retries: 5,
|
|
1243
|
-
minTimeout: 1000,
|
|
1244
|
-
maxTimeout: 60000,
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
```
|
|
1248
|
-
|
|
1249
|
-
### Implementação Avançada com Crawlee
|
|
1250
|
-
|
|
1251
|
-
```javascript
|
|
1252
|
-
import { PlaywrightCrawler } from 'crawlee';
|
|
1253
|
-
|
|
1254
|
-
class RetryAfterHandler {
|
|
1255
|
-
constructor() {
|
|
1256
|
-
this.retryAfterCache = new Map();
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
async handleRetryAfter(request, response) {
|
|
1260
|
-
const retryAfter = response.headers.get('Retry-After');
|
|
1261
|
-
|
|
1262
|
-
if (retryAfter) {
|
|
1263
|
-
let delay;
|
|
1264
|
-
|
|
1265
|
-
if (isNaN(retryAfter)) {
|
|
1266
|
-
const retryDate = new Date(retryAfter);
|
|
1267
|
-
delay = Math.max(0, retryDate.getTime() - Date.now());
|
|
1268
|
-
} else {
|
|
1269
|
-
delay = parseInt(retryAfter) * 1000;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
// Adicionar margem de segurança (10%)
|
|
1273
|
-
delay = Math.ceil(delay * 1.1);
|
|
1274
|
-
|
|
1275
|
-
this.retryAfterCache.set(request.url, {
|
|
1276
|
-
delay,
|
|
1277
|
-
timestamp: Date.now(),
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
return delay;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
return null;
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
getDelay(url) {
|
|
1287
|
-
const cached = this.retryAfterCache.get(url);
|
|
1288
|
-
if (cached && Date.now() - cached.timestamp < cached.delay) {
|
|
1289
|
-
return cached.delay - (Date.now() - cached.timestamp);
|
|
1290
|
-
}
|
|
1291
|
-
return null;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// Usar com Crawlee
|
|
1296
|
-
const retryHandler = new RetryAfterHandler();
|
|
1297
|
-
|
|
1298
|
-
const crawler = new PlaywrightCrawler({
|
|
1299
|
-
requestHandler: async ({ request, page, response }) => {
|
|
1300
|
-
// Verificar Retry-After
|
|
1301
|
-
const delay = await retryHandler.handleRetryAfter(request, response);
|
|
1302
|
-
|
|
1303
|
-
if (delay) {
|
|
1304
|
-
console.log(`Waiting ${delay}ms for Retry-After`);
|
|
1305
|
-
await page.waitForTimeout(delay);
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
await page.goto(request.url);
|
|
1309
|
-
return { title: await page.title() };
|
|
1310
|
-
},
|
|
1311
|
-
});
|
|
1312
|
-
```
|
|
1313
|
-
|
|
1314
|
-
### Retry-After com Múltiplos Headers
|
|
1315
|
-
|
|
1316
|
-
```javascript
|
|
1317
|
-
async function parseRetryAfter(response) {
|
|
1318
|
-
const retryAfter = response.headers.get('Retry-After');
|
|
1319
|
-
const xRetryAfter = response.headers.get('X-Retry-After');
|
|
1320
|
-
const rateLimitReset = response.headers.get('X-Rate-Limit-Reset');
|
|
1321
|
-
const rateLimitRemaining = response.headers.get('X-Rate-Limit-Remaining');
|
|
1322
|
-
|
|
1323
|
-
// Prioridade: Retry-After > X-Retry-After > X-Rate-Limit-Reset
|
|
1324
|
-
if (retryAfter) {
|
|
1325
|
-
return parseRetryHeaderValue(retryAfter);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
if (xRetryAfter) {
|
|
1329
|
-
return parseRetryHeaderValue(xRetryAfter);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
if (rateLimitReset) {
|
|
1333
|
-
const resetTime = parseInt(rateLimitReset) * 1000;
|
|
1334
|
-
return Math.max(0, resetTime - Date.now());
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
return null;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
function parseRetryHeaderValue(value) {
|
|
1341
|
-
if (isNaN(value)) {
|
|
1342
|
-
// HTTP-date
|
|
1343
|
-
const retryDate = new Date(value);
|
|
1344
|
-
return Math.max(0, retryDate.getTime() - Date.now());
|
|
1345
|
-
}
|
|
1346
|
-
// Seconds
|
|
1347
|
-
return parseInt(value) * 1000;
|
|
1348
|
-
}
|
|
1349
|
-
```
|
|
1350
|
-
|
|
1351
|
-
---
|
|
1352
|
-
|
|
1353
|
-
## 8. Retry com Proxy Rotation
|
|
1354
|
-
|
|
1355
|
-
### Padrão Básico
|
|
1356
|
-
|
|
1357
|
-
```javascript
|
|
1358
|
-
import pRetry from 'p-retry';
|
|
1359
|
-
|
|
1360
|
-
class RetryWithProxyRotation {
|
|
1361
|
-
constructor(proxies) {
|
|
1362
|
-
this.proxies = proxies;
|
|
1363
|
-
this.currentIndex = 0;
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
getNextProxy() {
|
|
1367
|
-
const proxy = this.proxies[this.currentIndex % this.proxies.length];
|
|
1368
|
-
this.currentIndex++;
|
|
1369
|
-
return proxy;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
async fetchWithRetry(url, options = {}) {
|
|
1373
|
-
return pRetry(async (attempt) => {
|
|
1374
|
-
const proxy = this.getNextProxy();
|
|
1375
|
-
console.log(`Attempt ${attempt} using proxy ${proxy}`);
|
|
1376
|
-
|
|
1377
|
-
const controller = new AbortController();
|
|
1378
|
-
const timeoutId = setTimeout(() => controller.abort(), options.timeout || 10000);
|
|
1379
|
-
|
|
1380
|
-
try {
|
|
1381
|
-
const response = await fetch(url, {
|
|
1382
|
-
...options,
|
|
1383
|
-
signal: controller.signal,
|
|
1384
|
-
dispatcher: this.createAgent(proxy),
|
|
1385
|
-
});
|
|
1386
|
-
|
|
1387
|
-
clearTimeout(timeoutId);
|
|
1388
|
-
|
|
1389
|
-
if (!response.ok) {
|
|
1390
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
1391
|
-
error.status = response.status;
|
|
1392
|
-
throw error;
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
return response.json();
|
|
1396
|
-
} catch (error) {
|
|
1397
|
-
clearTimeout(timeoutId);
|
|
1398
|
-
throw error;
|
|
1399
|
-
}
|
|
1400
|
-
}, {
|
|
1401
|
-
retries: options.retries || 3,
|
|
1402
|
-
minTimeout: options.minTimeout || 1000,
|
|
1403
|
-
maxTimeout: options.maxTimeout || 10000,
|
|
1404
|
-
onFailedAttempt: async (error) => {
|
|
1405
|
-
console.log(`Attempt ${error.attemptNumber} failed: ${error.message}`);
|
|
1406
|
-
if (error.retriesLeft === 0) {
|
|
1407
|
-
throw new pRetry.AbortError(error);
|
|
1408
|
-
}
|
|
1409
|
-
},
|
|
1410
|
-
});
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
createAgent(proxy) {
|
|
1414
|
-
if (proxy.startsWith('socks')) {
|
|
1415
|
-
const { SocksProxyAgent } = require('socks-proxy-agent');
|
|
1416
|
-
return new SocksProxyAgent(proxy);
|
|
1417
|
-
}
|
|
1418
|
-
const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
1419
|
-
return new HttpsProxyAgent(proxy);
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
// Uso
|
|
1424
|
-
const fetcher = new RetryWithProxyRotation([
|
|
1425
|
-
'http://proxy1.com:8080',
|
|
1426
|
-
'http://proxy2.com:8080',
|
|
1427
|
-
'http://proxy3.com:8080',
|
|
1428
|
-
]);
|
|
1429
|
-
|
|
1430
|
-
const result = await fetcher.fetchWithRetry('https://example.com/api', {
|
|
1431
|
-
retries: 5,
|
|
1432
|
-
timeout: 10000,
|
|
1433
|
-
});
|
|
1434
|
-
```
|
|
1435
|
-
|
|
1436
|
-
### Padrão com Status Codes
|
|
1437
|
-
|
|
1438
|
-
```javascript
|
|
1439
|
-
import pRetry from 'p-retry';
|
|
1440
|
-
|
|
1441
|
-
class SmartRetryWithProxy {
|
|
1442
|
-
constructor(proxies, options = {}) {
|
|
1443
|
-
this.proxies = proxies;
|
|
1444
|
-
this.currentIndex = 0;
|
|
1445
|
-
this.retryableStatuses = options.retryableStatuses || [429, 500, 502, 503, 504];
|
|
1446
|
-
this.proxyFailureThreshold = options.proxyFailureThreshold || 3;
|
|
1447
|
-
this.proxyFailures = new Map();
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
getNextProxy() {
|
|
1451
|
-
// Filtrar proxies com muitas falhas
|
|
1452
|
-
const availableProxies = this.proxies.filter(proxy => {
|
|
1453
|
-
const failures = this.proxyFailures.get(proxy) || 0;
|
|
1454
|
-
return failures < this.proxyFailureThreshold;
|
|
1455
|
-
});
|
|
1456
|
-
|
|
1457
|
-
if (availableProxies.length === 0) {
|
|
1458
|
-
// Resetar contadores de falha
|
|
1459
|
-
this.proxyFailures.clear();
|
|
1460
|
-
return this.proxies[0];
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
const proxy = availableProxies[this.currentIndex % availableProxies.length];
|
|
1464
|
-
this.currentIndex++;
|
|
1465
|
-
return proxy;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
reportProxyFailure(proxy) {
|
|
1469
|
-
const failures = this.proxyFailures.get(proxy) || 0;
|
|
1470
|
-
this.proxyFailures.set(proxy, failures + 1);
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
async fetchWithRetry(url, options = {}) {
|
|
1474
|
-
return pRetry(async (attempt) => {
|
|
1475
|
-
const proxy = this.getNextProxy();
|
|
1476
|
-
console.log(`Attempt ${attempt} using proxy ${proxy}`);
|
|
1477
|
-
|
|
1478
|
-
try {
|
|
1479
|
-
const response = await fetch(url, {
|
|
1480
|
-
...options,
|
|
1481
|
-
dispatcher: this.createAgent(proxy),
|
|
1482
|
-
});
|
|
1483
|
-
|
|
1484
|
-
if (!response.ok) {
|
|
1485
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
1486
|
-
error.status = response.status;
|
|
1487
|
-
error.proxy = proxy;
|
|
1488
|
-
throw error;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
return response.json();
|
|
1492
|
-
} catch (error) {
|
|
1493
|
-
if (error.status && !this.retryableStatuses.includes(error.status)) {
|
|
1494
|
-
throw new pRetry.AbortError(error);
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
if (error.proxy) {
|
|
1498
|
-
this.reportProxyFailure(error.proxy);
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
throw error;
|
|
1502
|
-
}
|
|
1503
|
-
}, {
|
|
1504
|
-
retries: options.retries || 5,
|
|
1505
|
-
minTimeout: options.minTimeout || 1000,
|
|
1506
|
-
maxTimeout: options.maxTimeout || 30000,
|
|
1507
|
-
randomize: true,
|
|
1508
|
-
onFailedAttempt: async (error) => {
|
|
1509
|
-
console.log(`Attempt ${error.attemptNumber} failed: ${error.message}`);
|
|
1510
|
-
},
|
|
1511
|
-
});
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
createAgent(proxy) {
|
|
1515
|
-
if (proxy.startsWith('socks')) {
|
|
1516
|
-
const { SocksProxyAgent } = require('socks-proxy-agent');
|
|
1517
|
-
return new SocksProxyAgent(proxy);
|
|
1518
|
-
}
|
|
1519
|
-
const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
1520
|
-
return new HttpsProxyAgent(proxy);
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
// Uso
|
|
1525
|
-
const fetcher = new SmartRetryWithProxy([
|
|
1526
|
-
'http://proxy1.com:8080',
|
|
1527
|
-
'http://proxy2.com:8080',
|
|
1528
|
-
'socks5://proxy3.com:1080',
|
|
1529
|
-
], {
|
|
1530
|
-
retryableStatuses: [429, 500, 502, 503, 504],
|
|
1531
|
-
proxyFailureThreshold: 3,
|
|
1532
|
-
});
|
|
1533
|
-
|
|
1534
|
-
const result = await fetcher.fetchWithRetry('https://example.com/api');
|
|
1535
|
-
```
|
|
1536
|
-
|
|
1537
|
-
### Padrão com Crawlee
|
|
1538
|
-
|
|
1539
|
-
```javascript
|
|
1540
|
-
import { PlaywrightCrawler, ProxyConfiguration } from 'crawlee';
|
|
1541
|
-
|
|
1542
|
-
class CrawleeRetryWithProxy {
|
|
1543
|
-
constructor(proxies) {
|
|
1544
|
-
this.proxies = proxies;
|
|
1545
|
-
this.currentIndex = 0;
|
|
1546
|
-
this.proxyFailures = new Map();
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
getNextProxy() {
|
|
1550
|
-
const availableProxies = this.proxies.filter(proxy => {
|
|
1551
|
-
const failures = this.proxyFailures.get(proxy) || 0;
|
|
1552
|
-
return failures < 3;
|
|
1553
|
-
});
|
|
1554
|
-
|
|
1555
|
-
if (availableProxies.length === 0) {
|
|
1556
|
-
this.proxyFailures.clear();
|
|
1557
|
-
return this.proxies[0];
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
const proxy = availableProxies[this.currentIndex % availableProxies.length];
|
|
1561
|
-
this.currentIndex++;
|
|
1562
|
-
return proxy;
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
reportFailure(proxy) {
|
|
1566
|
-
const failures = this.proxyFailures.get(proxy) || 0;
|
|
1567
|
-
this.proxyFailures.set(proxy, failures + 1);
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
getCrawlerOptions() {
|
|
1571
|
-
return {
|
|
1572
|
-
proxyConfiguration: new ProxyConfiguration({
|
|
1573
|
-
newUrlFunction: () => this.getNextProxy(),
|
|
1574
|
-
}),
|
|
1575
|
-
retryOnBlocked: true,
|
|
1576
|
-
maxRequestRetries: 5,
|
|
1577
|
-
requestHandlerTimeoutSecs: 30,
|
|
1578
|
-
requestHandler: async ({ request, page, proxyInfo }) => {
|
|
1579
|
-
try {
|
|
1580
|
-
await page.goto(request.url, { timeout: 30000 });
|
|
1581
|
-
const title = await page.title();
|
|
1582
|
-
return { url: request.url, title };
|
|
1583
|
-
} catch (error) {
|
|
1584
|
-
this.reportFailure(proxyInfo.url);
|
|
1585
|
-
throw error;
|
|
1586
|
-
}
|
|
1587
|
-
},
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
// Uso
|
|
1593
|
-
const retryProxy = new CrawleeRetryWithProxy([
|
|
1594
|
-
'http://proxy1.com:8080',
|
|
1595
|
-
'http://proxy2.com:8080',
|
|
1596
|
-
'socks5://proxy3.com:1080',
|
|
1597
|
-
]);
|
|
1598
|
-
|
|
1599
|
-
const crawler = new PlaywrightCrawler(retryProxy.getCrawlerOptions());
|
|
1600
|
-
await crawler.run(['https://example.com']);
|
|
1601
|
-
```
|
|
1602
|
-
|
|
1603
|
-
---
|
|
1604
|
-
|
|
1605
|
-
## 9. Exemplos Completos
|
|
1606
|
-
|
|
1607
|
-
### 9.1 Exemplo Completo: Retry com Rate Limiting
|
|
1608
|
-
|
|
1609
|
-
```javascript
|
|
1610
|
-
import pRetry from 'p-retry';
|
|
1611
|
-
import { RateLimiterMemory } from 'rate-limiter-flexible';
|
|
1612
|
-
|
|
1613
|
-
class ResilientFetcher {
|
|
1614
|
-
constructor(options = {}) {
|
|
1615
|
-
this.rateLimiter = new RateLimiterMemory({
|
|
1616
|
-
points: options.rateLimit?.points || 10,
|
|
1617
|
-
duration: options.rateLimit?.duration || 1,
|
|
1618
|
-
});
|
|
1619
|
-
|
|
1620
|
-
this.retryOptions = {
|
|
1621
|
-
retries: options.retries || 5,
|
|
1622
|
-
minTimeout: options.minTimeout || 1000,
|
|
1623
|
-
maxTimeout: options.maxTimeout || 30000,
|
|
1624
|
-
randomize: options.randomize ?? true,
|
|
1625
|
-
};
|
|
1626
|
-
|
|
1627
|
-
this.retryableStatuses = options.retryableStatuses || [429, 500, 502, 503, 504];
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
async fetch(url, options = {}) {
|
|
1631
|
-
// Rate limiting
|
|
1632
|
-
const domain = new URL(url).hostname;
|
|
1633
|
-
try {
|
|
1634
|
-
await this.rateLimiter.consume(domain);
|
|
1635
|
-
} catch (rejRes) {
|
|
1636
|
-
const retryAfter = Math.ceil(rejRes.msBeforeNext / 1000);
|
|
1637
|
-
console.log(`Rate limited by ${domain}. Waiting ${retryAfter}s`);
|
|
1638
|
-
await this.sleep(retryAfter * 1000);
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
// Retry logic
|
|
1642
|
-
return pRetry(async (attempt) => {
|
|
1643
|
-
console.log(`Attempt ${attempt} for ${url}`);
|
|
1644
|
-
|
|
1645
|
-
const controller = new AbortController();
|
|
1646
|
-
const timeoutId = setTimeout(() => controller.abort(), options.timeout || 10000);
|
|
1647
|
-
|
|
1648
|
-
try {
|
|
1649
|
-
const response = await fetch(url, {
|
|
1650
|
-
...options,
|
|
1651
|
-
signal: controller.signal,
|
|
1652
|
-
});
|
|
1653
|
-
|
|
1654
|
-
clearTimeout(timeoutId);
|
|
1655
|
-
|
|
1656
|
-
// Tratar Retry-After
|
|
1657
|
-
if (response.status === 429) {
|
|
1658
|
-
const retryAfter = response.headers.get('Retry-After');
|
|
1659
|
-
if (retryAfter) {
|
|
1660
|
-
const delay = this.parseRetryAfter(retryAfter);
|
|
1661
|
-
console.log(`Retry-After: ${delay}ms`);
|
|
1662
|
-
await this.sleep(delay);
|
|
1663
|
-
}
|
|
1664
|
-
throw new Error(`Rate limited (429)`);
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
if (!response.ok) {
|
|
1668
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
1669
|
-
error.status = response.status;
|
|
1670
|
-
throw error;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
return response.json();
|
|
1674
|
-
} catch (error) {
|
|
1675
|
-
clearTimeout(timeoutId);
|
|
1676
|
-
|
|
1677
|
-
// Não retry em erros não retryable
|
|
1678
|
-
if (error.status && !this.retryableStatuses.includes(error.status)) {
|
|
1679
|
-
throw new pRetry.AbortError(error);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
throw error;
|
|
1683
|
-
}
|
|
1684
|
-
}, {
|
|
1685
|
-
...this.retryOptions,
|
|
1686
|
-
onFailedAttempt: async (error) => {
|
|
1687
|
-
console.log(`Attempt ${error.attemptNumber} failed: ${error.message}`);
|
|
1688
|
-
console.log(`Retries left: ${error.retriesLeft}`);
|
|
1689
|
-
},
|
|
1690
|
-
});
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
parseRetryAfter(value) {
|
|
1694
|
-
if (isNaN(value)) {
|
|
1695
|
-
const retryDate = new Date(value);
|
|
1696
|
-
return Math.max(0, retryDate.getTime() - Date.now());
|
|
1697
|
-
}
|
|
1698
|
-
return parseInt(value) * 1000;
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
sleep(ms) {
|
|
1702
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
|
-
// Uso
|
|
1707
|
-
const fetcher = new ResilientFetcher({
|
|
1708
|
-
rateLimit: { points: 10, duration: 1 },
|
|
1709
|
-
retries: 5,
|
|
1710
|
-
minTimeout: 1000,
|
|
1711
|
-
maxTimeout: 30000,
|
|
1712
|
-
retryableStatuses: [429, 500, 502, 503, 504],
|
|
1713
|
-
});
|
|
1714
|
-
|
|
1715
|
-
const result = await fetcher.fetch('https://api.example.com/data');
|
|
1716
|
-
```
|
|
1717
|
-
|
|
1718
|
-
### 9.2 Exemplo Completo: Crawlee com Rate Limiting e Retry
|
|
1719
|
-
|
|
1720
|
-
```javascript
|
|
1721
|
-
import { PlaywrightCrawler, ProxyConfiguration } from 'crawlee';
|
|
1722
|
-
import { RateLimiterMemory } from 'rate-limiter-flexible';
|
|
1723
|
-
|
|
1724
|
-
class ProductionScraper {
|
|
1725
|
-
constructor(options = {}) {
|
|
1726
|
-
this.proxies = options.proxies || [];
|
|
1727
|
-
this.proxyIndex = 0;
|
|
1728
|
-
this.proxyFailures = new Map();
|
|
1729
|
-
this.rateLimiters = new Map();
|
|
1730
|
-
this.defaultRateLimit = options.rateLimit || { points: 60, duration: 60 };
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
getProxy() {
|
|
1734
|
-
const available = this.proxies.filter(p =>
|
|
1735
|
-
(this.proxyFailures.get(p) || 0) < 3
|
|
1736
|
-
);
|
|
1737
|
-
|
|
1738
|
-
if (available.length === 0) {
|
|
1739
|
-
this.proxyFailures.clear();
|
|
1740
|
-
return this.proxies[0];
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
const proxy = available[this.proxyIndex % available.length];
|
|
1744
|
-
this.proxyIndex++;
|
|
1745
|
-
return proxy;
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
reportProxyFailure(proxy) {
|
|
1749
|
-
const failures = this.proxyFailures.get(proxy) || 0;
|
|
1750
|
-
this.proxyFailures.set(proxy, failures + 1);
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
getRateLimiter(domain) {
|
|
1754
|
-
if (!this.rateLimiters.has(domain)) {
|
|
1755
|
-
this.rateLimiters.set(domain, new RateLimiterMemory(this.defaultRateLimit));
|
|
1756
|
-
}
|
|
1757
|
-
return this.rateLimiters.get(domain);
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
async checkRateLimit(domain) {
|
|
1761
|
-
const limiter = this.getRateLimiter(domain);
|
|
1762
|
-
try {
|
|
1763
|
-
await limiter.consume();
|
|
1764
|
-
return { allowed: true };
|
|
1765
|
-
} catch (rejRes) {
|
|
1766
|
-
return {
|
|
1767
|
-
allowed: false,
|
|
1768
|
-
retryAfter: Math.ceil(rejRes.msBeforeNext / 1000),
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
getCrawlerOptions() {
|
|
1774
|
-
return {
|
|
1775
|
-
proxyConfiguration: new ProxyConfiguration({
|
|
1776
|
-
newUrlFunction: () => this.getProxy(),
|
|
1777
|
-
}),
|
|
1778
|
-
retryOnBlocked: true,
|
|
1779
|
-
maxRequestRetries: 5,
|
|
1780
|
-
requestHandlerTimeoutSecs: 30,
|
|
1781
|
-
requestHandler: async ({ request, page, proxyInfo }) => {
|
|
1782
|
-
const domain = new URL(request.url).hostname;
|
|
1783
|
-
|
|
1784
|
-
// Rate limiting
|
|
1785
|
-
const rateCheck = await this.checkRateLimit(domain);
|
|
1786
|
-
if (!rateCheck.allowed) {
|
|
1787
|
-
console.log(`Rate limited. Waiting ${rateCheck.retryAfter}s`);
|
|
1788
|
-
await page.waitForTimeout(rateCheck.retryAfter * 1000);
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
try {
|
|
1792
|
-
await page.goto(request.url, { timeout: 30000 });
|
|
1793
|
-
|
|
1794
|
-
// Extrair dados
|
|
1795
|
-
const title = await page.title();
|
|
1796
|
-
const content = await page.textContent('body');
|
|
1797
|
-
|
|
1798
|
-
return { url: request.url, title, content };
|
|
1799
|
-
} catch (error) {
|
|
1800
|
-
if (proxyInfo?.url) {
|
|
1801
|
-
this.reportProxyFailure(proxyInfo.url);
|
|
1802
|
-
}
|
|
1803
|
-
throw error;
|
|
1804
|
-
}
|
|
1805
|
-
},
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
async scrape(urls) {
|
|
1810
|
-
const crawler = new PlaywrightCrawler(this.getCrawlerOptions());
|
|
1811
|
-
await crawler.run(urls);
|
|
1812
|
-
|
|
1813
|
-
// Estatísticas
|
|
1814
|
-
console.log('Scraping completed');
|
|
1815
|
-
console.log('Proxy failures:', Object.fromEntries(this.proxyFailures));
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
// Uso
|
|
1820
|
-
const scraper = new ProductionScraper({
|
|
1821
|
-
proxies: [
|
|
1822
|
-
'http://proxy1.com:8080',
|
|
1823
|
-
'http://proxy2.com:8080',
|
|
1824
|
-
'socks5://proxy3.com:1080',
|
|
1825
|
-
],
|
|
1826
|
-
rateLimit: { points: 60, duration: 60 },
|
|
1827
|
-
});
|
|
1828
|
-
|
|
1829
|
-
await scraper.scrape([
|
|
1830
|
-
'https://example.com/page1',
|
|
1831
|
-
'https://example.com/page2',
|
|
1832
|
-
'https://example.com/page3',
|
|
1833
|
-
]);
|
|
1834
|
-
```
|
|
1835
|
-
|
|
1836
|
-
### 9.3 Exemplo Completo: Sistema de Retry com Circuit Breaker
|
|
1837
|
-
|
|
1838
|
-
```javascript
|
|
1839
|
-
import pRetry from 'p-retry';
|
|
1840
|
-
|
|
1841
|
-
class CircuitBreaker {
|
|
1842
|
-
constructor(options = {}) {
|
|
1843
|
-
this.failureThreshold = options.failureThreshold || 5;
|
|
1844
|
-
this.resetTimeout = options.resetTimeout || 60000;
|
|
1845
|
-
this.state = 'CLOSED';
|
|
1846
|
-
this.failureCount = 0;
|
|
1847
|
-
this.lastFailureTime = null;
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
async execute(fn) {
|
|
1851
|
-
if (this.state === 'OPEN') {
|
|
1852
|
-
if (Date.now() - this.lastFailureTime >= this.resetTimeout) {
|
|
1853
|
-
this.state = 'HALF_OPEN';
|
|
1854
|
-
} else {
|
|
1855
|
-
throw new Error('Circuit breaker is OPEN');
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
try {
|
|
1860
|
-
const result = await fn();
|
|
1861
|
-
this.onSuccess();
|
|
1862
|
-
return result;
|
|
1863
|
-
} catch (error) {
|
|
1864
|
-
this.onFailure();
|
|
1865
|
-
throw error;
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
onSuccess() {
|
|
1870
|
-
this.failureCount = 0;
|
|
1871
|
-
this.state = 'CLOSED';
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
onFailure() {
|
|
1875
|
-
this.failureCount++;
|
|
1876
|
-
this.lastFailureTime = Date.now();
|
|
1877
|
-
|
|
1878
|
-
if (this.failureCount >= this.failureThreshold) {
|
|
1879
|
-
this.state = 'OPEN';
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
getState() {
|
|
1884
|
-
return {
|
|
1885
|
-
state: this.state,
|
|
1886
|
-
failureCount: this.failureCount,
|
|
1887
|
-
lastFailureTime: this.lastFailureTime,
|
|
1888
|
-
};
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
class ResilientFetcher {
|
|
1893
|
-
constructor(options = {}) {
|
|
1894
|
-
this.circuitBreaker = new CircuitBreaker({
|
|
1895
|
-
failureThreshold: options.failureThreshold || 5,
|
|
1896
|
-
resetTimeout: options.resetTimeout || 60000,
|
|
1897
|
-
});
|
|
1898
|
-
|
|
1899
|
-
this.retryOptions = {
|
|
1900
|
-
retries: options.retries || 3,
|
|
1901
|
-
minTimeout: options.minTimeout || 1000,
|
|
1902
|
-
maxTimeout: options.maxTimeout || 10000,
|
|
1903
|
-
};
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
async fetch(url, options = {}) {
|
|
1907
|
-
return this.circuitBreaker.execute(async () => {
|
|
1908
|
-
return pRetry(async (attempt) => {
|
|
1909
|
-
console.log(`Attempt ${attempt} for ${url}`);
|
|
1910
|
-
|
|
1911
|
-
const response = await fetch(url, options);
|
|
1912
|
-
|
|
1913
|
-
if (!response.ok) {
|
|
1914
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
1915
|
-
error.status = response.status;
|
|
1916
|
-
throw error;
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
return response.json();
|
|
1920
|
-
}, this.retryOptions);
|
|
1921
|
-
});
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
getState() {
|
|
1925
|
-
return this.circuitBreaker.getState();
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// Uso
|
|
1930
|
-
const fetcher = new ResilientFetcher({
|
|
1931
|
-
failureThreshold: 5,
|
|
1932
|
-
resetTimeout: 60000,
|
|
1933
|
-
retries: 3,
|
|
1934
|
-
});
|
|
1935
|
-
|
|
1936
|
-
try {
|
|
1937
|
-
const result = await fetcher.fetch('https://api.example.com/data');
|
|
1938
|
-
console.log('Success:', result);
|
|
1939
|
-
} catch (error) {
|
|
1940
|
-
console.log('Failed:', error.message);
|
|
1941
|
-
console.log('Circuit breaker state:', fetcher.getState());
|
|
1942
|
-
}
|
|
1943
|
-
```
|
|
1944
|
-
|
|
1945
|
-
---
|
|
1946
|
-
|
|
1947
|
-
## Referências
|
|
1948
|
-
|
|
1949
|
-
1. p-retry - https://github.com/sindresorhus/p-retry
|
|
1950
|
-
2. async-retry - https://github.com/vercel/async-retry
|
|
1951
|
-
3. retry - https://github.com/STRML/retry
|
|
1952
|
-
4. exponential-backoff - https://github.com/coveo/exponential-backoff
|
|
1953
|
-
5. bottleneck - https://github.com/SGrondin/bottleneck
|
|
1954
|
-
6. p-throttle - https://github.com/sindresorhus/p-throttle
|
|
1955
|
-
7. p-limit - https://github.com/sindresorhus/p-limit
|
|
1956
|
-
8. p-queue - https://github.com/sindresorhus/p-queue
|
|
1957
|
-
9. rate-limiter-flexible - https://github.com/animir/node-rate-limiter-flexible
|
|
1958
|
-
10. Crawlee Rate Limiting - https://crawlee.dev/docs/guides/avoid-blocking
|