@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,1610 +0,0 @@
|
|
|
1
|
-
# Pesquisa: Caching Strategies para Web Scraping (2026)
|
|
2
|
-
|
|
3
|
-
## Table of Contents
|
|
4
|
-
|
|
5
|
-
- [Visão Geral](#visão-geral)
|
|
6
|
-
- [HTTP Response Caching](#http-response-caching)
|
|
7
|
-
- [In-Memory Caches](#in-memory-caches)
|
|
8
|
-
- [Abstraction Layers](#abstraction-layers)
|
|
9
|
-
- [Disk-Based Caching](#disk-based-caching)
|
|
10
|
-
- [TTL Strategies por Tipo de Conteúdo](#ttl-strategies-por-tipo-de-conteúdo)
|
|
11
|
-
- [Cache Invalidation](#cache-invalidation)
|
|
12
|
-
- [Crawlee Request Deduplication](#crawlee-request-deduplication)
|
|
13
|
-
- [Playwright Response Caching](#playwright-response-caching)
|
|
14
|
-
- [Multi-Layer Architecture](#multi-layer-architecture)
|
|
15
|
-
- [Código Completo](#código-completo)
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Visão Geral
|
|
20
|
-
|
|
21
|
-
Caching em web scraping serve para três propósitos principais:
|
|
22
|
-
|
|
23
|
-
1. **Reduzir requests HTTP** — Evitar baixar o mesmo conteúdo repetidamente
|
|
24
|
-
2. **Aumentar velocidade** — Respostas do cache são instantâneas vs. rede
|
|
25
|
-
3. **Respeitar rate limits** — Menos requests = menos chance de bloqueio
|
|
26
|
-
|
|
27
|
-
### Estratégias de Caching
|
|
28
|
-
|
|
29
|
-
| Nível | Armazenamento | Latência | Persistência | Caso de Uso |
|
|
30
|
-
|---|---|---|---|---|
|
|
31
|
-
| L1 | Memória (RAM) | <1ms | Volátil | Cache quente |
|
|
32
|
-
| L2 | Disco | 1-10ms | Persistente | Cache frio |
|
|
33
|
-
| HTTP | CDN/Proxy | Variável | Variável | Cache de rede |
|
|
34
|
-
| Browser | Playwright Page | N/A | Sessão | Cache de sessão |
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## HTTP Response Caching
|
|
39
|
-
|
|
40
|
-
### ETag / If-None-Match
|
|
41
|
-
|
|
42
|
-
O servidor retorna um `ETag` (hash do conteúdo). Na próxima request, o cliente envia `If-None-Match` com o ETag. Se o conteúdo não mudou, o servidor retorna `304 Not Modified` (sem corpo).
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
import { gotScraping } from 'got-scraping';
|
|
46
|
-
|
|
47
|
-
class EtagCache {
|
|
48
|
-
private store = new Map<string, { etag: string; body: string; timestamp: number }>();
|
|
49
|
-
|
|
50
|
-
async fetch(url: string): Promise<{ body: string; fromCache: boolean }> {
|
|
51
|
-
const cached = this.store.get(url);
|
|
52
|
-
|
|
53
|
-
const headers: Record<string, string> = {};
|
|
54
|
-
if (cached?.etag) {
|
|
55
|
-
headers['If-None-Match'] = cached.etag;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const response = await gotScraping(url, {
|
|
59
|
-
headers,
|
|
60
|
-
throwHttpErrors: false,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (response.statusCode === 304 && cached) {
|
|
64
|
-
console.log(`ETag HIT: ${url}`);
|
|
65
|
-
return { body: cached.body, fromCache: true };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const etag = response.headers['etag'];
|
|
69
|
-
if (etag) {
|
|
70
|
-
this.store.set(url, {
|
|
71
|
-
etag,
|
|
72
|
-
body: response.body,
|
|
73
|
-
timestamp: Date.now(),
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
console.log(`ETag MISS: ${url}`);
|
|
78
|
-
return { body: response.body, fromCache: false };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const cache = new EtagCache();
|
|
83
|
-
const result = await cache.fetch('https://example.com/api/data');
|
|
84
|
-
// Primeira request: MISS, baixa conteúdo, armazena ETag
|
|
85
|
-
// Segunda request: HIT, servidor retorna 304, usa cache local
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Last-Modified / If-Modified-Since
|
|
89
|
-
|
|
90
|
-
Similar ao ETag, mas usa timestamps. O servidor retorna `Last-Modified` e o cliente envia `If-Modified-Since`.
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
import { gotScraping } from 'got-scraping';
|
|
94
|
-
|
|
95
|
-
class ModifiedCache {
|
|
96
|
-
private store = new Map<string, {
|
|
97
|
-
lastModified: string;
|
|
98
|
-
body: string;
|
|
99
|
-
timestamp: number;
|
|
100
|
-
}>();
|
|
101
|
-
|
|
102
|
-
async fetch(url: string): Promise<{ body: string; fromCache: boolean }> {
|
|
103
|
-
const cached = this.store.get(url);
|
|
104
|
-
|
|
105
|
-
const headers: Record<string, string> = {};
|
|
106
|
-
if (cached?.lastModified) {
|
|
107
|
-
headers['If-Modified-Since'] = cached.lastModified;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const response = await gotScraping(url, {
|
|
111
|
-
headers,
|
|
112
|
-
throwHttpErrors: false,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (response.statusCode === 304 && cached) {
|
|
116
|
-
console.log(`Last-Modified HIT: ${url}`);
|
|
117
|
-
return { body: cached.body, fromCache: true };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const lastModified = response.headers['last-modified'];
|
|
121
|
-
if (lastModified) {
|
|
122
|
-
this.store.set(url, {
|
|
123
|
-
lastModified,
|
|
124
|
-
body: response.body,
|
|
125
|
-
timestamp: Date.now(),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
console.log(`Last-Modified MISS: ${url}`);
|
|
130
|
-
return { body: response.body, fromCache: false };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Cache-Control Directives
|
|
136
|
-
|
|
137
|
-
`Cache-Control` define regras de caching do lado do servidor:
|
|
138
|
-
|
|
139
|
-
| Diretiva | Significado |
|
|
140
|
-
|---|---|
|
|
141
|
-
| `max-age=N` | Cache válido por N segundos |
|
|
142
|
-
| `no-cache` | Revalidar antes de usar cache |
|
|
143
|
-
| `no-store` | Não armazenar em cache |
|
|
144
|
-
| `must-revalidate` | Revalidar quando expirar |
|
|
145
|
-
| `public` | Pode ser cacheado por CDNs |
|
|
146
|
-
| `private` | Só o cliente pode cachear |
|
|
147
|
-
| `s-maxage=N` | Max-age para shared caches (CDNs) |
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
import { gotScraping } from 'got-scraping';
|
|
151
|
-
|
|
152
|
-
function parseCacheControl(header: string): Record<string, string | boolean> {
|
|
153
|
-
const directives: Record<string, string | boolean> = {};
|
|
154
|
-
|
|
155
|
-
for (const part of header.split(',')) {
|
|
156
|
-
const [key, value] = part.trim().split('=');
|
|
157
|
-
directives[key.trim()] = value?.trim() ?? true;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return directives;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function getCacheMaxAge(url: string, headers: Record<string, string>): number | null {
|
|
164
|
-
const cacheControl = headers['cache-control'];
|
|
165
|
-
if (!cacheControl) return null;
|
|
166
|
-
|
|
167
|
-
const directives = parseCacheControl(cacheControl);
|
|
168
|
-
|
|
169
|
-
if (directives['no-store'] || directives['no-cache']) {
|
|
170
|
-
return 0; // Não cachear
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const maxAge = directives['max-age'] ?? directives['s-maxage'];
|
|
174
|
-
if (typeof maxAge === 'string') {
|
|
175
|
-
return Number.parseInt(maxAge, 10) * 1000; // Converter para ms
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
class SmartCache {
|
|
182
|
-
private store = new Map<string, { body: string; expiry: number }>();
|
|
183
|
-
|
|
184
|
-
async fetch(url: string): Promise<{ body: string; fromCache: boolean }> {
|
|
185
|
-
const cached = this.store.get(url);
|
|
186
|
-
|
|
187
|
-
// Verificar se cache ainda é válido
|
|
188
|
-
if (cached && cached.expiry > Date.now()) {
|
|
189
|
-
console.log(`Cache HIT: ${url}`);
|
|
190
|
-
return { body: cached.body, fromCache: true };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Buscar novo conteúdo
|
|
194
|
-
const response = await gotScraping(url, { throwHttpErrors: false });
|
|
195
|
-
|
|
196
|
-
if (response.statusCode === 200) {
|
|
197
|
-
const maxAge = getCacheMaxAge(url, response.headers as Record<string, string>);
|
|
198
|
-
|
|
199
|
-
if (maxAge === null || maxAge > 0) {
|
|
200
|
-
this.store.set(url, {
|
|
201
|
-
body: response.body,
|
|
202
|
-
expiry: Date.now() + (maxAge ?? 300_000), // Default 5 min
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
console.log(`Cache MISS: ${url}`);
|
|
208
|
-
return { body: response.body, fromCache: false };
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## In-Memory Caches
|
|
216
|
-
|
|
217
|
-
### lru-cache 11.x
|
|
218
|
-
|
|
219
|
-
`lru-cache` é a biblioteca de cache LRU mais popular no Node.js. Versão 11.x adiciona suporte a `fetchMethod` para cache transparente.
|
|
220
|
-
|
|
221
|
-
**Características:**
|
|
222
|
-
- LRU (Least Recently Used) eviction
|
|
223
|
-
- TTL (Time-To-Live) por item
|
|
224
|
-
- `fetchMethod` para cache transparente
|
|
225
|
-
- Max size com weighted size
|
|
226
|
-
- Stale-while-revalidate pattern
|
|
227
|
-
|
|
228
|
-
```ts
|
|
229
|
-
import { LRUCache } from 'lru-cache';
|
|
230
|
-
|
|
231
|
-
// Cache básico
|
|
232
|
-
const cache = new LRUCache<string, string>({
|
|
233
|
-
max: 500, // Máximo 500 itens
|
|
234
|
-
ttl: 1000 * 60 * 5, // TTL de 5 minutos
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
cache.set('key1', 'value1');
|
|
238
|
-
const value = cache.get('key1');
|
|
239
|
-
// → 'value1'
|
|
240
|
-
|
|
241
|
-
// Cache com fetchMethod (transparente)
|
|
242
|
-
const apiCache = new LRUCache<string, string>({
|
|
243
|
-
max: 100,
|
|
244
|
-
ttl: 1000 * 60 * 10, // 10 minutos
|
|
245
|
-
fetchMethod: async (key) => {
|
|
246
|
-
console.log(`Buscando: ${key}`);
|
|
247
|
-
const response = await fetch(key);
|
|
248
|
-
return response.text();
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Se a chave existe, retorna do cache. Se não, busca e armazena.
|
|
253
|
-
const html = await apiCache.fetch('https://example.com');
|
|
254
|
-
// → Busca da web e armazena
|
|
255
|
-
|
|
256
|
-
const cached = await apiCache.fetch('https://example.com');
|
|
257
|
-
// → Retorna do cache (sem buscar)
|
|
258
|
-
|
|
259
|
-
// Cache com stale-while-revalidate
|
|
260
|
-
const swrCache = new LRUCache<string, string>({
|
|
261
|
-
max: 100,
|
|
262
|
-
ttl: 1000 * 60 * 5, // 5 min para fresh
|
|
263
|
-
allowStale: true, // Permitir stale
|
|
264
|
-
updateAgeOnGet: true, // Renovar TTL ao acessar
|
|
265
|
-
fetchMethod: async (key) => {
|
|
266
|
-
const response = await fetch(key);
|
|
267
|
-
return response.text();
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Retorna stale imediatamente, revalida em background
|
|
272
|
-
const html2 = await swrCache.fetch('https://example.com');
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### node-cache 5.7.7
|
|
276
|
-
|
|
277
|
-
`node-cache` é uma alternativa simples com suporte nativo a stdTTL e eventos.
|
|
278
|
-
|
|
279
|
-
```ts
|
|
280
|
-
import NodeCache from 'node-cache';
|
|
281
|
-
|
|
282
|
-
const cache = new NodeCache({
|
|
283
|
-
stdTTL: 300, // TTL padrão de 5 minutos
|
|
284
|
-
checkperiod: 60, // Verificar expirados a cada 60s
|
|
285
|
-
useClones: false, // Não clonar valores (performance)
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Set com TTL customizado
|
|
289
|
-
cache.set('key1', 'value1', 600); // 10 minutos
|
|
290
|
-
cache.set('key2', 'value2'); // TTL padrão (5 min)
|
|
291
|
-
|
|
292
|
-
// Get
|
|
293
|
-
const value = cache.get<string>('key1');
|
|
294
|
-
|
|
295
|
-
// Eventos
|
|
296
|
-
cache.on('expired', (key, value) => {
|
|
297
|
-
console.log(`Chave expirada: ${key}`);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
cache.on('del', (key, value) => {
|
|
301
|
-
console.log(`Chave deletada: ${key}`);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// Stats
|
|
305
|
-
const stats = cache.getStats();
|
|
306
|
-
console.log(`Hits: ${stats.hits}, Misses: ${stats.misses}`);
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### quick-lru 7.x
|
|
310
|
-
|
|
311
|
-
`quick-lru` é uma implementação LRU ultraleve e rápida. Sem TTL, sem features extras — apenas LRU puro.
|
|
312
|
-
|
|
313
|
-
```ts
|
|
314
|
-
import QuickLRU from 'quick-lru';
|
|
315
|
-
|
|
316
|
-
const cache = new QuickLRU<string, string>({
|
|
317
|
-
maxSize: 1000,
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
cache.set('key1', 'value1');
|
|
321
|
-
cache.get('key1');
|
|
322
|
-
// → 'value1'
|
|
323
|
-
|
|
324
|
-
// Iterador
|
|
325
|
-
for (const [key, value] of cache) {
|
|
326
|
-
console.log(`${key}: ${value}`);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Resizing
|
|
330
|
-
cache.resize(2000);
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### Comparação de In-Memory Caches
|
|
334
|
-
|
|
335
|
-
| Feature | lru-cache 11.x | node-cache 5.7 | quick-lru 7.x |
|
|
336
|
-
|---|---|---|---|
|
|
337
|
-
| LRU | ✅ | ❌ (FIFO) | ✅ |
|
|
338
|
-
| TTL | ✅ | ✅ | ❌ |
|
|
339
|
-
| fetchMethod | ✅ | ❌ | ❌ |
|
|
340
|
-
| Eventos | ❌ | ✅ | ❌ |
|
|
341
|
-
| Tamanho | ~20KB | ~15KB | ~3KB |
|
|
342
|
-
| Performance | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
|
|
343
|
-
| Use case | Geral | Simples | Performance |
|
|
344
|
-
|
|
345
|
-
---
|
|
346
|
-
|
|
347
|
-
## Abstraction Layers
|
|
348
|
-
|
|
349
|
-
### cache-manager 7.2.8
|
|
350
|
-
|
|
351
|
-
`cache-manager` abstrai o storage e permite trocar backends (memória, Redis, S3, etc.) sem mudar código.
|
|
352
|
-
|
|
353
|
-
**Características:**
|
|
354
|
-
- Multi-store (L1 + L2)
|
|
355
|
-
- Store switching (memória → Redis)
|
|
356
|
-
- Wrap pattern (cache transparente)
|
|
357
|
-
- Eventos
|
|
358
|
-
|
|
359
|
-
```ts
|
|
360
|
-
import { caching } from 'cache-manager';
|
|
361
|
-
|
|
362
|
-
// Cache de memória
|
|
363
|
-
const memoryCache = await caching('memory', {
|
|
364
|
-
max: 1000,
|
|
365
|
-
ttl: 300, // 5 minutos
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// Usar
|
|
369
|
-
await memoryCache.set('key1', 'value1', 600); // TTL de 10 min
|
|
370
|
-
const value = await memoryCache.get<string>('key1');
|
|
371
|
-
|
|
372
|
-
// Wrap — cache transparente
|
|
373
|
-
const data = await memoryCache.wrap('expensive-query', async () => {
|
|
374
|
-
// Esta função só executa se não houver cache
|
|
375
|
-
const result = await fetchExpensiveData();
|
|
376
|
-
return result;
|
|
377
|
-
}, { ttl: 600 });
|
|
378
|
-
|
|
379
|
-
// Store de Redis (se disponível)
|
|
380
|
-
// import { redisStore } from 'cache-manager-redis-yet';
|
|
381
|
-
// const redisCache = await caching(redisStore, {
|
|
382
|
-
// host: 'localhost',
|
|
383
|
-
// port: 6379,
|
|
384
|
-
// ttl: 300,
|
|
385
|
-
// });
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### keyv 4.x
|
|
389
|
-
|
|
390
|
-
`keyv` é uma abstração simples com suporte a múltiplos backends via plugins.
|
|
391
|
-
|
|
392
|
-
```ts
|
|
393
|
-
import Keyv from 'keyv';
|
|
394
|
-
|
|
395
|
-
// Cache de memória
|
|
396
|
-
const cache = new Keyv({ ttl: 300_000 }); // 5 min
|
|
397
|
-
|
|
398
|
-
// Usar
|
|
399
|
-
await cache.set('key1', 'value1');
|
|
400
|
-
const value = await cache.get('key1');
|
|
401
|
-
|
|
402
|
-
// Com Redis (se disponível)
|
|
403
|
-
// import KeyvRedis from '@keyv/redis';
|
|
404
|
-
// const cache = new Keyv(new KeyvRedis('redis://localhost:6379'));
|
|
405
|
-
|
|
406
|
-
// Namespace
|
|
407
|
-
const userCache = new Keyv({ namespace: 'users' });
|
|
408
|
-
const productCache = new Keyv({ namespace: 'products' });
|
|
409
|
-
|
|
410
|
-
await userCache.set('user:123', { name: 'João' });
|
|
411
|
-
await productCache.set('product:456', { name: 'Produto' });
|
|
412
|
-
|
|
413
|
-
// Iteração
|
|
414
|
-
for await (const [key, value] of cache.iterator()) {
|
|
415
|
-
console.log(`${key}: ${value}`);
|
|
416
|
-
}
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
---
|
|
420
|
-
|
|
421
|
-
## Disk-Based Caching
|
|
422
|
-
|
|
423
|
-
### cacache 21.0.0
|
|
424
|
-
|
|
425
|
-
`cacache` é um cache content-addressable (baseado em hash do conteúdo). Usado pelo npm internamente.
|
|
426
|
-
|
|
427
|
-
**Características:**
|
|
428
|
-
- Content-addressable (hash SHA-512 do conteúdo)
|
|
429
|
-
- Integrity verification automática
|
|
430
|
-
- Suporte a metadados
|
|
431
|
-
- Compactação
|
|
432
|
-
- Não expira por padrão (usar cleanup manual)
|
|
433
|
-
|
|
434
|
-
```ts
|
|
435
|
-
import cacache from 'cacache';
|
|
436
|
-
import { tmpdir } from 'os';
|
|
437
|
-
import { join } from 'path';
|
|
438
|
-
|
|
439
|
-
const CACHE_DIR = join(tmpdir(), 'my-scraper-cache');
|
|
440
|
-
|
|
441
|
-
// Armazenar
|
|
442
|
-
const data = { url: 'https://example.com', content: '<html>...</html>' };
|
|
443
|
-
await cacache.put(CACHE_DIR, 'https://example.com', JSON.stringify(data));
|
|
444
|
-
|
|
445
|
-
// Recuperar
|
|
446
|
-
const cached = await cacache.get(CACHE_DIR, 'https://example.com');
|
|
447
|
-
if (cached) {
|
|
448
|
-
const parsed = JSON.parse(cached.data.toString());
|
|
449
|
-
console.log('Cache HIT:', parsed.url);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Verificar se existe
|
|
453
|
-
const hasKey = await cacache.get(CACHE_DIR, 'https://example.com');
|
|
454
|
-
console.log('Existe:', !!hasKey);
|
|
455
|
-
|
|
456
|
-
// Deletar
|
|
457
|
-
await cacache.rm(CACHE_DIR, 'https://example.com');
|
|
458
|
-
|
|
459
|
-
// Listar todas as chaves
|
|
460
|
-
const entries = await cacache.ls(CACHE_DIR);
|
|
461
|
-
for (const [key, entry] of Object.entries(entries)) {
|
|
462
|
-
console.log(`${key}: ${entry.integrity}`);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Cleanup de entradas antigas
|
|
466
|
-
const { entries: allEntries } = await cacache.get(CACHE_DIR);
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
### flat-cache 6.1.22
|
|
470
|
-
|
|
471
|
-
`flat-cache` armazena tudo em um único arquivo JSON. Simples e rápido para caches pequenos.
|
|
472
|
-
|
|
473
|
-
```ts
|
|
474
|
-
import flatCache from 'flat-cache';
|
|
475
|
-
import { join } from 'path';
|
|
476
|
-
|
|
477
|
-
const cacheDir = join(process.cwd(), '.cache');
|
|
478
|
-
const cache = flatCache.load('scraper-cache', cacheDir);
|
|
479
|
-
|
|
480
|
-
// Set
|
|
481
|
-
cache.set('https://example.com', {
|
|
482
|
-
content: '<html>...</html>',
|
|
483
|
-
timestamp: Date.now(),
|
|
484
|
-
});
|
|
485
|
-
cache.save(true); // true = pretty print
|
|
486
|
-
|
|
487
|
-
// Get
|
|
488
|
-
const cached = cache.getKey('https://example.com');
|
|
489
|
-
console.log(cached);
|
|
490
|
-
|
|
491
|
-
// Deletar
|
|
492
|
-
cache.removeKey('https://example.com');
|
|
493
|
-
|
|
494
|
-
// Listar
|
|
495
|
-
const keys = cache.allKeys();
|
|
496
|
-
console.log(keys);
|
|
497
|
-
|
|
498
|
-
// Cleanup de itens antigos
|
|
499
|
-
const allEntries = cache.all();
|
|
500
|
-
const oneHourAgo = Date.now() - 3600_000;
|
|
501
|
-
|
|
502
|
-
for (const [key, entry] of Object.entries(allEntries)) {
|
|
503
|
-
if ((entry as any).timestamp < oneHourAgo) {
|
|
504
|
-
cache.removeKey(key);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
cache.save();
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
### Comparação de Disk-Based Caches
|
|
511
|
-
|
|
512
|
-
| Feature | cacache 21.0 | flat-cache 6.1 |
|
|
513
|
-
|---|---|---|
|
|
514
|
-
| Armazenamento | Content-addressable | JSON file |
|
|
515
|
-
| Integridade | SHA-512 automática | Nenhuma |
|
|
516
|
-
| Performance | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
|
517
|
-
| Tamanho | Grande (hashes) | Pequeno |
|
|
518
|
-
| Use case | Cache robusto | Cache simples |
|
|
519
|
-
| Cleanup | Manual | Manual |
|
|
520
|
-
|
|
521
|
-
---
|
|
522
|
-
|
|
523
|
-
## TTL Strategies por Tipo de Conteúdo
|
|
524
|
-
|
|
525
|
-
### Estimativas de TTL
|
|
526
|
-
|
|
527
|
-
| Tipo de Conteúdo | TTL | Exemplo |
|
|
528
|
-
|---|---|---|
|
|
529
|
-
| **Estático** | 24-72 horas | Páginas de termos, sobre, FAQ |
|
|
530
|
-
| **Semi-estático** | 4-24 horas | Blog posts, documentação |
|
|
531
|
-
| **Dinâmico** | 30 min - 4 horas | Páginas de produto, preços |
|
|
532
|
-
| **Tempo real** | 30 segundos - 5 minutos | Busca, feed, cotações |
|
|
533
|
-
| **Transacional** | 0 (não cachear) | Checkout, login, pagamento |
|
|
534
|
-
|
|
535
|
-
### Implementação por Tipo
|
|
536
|
-
|
|
537
|
-
```ts
|
|
538
|
-
import { LRUCache } from 'lru-cache';
|
|
539
|
-
|
|
540
|
-
enum ContentType {
|
|
541
|
-
STATIC = 'static',
|
|
542
|
-
SEMI_STATIC = 'semi-static',
|
|
543
|
-
DYNAMIC = 'dynamic',
|
|
544
|
-
REALTIME = 'realtime',
|
|
545
|
-
TRANSACTIONAL = 'transactional',
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const TTL_CONFIG: Record<ContentType, { ttl: number; staleWhileRevalidate?: number }> = {
|
|
549
|
-
[ContentType.STATIC]: {
|
|
550
|
-
ttl: 1000 * 60 * 60 * 24, // 24 horas
|
|
551
|
-
staleWhileRevalidate: 1000 * 60 * 60 * 48, // 48h stale
|
|
552
|
-
},
|
|
553
|
-
[ContentType.SEMI_STATIC]: {
|
|
554
|
-
ttl: 1000 * 60 * 60 * 4, // 4 horas
|
|
555
|
-
staleWhileRevalidate: 1000 * 60 * 60 * 12, // 12h stale
|
|
556
|
-
},
|
|
557
|
-
[ContentType.DYNAMIC]: {
|
|
558
|
-
ttl: 1000 * 60 * 30, // 30 minutos
|
|
559
|
-
staleWhileRevalidate: 1000 * 60 * 60, // 1h stale
|
|
560
|
-
},
|
|
561
|
-
[ContentType.REALTIME]: {
|
|
562
|
-
ttl: 1000 * 30, // 30 segundos
|
|
563
|
-
staleWhileRevalidate: 1000 * 60, // 1 minuto stale
|
|
564
|
-
},
|
|
565
|
-
[ContentType.TRANSACTIONAL]: {
|
|
566
|
-
ttl: 0, // Não cachear
|
|
567
|
-
},
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
function classifyContentType(url: string): ContentType {
|
|
571
|
-
const parsed = new URL(url);
|
|
572
|
-
const path = parsed.pathname.toLowerCase();
|
|
573
|
-
|
|
574
|
-
if (path.match(/\.(css|js|png|jpg|gif|svg|ico|woff2?)$/)) {
|
|
575
|
-
return ContentType.STATIC;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
if (path.startsWith('/terms') || path.startsWith('/privacy') || path.startsWith('/about')) {
|
|
579
|
-
return ContentType.STATIC;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (path.startsWith('/blog/') || path.startsWith('/docs/')) {
|
|
583
|
-
return ContentType.SEMI_STATIC;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (path.startsWith('/products/') || path.startsWith('/categories/')) {
|
|
587
|
-
return ContentType.DYNAMIC;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
if (path.startsWith('/search') || path.startsWith('/api/search')) {
|
|
591
|
-
return ContentType.REALTIME;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (path.startsWith('/checkout') || path.startsWith('/cart')) {
|
|
595
|
-
return ContentType.TRANSACTIONAL;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
return ContentType.DYNAMIC;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// Cache com TTL dinâmico
|
|
602
|
-
class DynamicTTLCache {
|
|
603
|
-
private cache = new LRUCache<string, { body: string; type: ContentType }>({
|
|
604
|
-
max: 1000,
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
async fetch(url: string, fetcher: () => Promise<string>): Promise<string> {
|
|
608
|
-
const type = classifyContentType(url);
|
|
609
|
-
const config = TTL_CONFIG[type];
|
|
610
|
-
|
|
611
|
-
if (config.ttl === 0) {
|
|
612
|
-
// Não cachear
|
|
613
|
-
return fetcher();
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
const cached = this.cache.get(url);
|
|
617
|
-
if (cached && cached.type === type) {
|
|
618
|
-
return cached.body;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const body = await fetcher();
|
|
622
|
-
this.cache.set(url, { body, type }, { ttl: config.ttl });
|
|
623
|
-
|
|
624
|
-
return body;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
### Detecção de Tipo via Headers
|
|
630
|
-
|
|
631
|
-
```ts
|
|
632
|
-
function detectContentType(headers: Record<string, string>): ContentType {
|
|
633
|
-
const contentType = headers['content-type'] ?? '';
|
|
634
|
-
const cacheControl = headers['cache-control'] ?? '';
|
|
635
|
-
|
|
636
|
-
// Se servidor diz "no-store", não cachear
|
|
637
|
-
if (cacheControl.includes('no-store')) {
|
|
638
|
-
return ContentType.TRANSACTIONAL;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Se servidor diz "max-age", usar esse valor
|
|
642
|
-
const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
|
|
643
|
-
if (maxAgeMatch) {
|
|
644
|
-
const maxAge = Number.parseInt(maxAgeMatch[1], 10);
|
|
645
|
-
if (maxAge <= 60) return ContentType.REALTIME;
|
|
646
|
-
if (maxAge <= 3600) return ContentType.DYNAMIC;
|
|
647
|
-
if (maxAge <= 86400) return ContentType.SEMI_STATIC;
|
|
648
|
-
return ContentType.STATIC;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// Classificar por content-type
|
|
652
|
-
if (contentType.includes('json')) return ContentType.DYNAMIC;
|
|
653
|
-
if (contentType.includes('html')) return ContentType.SEMI_STATIC;
|
|
654
|
-
if (contentType.includes('image') || contentType.includes('css') || contentType.includes('js')) {
|
|
655
|
-
return ContentType.STATIC;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
return ContentType.DYNAMIC;
|
|
659
|
-
}
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
---
|
|
663
|
-
|
|
664
|
-
## Cache Invalidation
|
|
665
|
-
|
|
666
|
-
### TTL-Based
|
|
667
|
-
|
|
668
|
-
O formato mais simples: cada item tem um TTL e é automaticamente removido quando expira.
|
|
669
|
-
|
|
670
|
-
```ts
|
|
671
|
-
import { LRUCache } from 'lru-cache';
|
|
672
|
-
|
|
673
|
-
const cache = new LRUCache<string, string>({
|
|
674
|
-
max: 1000,
|
|
675
|
-
ttl: 1000 * 60 * 5, // 5 minutos
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
// Item expira automaticamente após 5 minutos
|
|
679
|
-
cache.set('key', 'value');
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
### Explicit Invalidation
|
|
683
|
-
|
|
684
|
-
Delete manualmente quando o conteúdo muda.
|
|
685
|
-
|
|
686
|
-
```ts
|
|
687
|
-
class ExplicitCache {
|
|
688
|
-
private cache = new Map<string, { body: string; version: number }>();
|
|
689
|
-
|
|
690
|
-
set(url: string, body: string): void {
|
|
691
|
-
const existing = this.cache.get(url);
|
|
692
|
-
this.cache.set(url, {
|
|
693
|
-
body,
|
|
694
|
-
version: (existing?.version ?? 0) + 1,
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
get(url: string): string | null {
|
|
699
|
-
return this.cache.get(url)?.body ?? null;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
invalidate(url: string): void {
|
|
703
|
-
this.cache.delete(url);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
invalidatePattern(pattern: RegExp): number {
|
|
707
|
-
let count = 0;
|
|
708
|
-
for (const key of this.cache.keys()) {
|
|
709
|
-
if (pattern.test(key)) {
|
|
710
|
-
this.cache.delete(key);
|
|
711
|
-
count++;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
return count;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
invalidateAll(): void {
|
|
718
|
-
this.cache.clear();
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
const cache = new ExplicitCache();
|
|
723
|
-
cache.set('https://example.com/page1', 'content1');
|
|
724
|
-
|
|
725
|
-
// Invalidar uma URL
|
|
726
|
-
cache.invalidate('https://example.com/page1');
|
|
727
|
-
|
|
728
|
-
// Invalidar todas as URLs de um domínio
|
|
729
|
-
cache.invalidatePattern(/^https:\/\/example\.com\//);
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
### Versioned Keys
|
|
733
|
-
|
|
734
|
-
Cada versão do conteúdo tem uma chave diferente. Cache antigo continua válido até expirar.
|
|
735
|
-
|
|
736
|
-
```ts
|
|
737
|
-
class VersionedCache {
|
|
738
|
-
private cache = new Map<string, { body: string; version: number }>();
|
|
739
|
-
private versions = new Map<string, number>();
|
|
740
|
-
|
|
741
|
-
getCurrentVersion(url: string): number {
|
|
742
|
-
return this.versions.get(url) ?? 0;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
set(url: string, body: string): void {
|
|
746
|
-
const version = this.getCurrentVersion(url) + 1;
|
|
747
|
-
this.versions.set(url, version);
|
|
748
|
-
this.cache.set(`${url}#v${version}`, { body, version });
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
get(url: string): string | null {
|
|
752
|
-
const version = this.getCurrentVersion(url);
|
|
753
|
-
if (version === 0) return null;
|
|
754
|
-
|
|
755
|
-
const cached = this.cache.get(`${url}#v${version}`);
|
|
756
|
-
return cached?.body ?? null;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// Versão específica
|
|
760
|
-
getAtVersion(url: string, version: number): string | null {
|
|
761
|
-
const cached = this.cache.get(`${url}#v${version}`);
|
|
762
|
-
return cached?.body ?? null;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// Limpar versões antigas (manter últimas N)
|
|
766
|
-
cleanup(url: string, keepLast: number = 3): void {
|
|
767
|
-
const currentVersion = this.getCurrentVersion(url);
|
|
768
|
-
const versionsToDelete = currentVersion - keepLast;
|
|
769
|
-
|
|
770
|
-
for (let v = 1; v <= versionsToDelete; v++) {
|
|
771
|
-
this.cache.delete(`${url}#v${v}`);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const cache = new VersionedCache();
|
|
777
|
-
cache.set('https://example.com', 'v1');
|
|
778
|
-
cache.set('https://example.com', 'v2');
|
|
779
|
-
cache.set('https://example.com', 'v3');
|
|
780
|
-
|
|
781
|
-
console.log(cache.get('https://example.com')); // v3
|
|
782
|
-
console.log(cache.getAtVersion('https://example.com', 1)); // v1
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
### Pattern-Based Invalidation
|
|
786
|
-
|
|
787
|
-
Invalidar múltiplas chaves que correspondem a um padrão.
|
|
788
|
-
|
|
789
|
-
```ts
|
|
790
|
-
class PatternCache {
|
|
791
|
-
private cache = new Map<string, string>();
|
|
792
|
-
|
|
793
|
-
set(key: string, value: string): void {
|
|
794
|
-
this.cache.set(key, value);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
get(key: string): string | null {
|
|
798
|
-
return this.cache.get(key) ?? null;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
invalidate(pattern: string | RegExp): string[] {
|
|
802
|
-
const deleted: string[] = [];
|
|
803
|
-
const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
|
|
804
|
-
|
|
805
|
-
for (const key of this.cache.keys()) {
|
|
806
|
-
if (regex.test(key)) {
|
|
807
|
-
this.cache.delete(key);
|
|
808
|
-
deleted.push(key);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
return deleted;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Invalidar por domínio
|
|
816
|
-
invalidateDomain(domain: string): string[] {
|
|
817
|
-
return this.invalidate(new RegExp(`^https?://${domain.replace('.', '\\.')}/`));
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// Invalidar por path
|
|
821
|
-
invalidatePath(path: string): string[] {
|
|
822
|
-
return this.invalidate(new RegExp(`${path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
const cache = new PatternCache();
|
|
827
|
-
cache.set('https://example.com/page1', 'c1');
|
|
828
|
-
cache.set('https://example.com/page2', 'c2');
|
|
829
|
-
cache.set('https://other.com/page1', 'c3');
|
|
830
|
-
|
|
831
|
-
const deleted = cache.invalidateDomain('example.com');
|
|
832
|
-
// → ['https://example.com/page1', 'https://example.com/page2']
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
### Event-Driven Invalidation
|
|
836
|
-
|
|
837
|
-
Invalidar cache quando um evento externo ocorre.
|
|
838
|
-
|
|
839
|
-
```ts
|
|
840
|
-
import { EventEmitter } from 'events';
|
|
841
|
-
|
|
842
|
-
class EventDrivenCache extends EventEmitter {
|
|
843
|
-
private cache = new Map<string, { body: string; tags: string[] }>();
|
|
844
|
-
|
|
845
|
-
set(url: string, body: string, tags: string[] = []): void {
|
|
846
|
-
this.cache.set(url, { body, tags });
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
get(url: string): string | null {
|
|
850
|
-
return this.cache.get(url)?.body ?? null;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
// Invalidar por tag
|
|
854
|
-
invalidateTag(tag: string): string[] {
|
|
855
|
-
const deleted: string[] = [];
|
|
856
|
-
|
|
857
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
858
|
-
if (entry.tags.includes(tag)) {
|
|
859
|
-
this.cache.delete(key);
|
|
860
|
-
deleted.push(key);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
this.emit('invalidated', { tag, count: deleted.length });
|
|
865
|
-
return deleted;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// Invalidar quando conteúdo muda
|
|
869
|
-
onContentChange(url: string, tags: string[]): void {
|
|
870
|
-
// Invalidar todas as entradas com tags relacionadas
|
|
871
|
-
for (const tag of tags) {
|
|
872
|
-
this.invalidateTag(tag);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const cache = new EventDrivenCache();
|
|
878
|
-
cache.on('invalidated', ({ tag, count }) => {
|
|
879
|
-
console.log(`Tag "${tag}" invalidou ${count} entradas`);
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
cache.set('https://example.com/products', 'products-list', ['products', 'homepage']);
|
|
883
|
-
cache.set('https://example.com/product/1', 'product-1', ['products', 'product-1']);
|
|
884
|
-
cache.set('https://example.com/blog', 'blog-list', ['blog', 'homepage']);
|
|
885
|
-
|
|
886
|
-
cache.invalidateTag('products');
|
|
887
|
-
// → Invalidou 2 entradas
|
|
888
|
-
```
|
|
889
|
-
|
|
890
|
-
---
|
|
891
|
-
|
|
892
|
-
## Crawlee Request Deduplication
|
|
893
|
-
|
|
894
|
-
### uniqueKey
|
|
895
|
-
|
|
896
|
-
Crawlee usa `uniqueKey` para deduplicação automática de requests. Cada request tem uma chave única derivada da URL e opções.
|
|
897
|
-
|
|
898
|
-
```ts
|
|
899
|
-
import { PlaywrightCrawler } from 'crawlee';
|
|
900
|
-
|
|
901
|
-
const crawler = new PlaywrightCrawler({
|
|
902
|
-
requestHandlerTimeoutSecs: 30,
|
|
903
|
-
async requestHandler({ page, enqueueLinks, request }) {
|
|
904
|
-
// Crawlee automaticamente ignora URLs duplicadas
|
|
905
|
-
// baseado no uniqueKey
|
|
906
|
-
|
|
907
|
-
const links = await page.$$eval('a[href]', anchors =>
|
|
908
|
-
anchors.map(a => a.href)
|
|
909
|
-
);
|
|
910
|
-
|
|
911
|
-
// enqueueLinks deduplica automaticamente
|
|
912
|
-
await enqueueLinks({ urls: links });
|
|
913
|
-
},
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
// Crawlee mantém um set de uniqueKeys em memória e disco
|
|
917
|
-
// URLs com o mesmo uniqueKey são ignoradas
|
|
918
|
-
```
|
|
919
|
-
|
|
920
|
-
### customKeyFunction
|
|
921
|
-
|
|
922
|
-
Para customizar a deduplicação (ex: ignorar query params):
|
|
923
|
-
|
|
924
|
-
```ts
|
|
925
|
-
import { PlaywrightCrawler, Request } from 'crawlee';
|
|
926
|
-
|
|
927
|
-
const crawler = new PlaywrightCrawler({
|
|
928
|
-
requestHandlerTimeoutSecs: 30,
|
|
929
|
-
async requestHandler({ page, enqueueLinks }) {
|
|
930
|
-
const links = await page.$$eval('a[href]', anchors =>
|
|
931
|
-
anchors.map(a => a.href)
|
|
932
|
-
);
|
|
933
|
-
|
|
934
|
-
// Criar requests com chave customizada
|
|
935
|
-
for (const url of links) {
|
|
936
|
-
const request = new Request({
|
|
937
|
-
url,
|
|
938
|
-
// Crawlee usa URL como uniqueKey por padrão
|
|
939
|
-
// Para customizar, usar手動 construction
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Ou usar glob patterns no enqueueLinks
|
|
944
|
-
await enqueueLinks({
|
|
945
|
-
globs: ['https://example.com/**'],
|
|
946
|
-
exclude: ['**/page=*'], // Excluir paginação
|
|
947
|
-
});
|
|
948
|
-
},
|
|
949
|
-
});
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
### Request Queue Persistence
|
|
953
|
-
|
|
954
|
-
O `RequestQueue` persiste em disco (SQLite via `@crawlee/storage`), sobrevivendo a crashes:
|
|
955
|
-
|
|
956
|
-
```ts
|
|
957
|
-
import { RequestQueue, PlaywrightCrawler } from 'crawlee';
|
|
958
|
-
|
|
959
|
-
// Abrir fila persistente
|
|
960
|
-
const queue = await RequestQueue.open('my-crawl-queue');
|
|
961
|
-
|
|
962
|
-
// Adicionar request
|
|
963
|
-
await queue.addRequest({
|
|
964
|
-
url: 'https://example.com',
|
|
965
|
-
userData: { depth: 0 },
|
|
966
|
-
});
|
|
967
|
-
|
|
968
|
-
// Abrir crawler com fila existente
|
|
969
|
-
const crawler = new PlaywrightCrawler({
|
|
970
|
-
requestQueue: queue,
|
|
971
|
-
async requestHandler({ page, request }) {
|
|
972
|
-
// Processar...
|
|
973
|
-
},
|
|
974
|
-
});
|
|
975
|
-
|
|
976
|
-
// Mesmo após crash, a fila continua de onde parou
|
|
977
|
-
await crawler.run(); // Retoma automaticamente
|
|
978
|
-
```
|
|
979
|
-
|
|
980
|
-
---
|
|
981
|
-
|
|
982
|
-
## Playwright Response Caching
|
|
983
|
-
|
|
984
|
-
### page.route() Interception
|
|
985
|
-
|
|
986
|
-
Interceptar responses no Playwright para cache de sessão:
|
|
987
|
-
|
|
988
|
-
```ts
|
|
989
|
-
import { chromium } from 'playwright';
|
|
990
|
-
|
|
991
|
-
class PlaywrightCache {
|
|
992
|
-
private cache = new Map<string, { body: string; headers: Record<string, string> }>();
|
|
993
|
-
|
|
994
|
-
async setupCache(page: any): Promise<void> {
|
|
995
|
-
await page.route('**/*', async (route: any) => {
|
|
996
|
-
const url = route.request().url();
|
|
997
|
-
const cached = this.cache.get(url);
|
|
998
|
-
|
|
999
|
-
if (cached) {
|
|
1000
|
-
console.log(`Playwright cache HIT: ${url}`);
|
|
1001
|
-
await route.fulfill({
|
|
1002
|
-
status: 200,
|
|
1003
|
-
contentType: 'text/html',
|
|
1004
|
-
body: cached.body,
|
|
1005
|
-
});
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
// Continuar request normalmente
|
|
1010
|
-
const response = await route.fetch();
|
|
1011
|
-
const body = await response.text();
|
|
1012
|
-
|
|
1013
|
-
// Armazenar em cache
|
|
1014
|
-
this.cache.set(url, {
|
|
1015
|
-
body,
|
|
1016
|
-
headers: response.headers(),
|
|
1017
|
-
});
|
|
1018
|
-
|
|
1019
|
-
await route.fulfill({
|
|
1020
|
-
status: response.status(),
|
|
1021
|
-
headers: response.headers(),
|
|
1022
|
-
body,
|
|
1023
|
-
});
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// Usar
|
|
1029
|
-
const browser = await chromium.launch();
|
|
1030
|
-
const context = await browser.newContext();
|
|
1031
|
-
const page = await context.newPage();
|
|
1032
|
-
|
|
1033
|
-
const cache = new PlaywrightCache();
|
|
1034
|
-
await cache.setupCache(page);
|
|
1035
|
-
|
|
1036
|
-
// Primeira visita: baixa da web
|
|
1037
|
-
await page.goto('https://example.com');
|
|
1038
|
-
// Segunda visita: usa cache
|
|
1039
|
-
await page.goto('https://example.com');
|
|
1040
|
-
```
|
|
1041
|
-
|
|
1042
|
-
### Cache com Estratégia de Revalidação
|
|
1043
|
-
|
|
1044
|
-
```ts
|
|
1045
|
-
import { chromium } from 'playwright';
|
|
1046
|
-
|
|
1047
|
-
class RevalidatingCache {
|
|
1048
|
-
private cache = new Map<string, {
|
|
1049
|
-
body: string;
|
|
1050
|
-
etag?: string;
|
|
1051
|
-
lastModified?: string;
|
|
1052
|
-
expiry: number;
|
|
1053
|
-
}>();
|
|
1054
|
-
|
|
1055
|
-
async setupCache(page: any, defaultTtl = 300_000): Promise<void> {
|
|
1056
|
-
await page.route('**/*', async (route: any) => {
|
|
1057
|
-
const url = route.request().url();
|
|
1058
|
-
const cached = this.cache.get(url);
|
|
1059
|
-
|
|
1060
|
-
// Verificar se cache ainda é válido
|
|
1061
|
-
if (cached && cached.expiry > Date.now()) {
|
|
1062
|
-
console.log(`Cache HIT: ${url}`);
|
|
1063
|
-
await route.fulfill({
|
|
1064
|
-
status: 200,
|
|
1065
|
-
body: cached.body,
|
|
1066
|
-
});
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
// Cache expirado ou inexistente — buscar com headers de revalidação
|
|
1071
|
-
const headers: Record<string, string> = {};
|
|
1072
|
-
if (cached?.etag) headers['If-None-Match'] = cached.etag;
|
|
1073
|
-
if (cached?.lastModified) headers['If-Modified-Since'] = cached.lastModified;
|
|
1074
|
-
|
|
1075
|
-
const response = await route.fetch({ headers });
|
|
1076
|
-
|
|
1077
|
-
if (response.status() === 304 && cached) {
|
|
1078
|
-
// Conteúdo não mudou — renovar TTL
|
|
1079
|
-
console.log(`Revalidado: ${url}`);
|
|
1080
|
-
this.cache.set(url, {
|
|
1081
|
-
...cached,
|
|
1082
|
-
expiry: Date.now() + defaultTtl,
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
await route.fulfill({
|
|
1086
|
-
status: 200,
|
|
1087
|
-
body: cached.body,
|
|
1088
|
-
});
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
// Conteúdo mudou — atualizar cache
|
|
1093
|
-
const body = await response.text();
|
|
1094
|
-
const responseHeaders = response.headers();
|
|
1095
|
-
|
|
1096
|
-
this.cache.set(url, {
|
|
1097
|
-
body,
|
|
1098
|
-
etag: responseHeaders['etag'] ?? cached?.etag,
|
|
1099
|
-
lastModified: responseHeaders['last-modified'] ?? cached?.lastModified,
|
|
1100
|
-
expiry: Date.now() + defaultTtl,
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
console.log(`Cache MISS: ${url}`);
|
|
1104
|
-
await route.fulfill({
|
|
1105
|
-
status: response.status(),
|
|
1106
|
-
headers: responseHeaders,
|
|
1107
|
-
body,
|
|
1108
|
-
});
|
|
1109
|
-
});
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
```
|
|
1113
|
-
|
|
1114
|
-
### Cache Seletivo (Apenas para Tipos Específicos)
|
|
1115
|
-
|
|
1116
|
-
```ts
|
|
1117
|
-
class SelectiveCache {
|
|
1118
|
-
private cache = new Map<string, string>();
|
|
1119
|
-
|
|
1120
|
-
async setupCache(page: any): Promise<void> {
|
|
1121
|
-
await page.route('**/*', async (route: any) => {
|
|
1122
|
-
const url = route.request().url();
|
|
1123
|
-
const resourceType = route.request().resourceType();
|
|
1124
|
-
|
|
1125
|
-
// Cache apenas para CSS, JS e imagens
|
|
1126
|
-
if (!['stylesheet', 'script', 'image', 'font'].includes(resourceType)) {
|
|
1127
|
-
await route.continue();
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
const cached = this.cache.get(url);
|
|
1132
|
-
if (cached) {
|
|
1133
|
-
console.log(`Cache HIT (${resourceType}): ${url}`);
|
|
1134
|
-
await route.fulfill({ body: cached });
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
const response = await route.fetch();
|
|
1139
|
-
const body = await response.text();
|
|
1140
|
-
|
|
1141
|
-
if (response.status() === 200) {
|
|
1142
|
-
this.cache.set(url, body);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
await route.fulfill({
|
|
1146
|
-
status: response.status(),
|
|
1147
|
-
body,
|
|
1148
|
-
});
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
```
|
|
1153
|
-
|
|
1154
|
-
---
|
|
1155
|
-
|
|
1156
|
-
## Multi-Layer Architecture
|
|
1157
|
-
|
|
1158
|
-
### L1 (Memória) + L2 (Disco)
|
|
1159
|
-
|
|
1160
|
-
Arquitetura de duas camadas: cache quente em memória, cache frio em disco.
|
|
1161
|
-
|
|
1162
|
-
```ts
|
|
1163
|
-
import { LRUCache } from 'lru-cache';
|
|
1164
|
-
import cacache from 'cacache';
|
|
1165
|
-
import { join } from 'path';
|
|
1166
|
-
import { tmpdir } from 'os';
|
|
1167
|
-
|
|
1168
|
-
class MultiLayerCache {
|
|
1169
|
-
private l1: LRUCache<string, string>;
|
|
1170
|
-
private l2Dir: string;
|
|
1171
|
-
|
|
1172
|
-
constructor(l2Dir?: string) {
|
|
1173
|
-
this.l1 = new LRUCache<string, string>({
|
|
1174
|
-
max: 500,
|
|
1175
|
-
ttl: 1000 * 60 * 5, // 5 minutos
|
|
1176
|
-
});
|
|
1177
|
-
|
|
1178
|
-
this.l2Dir = l2Dir ?? join(tmpdir(), 'scraper-cache');
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
async get(url: string): Promise<{ body: string; source: 'l1' | 'l2' | null }> {
|
|
1182
|
-
// L1: Memória
|
|
1183
|
-
const l1Value = this.l1.get(url);
|
|
1184
|
-
if (l1Value) {
|
|
1185
|
-
return { body: l1Value, source: 'l1' };
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// L2: Disco
|
|
1189
|
-
try {
|
|
1190
|
-
const l2Entry = await cacache.get(this.l2Dir, url);
|
|
1191
|
-
if (l2Entry) {
|
|
1192
|
-
const body = l2Entry.data.toString();
|
|
1193
|
-
// Promover para L1
|
|
1194
|
-
this.l1.set(url, body);
|
|
1195
|
-
return { body, source: 'l2' };
|
|
1196
|
-
}
|
|
1197
|
-
} catch {
|
|
1198
|
-
// Cache miss no disco
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
return { body: '', source: null };
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
async set(url: string, body: string): Promise<void> {
|
|
1205
|
-
// Armazenar em L1
|
|
1206
|
-
this.l1.set(url, body);
|
|
1207
|
-
|
|
1208
|
-
// Armazenar em L2
|
|
1209
|
-
await cacache.put(this.l2Dir, url, body, {
|
|
1210
|
-
metadata: { url, timestamp: Date.now() },
|
|
1211
|
-
});
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
async invalidate(url: string): Promise<void> {
|
|
1215
|
-
this.l1.delete(url);
|
|
1216
|
-
await cacache.rm(this.l2Dir, url);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
async invalidateDomain(domain: string): Promise<void> {
|
|
1220
|
-
// L1: invalidar todas as chaves do domínio
|
|
1221
|
-
for (const key of this.l1.keys()) {
|
|
1222
|
-
if (key.includes(domain)) {
|
|
1223
|
-
this.l1.delete(key);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
// L2: listar e deletar
|
|
1228
|
-
const entries = await cacache.ls(this.l2Dir);
|
|
1229
|
-
for (const [key] of Object.entries(entries)) {
|
|
1230
|
-
if (key.includes(domain)) {
|
|
1231
|
-
await cacache.rm(this.l2Dir, key);
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
async stats(): Promise<{ l1Size: number; l2Size: number }> {
|
|
1237
|
-
const entries = await cacache.ls(this.l2Dir);
|
|
1238
|
-
return {
|
|
1239
|
-
l1Size: this.l1.size,
|
|
1240
|
-
l2Size: Object.keys(entries).length,
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
// Usar
|
|
1246
|
-
const cache = new MultiLayerCache();
|
|
1247
|
-
|
|
1248
|
-
// Fetch com cache de duas camadas
|
|
1249
|
-
async function fetchWithCache(url: string): Promise<string> {
|
|
1250
|
-
const cached = await cache.get(url);
|
|
1251
|
-
if (cached.source) {
|
|
1252
|
-
console.log(`Cache HIT (${cached.source}): ${url}`);
|
|
1253
|
-
return cached.body;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
console.log(`Cache MISS: ${url}`);
|
|
1257
|
-
const response = await fetch(url);
|
|
1258
|
-
const body = await response.text();
|
|
1259
|
-
|
|
1260
|
-
await cache.set(url, body);
|
|
1261
|
-
return body;
|
|
1262
|
-
}
|
|
1263
|
-
```
|
|
1264
|
-
|
|
1265
|
-
### L1 + L2 com Stale-While-Revalidate
|
|
1266
|
-
|
|
1267
|
-
```ts
|
|
1268
|
-
class SWRCache {
|
|
1269
|
-
private l1: LRUCache<string, { body: string; expiry: number }>;
|
|
1270
|
-
private l2Dir: string;
|
|
1271
|
-
|
|
1272
|
-
constructor(l2Dir: string) {
|
|
1273
|
-
this.l1 = new LRUCache<string, { body: string; expiry: number }>({
|
|
1274
|
-
max: 500,
|
|
1275
|
-
});
|
|
1276
|
-
this.l2Dir = l2Dir;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
async fetch(
|
|
1280
|
-
url: string,
|
|
1281
|
-
fetcher: () => Promise<string>,
|
|
1282
|
-
options: { ttl?: number; swr?: number } = {},
|
|
1283
|
-
): Promise<string> {
|
|
1284
|
-
const { ttl = 300_000, swr = 600_000 } = options; // 5 min TTL, 10 min SWR
|
|
1285
|
-
const now = Date.now();
|
|
1286
|
-
|
|
1287
|
-
// L1 check
|
|
1288
|
-
const l1 = this.l1.get(url);
|
|
1289
|
-
if (l1 && l1.expiry > now) {
|
|
1290
|
-
return l1.body; // Fresh
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
if (l1 && l1.expiry + swr > now) {
|
|
1294
|
-
// Stale mas utilizável — revalidar em background
|
|
1295
|
-
console.log(`SWR: Revalidating ${url} in background`);
|
|
1296
|
-
fetcher().then(body => {
|
|
1297
|
-
this.l1.set(url, { body, expiry: now + ttl });
|
|
1298
|
-
cacache.put(this.l2Dir, url, body);
|
|
1299
|
-
});
|
|
1300
|
-
return l1.body; // Return stale imediatamente
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
// L2 check
|
|
1304
|
-
try {
|
|
1305
|
-
const l2 = await cacache.get(this.l2Dir, url);
|
|
1306
|
-
if (l2) {
|
|
1307
|
-
const body = l2.data.toString();
|
|
1308
|
-
this.l1.set(url, { body, expiry: now + ttl });
|
|
1309
|
-
return body;
|
|
1310
|
-
}
|
|
1311
|
-
} catch {
|
|
1312
|
-
// L2 miss
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
// Fetch completo
|
|
1316
|
-
console.log(`Full fetch: ${url}`);
|
|
1317
|
-
const body = await fetcher();
|
|
1318
|
-
|
|
1319
|
-
this.l1.set(url, { body, expiry: now + ttl });
|
|
1320
|
-
await cacache.put(this.l2Dir, url, body);
|
|
1321
|
-
|
|
1322
|
-
return body;
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
```
|
|
1326
|
-
|
|
1327
|
-
---
|
|
1328
|
-
|
|
1329
|
-
## Código Completo
|
|
1330
|
-
|
|
1331
|
-
### Exemplo 1: Web Scraper com Cache Completo
|
|
1332
|
-
|
|
1333
|
-
```ts
|
|
1334
|
-
import { PlaywrightCrawler } from 'crawlee';
|
|
1335
|
-
import { LRUCache } from 'lru-cache';
|
|
1336
|
-
import cacache from 'cacache';
|
|
1337
|
-
import { join } from 'path';
|
|
1338
|
-
import { tmpdir } from 'os';
|
|
1339
|
-
|
|
1340
|
-
interface ScrapeCacheConfig {
|
|
1341
|
-
l1MaxSize: number;
|
|
1342
|
-
l1Ttl: number;
|
|
1343
|
-
l2Dir: string;
|
|
1344
|
-
defaultTtl: number;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
class ScrapeCache {
|
|
1348
|
-
private l1: LRUCache<string, string>;
|
|
1349
|
-
private l2Dir: string;
|
|
1350
|
-
|
|
1351
|
-
constructor(config: ScrapeCacheConfig) {
|
|
1352
|
-
this.l1 = new LRUCache({
|
|
1353
|
-
max: config.l1MaxSize,
|
|
1354
|
-
ttl: config.l1Ttl,
|
|
1355
|
-
});
|
|
1356
|
-
this.l2Dir = config.l2Dir;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
async get(url: string): Promise<string | null> {
|
|
1360
|
-
const l1 = this.l1.get(url);
|
|
1361
|
-
if (l1) return l1;
|
|
1362
|
-
|
|
1363
|
-
try {
|
|
1364
|
-
const l2 = await cacache.get(this.l2Dir, url);
|
|
1365
|
-
if (l2) {
|
|
1366
|
-
const body = l2.data.toString();
|
|
1367
|
-
this.l1.set(url, body);
|
|
1368
|
-
return body;
|
|
1369
|
-
}
|
|
1370
|
-
} catch {}
|
|
1371
|
-
|
|
1372
|
-
return null;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
async set(url: string, body: string): Promise<void> {
|
|
1376
|
-
this.l1.set(url, body);
|
|
1377
|
-
await cacache.put(this.l2Dir, url, body, {
|
|
1378
|
-
metadata: { url, timestamp: Date.now() },
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
async size(): Promise<number> {
|
|
1383
|
-
const entries = await cacache.ls(this.l2Dir);
|
|
1384
|
-
return Object.keys(entries).length;
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// Configurar
|
|
1389
|
-
const cache = new ScrapeCache({
|
|
1390
|
-
l1MaxSize: 500,
|
|
1391
|
-
l1Ttl: 1000 * 60 * 5,
|
|
1392
|
-
l2Dir: join(tmpdir(), 'webscrape-cache'),
|
|
1393
|
-
defaultTtl: 1000 * 60 * 60,
|
|
1394
|
-
});
|
|
1395
|
-
|
|
1396
|
-
// Crawler com cache
|
|
1397
|
-
const crawler = new PlaywrightCrawler({
|
|
1398
|
-
maxConcurrency: 3,
|
|
1399
|
-
requestHandlerTimeoutSecs: 30,
|
|
1400
|
-
async requestHandler({ page, request }) {
|
|
1401
|
-
const url = request.url;
|
|
1402
|
-
|
|
1403
|
-
// Verificar cache
|
|
1404
|
-
const cached = await cache.get(url);
|
|
1405
|
-
if (cached) {
|
|
1406
|
-
console.log(`Cache HIT: ${url}`);
|
|
1407
|
-
// Processar HTML do cache
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
// Cache miss — buscar
|
|
1412
|
-
console.log(`Cache MISS: ${url}`);
|
|
1413
|
-
const html = await page.content();
|
|
1414
|
-
|
|
1415
|
-
// Armazenar em cache
|
|
1416
|
-
await cache.set(url, html);
|
|
1417
|
-
|
|
1418
|
-
// Processar...
|
|
1419
|
-
},
|
|
1420
|
-
});
|
|
1421
|
-
|
|
1422
|
-
await crawler.run(['https://example.com']);
|
|
1423
|
-
|
|
1424
|
-
// Estatísticas
|
|
1425
|
-
console.log(`Cache size: ${await cache.size()}`);
|
|
1426
|
-
```
|
|
1427
|
-
|
|
1428
|
-
### Exemplo 2: Cache com ETag e Revalidação
|
|
1429
|
-
|
|
1430
|
-
```ts
|
|
1431
|
-
import got from 'got';
|
|
1432
|
-
|
|
1433
|
-
interface CachedResponse {
|
|
1434
|
-
body: string;
|
|
1435
|
-
etag?: string;
|
|
1436
|
-
lastModified?: string;
|
|
1437
|
-
expiry: number;
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
class ETagRevalidatingCache {
|
|
1441
|
-
private cache = new Map<string, CachedResponse>();
|
|
1442
|
-
|
|
1443
|
-
async fetch(url: string, ttl = 300_000): Promise<string> {
|
|
1444
|
-
const cached = this.cache.get(url);
|
|
1445
|
-
const now = Date.now();
|
|
1446
|
-
|
|
1447
|
-
if (cached && cached.expiry > now) {
|
|
1448
|
-
return cached.body;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
const headers: Record<string, string> = {};
|
|
1452
|
-
if (cached?.etag) headers['If-None-Match'] = cached.etag;
|
|
1453
|
-
if (cached?.lastModified) headers['If-Modified-Since'] = cached.lastModified;
|
|
1454
|
-
|
|
1455
|
-
const response = await got(url, {
|
|
1456
|
-
headers,
|
|
1457
|
-
throwHttpErrors: false,
|
|
1458
|
-
});
|
|
1459
|
-
|
|
1460
|
-
if (response.statusCode === 304 && cached) {
|
|
1461
|
-
this.cache.set(url, { ...cached, expiry: now + ttl });
|
|
1462
|
-
return cached.body;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
const body = response.body;
|
|
1466
|
-
const newCached: CachedResponse = {
|
|
1467
|
-
body,
|
|
1468
|
-
etag: response.headers['etag'] ?? cached?.etag,
|
|
1469
|
-
lastModified: response.headers['last-modified'] ?? cached?.lastModified,
|
|
1470
|
-
expiry: now + ttl,
|
|
1471
|
-
};
|
|
1472
|
-
|
|
1473
|
-
this.cache.set(url, newCached);
|
|
1474
|
-
return body;
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
```
|
|
1478
|
-
|
|
1479
|
-
### Exemplo 3: Cache com Playwright e Intercepção
|
|
1480
|
-
|
|
1481
|
-
```ts
|
|
1482
|
-
import { chromium } from 'playwright';
|
|
1483
|
-
|
|
1484
|
-
class PlaywrightMultiLayerCache {
|
|
1485
|
-
private memoryCache = new Map<string, { body: Buffer; contentType: string }>();
|
|
1486
|
-
|
|
1487
|
-
async scrapeWithCache(url: string): Promise<{ html: string; fromCache: boolean }> {
|
|
1488
|
-
const browser = await chromium.launch();
|
|
1489
|
-
const page = await browser.newPage();
|
|
1490
|
-
|
|
1491
|
-
let fromCache = false;
|
|
1492
|
-
|
|
1493
|
-
await page.route('**/*', async (route) => {
|
|
1494
|
-
const requestUrl = route.request().url();
|
|
1495
|
-
const resourceType = route.request().resourceType();
|
|
1496
|
-
|
|
1497
|
-
// Cache apenas para páginas HTML
|
|
1498
|
-
if (resourceType !== 'document') {
|
|
1499
|
-
await route.continue();
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
const cached = this.memoryCache.get(requestUrl);
|
|
1504
|
-
if (cached) {
|
|
1505
|
-
fromCache = true;
|
|
1506
|
-
await route.fulfill({
|
|
1507
|
-
status: 200,
|
|
1508
|
-
contentType: cached.contentType,
|
|
1509
|
-
body: cached.body,
|
|
1510
|
-
});
|
|
1511
|
-
return;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
const response = await route.fetch();
|
|
1515
|
-
const body = await response.body();
|
|
1516
|
-
|
|
1517
|
-
if (response.status() === 200) {
|
|
1518
|
-
this.memoryCache.set(requestUrl, {
|
|
1519
|
-
body,
|
|
1520
|
-
contentType: response.headers()['content-type'] ?? 'text/html',
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
await route.fulfill({
|
|
1525
|
-
status: response.status(),
|
|
1526
|
-
headers: response.headers(),
|
|
1527
|
-
body,
|
|
1528
|
-
});
|
|
1529
|
-
});
|
|
1530
|
-
|
|
1531
|
-
await page.goto(url, { waitUntil: 'networkidle' });
|
|
1532
|
-
const html = await page.content();
|
|
1533
|
-
await browser.close();
|
|
1534
|
-
|
|
1535
|
-
return { html, fromCache };
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
// Usar
|
|
1540
|
-
const cache = new PlaywrightMultiLayerCache();
|
|
1541
|
-
const result = await cache.scrapeWithCache('https://example.com');
|
|
1542
|
-
console.log(`From cache: ${result.fromCache}`);
|
|
1543
|
-
```
|
|
1544
|
-
|
|
1545
|
-
### Exemplo 4: Cache Manager com Múltiplos Backends
|
|
1546
|
-
|
|
1547
|
-
```ts
|
|
1548
|
-
import { caching } from 'cache-manager';
|
|
1549
|
-
|
|
1550
|
-
async function createMultiBackendCache() {
|
|
1551
|
-
// L1: Memória
|
|
1552
|
-
const l1 = await caching('memory', {
|
|
1553
|
-
max: 500,
|
|
1554
|
-
ttl: 300, // 5 min
|
|
1555
|
-
});
|
|
1556
|
-
|
|
1557
|
-
// L2: Redis (se disponível)
|
|
1558
|
-
// const l2 = await caching(redisStore, {
|
|
1559
|
-
// host: 'localhost',
|
|
1560
|
-
// port: 6379,
|
|
1561
|
-
// ttl: 3600, // 1 hora
|
|
1562
|
-
// });
|
|
1563
|
-
|
|
1564
|
-
async function get<T>(key: string): Promise<T | null> {
|
|
1565
|
-
// L1
|
|
1566
|
-
const l1Value = await l1.get<T>(key);
|
|
1567
|
-
if (l1Value !== undefined) return l1Value;
|
|
1568
|
-
|
|
1569
|
-
// L2 (se disponível)
|
|
1570
|
-
// const l2Value = await l2.get<T>(key);
|
|
1571
|
-
// if (l2Value !== undefined) {
|
|
1572
|
-
// await l1.set(key, l2Value, 300); // Promover para L1
|
|
1573
|
-
// return l2Value;
|
|
1574
|
-
// }
|
|
1575
|
-
|
|
1576
|
-
return null;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
async function set<T>(key: string, value: T, ttl?: number): Promise<void> {
|
|
1580
|
-
await l1.set(key, value, ttl ?? 300);
|
|
1581
|
-
// await l2.set(key, value, ttl ?? 3600);
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
async function invalidate(key: string): Promise<void> {
|
|
1585
|
-
await l1.del(key);
|
|
1586
|
-
// await l2.del(key);
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
return { get, set, invalidate };
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
const cache = await createMultiBackendCache();
|
|
1593
|
-
await cache.set('key', 'value');
|
|
1594
|
-
const value = await cache.get<string>('key');
|
|
1595
|
-
```
|
|
1596
|
-
|
|
1597
|
-
---
|
|
1598
|
-
|
|
1599
|
-
## Referências
|
|
1600
|
-
|
|
1601
|
-
- [lru-cache npm](https://www.npmjs.com/package/lru-cache)
|
|
1602
|
-
- [node-cache npm](https://www.npmjs.com/package/node-cache)
|
|
1603
|
-
- [quick-lru npm](https://www.npmjs.com/package/quick-lru)
|
|
1604
|
-
- [cache-manager npm](https://www.npmjs.com/package/cache-manager)
|
|
1605
|
-
- [keyv npm](https://www.npmjs.com/package/keyv)
|
|
1606
|
-
- [cacache npm](https://www.npmjs.com/package/cacache)
|
|
1607
|
-
- [flat-cache npm](https://www.npmjs.com/package/flat-cache)
|
|
1608
|
-
- [got-scraping npm](https://www.npmjs.com/package/got-scraping)
|
|
1609
|
-
- [Playwright Network Interception](https://playwright.dev/docs/network)
|
|
1610
|
-
- [Crawlee Documentation](https://crawlee.dev)
|