@torrent-tv/proxy 2.87.20 → 2.88.0
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/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +8 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +133 -133
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +50 -50
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +106 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +138 -138
- package/routes/api/transcode-sessions/post.js +173 -173
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +380 -380
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/server.js +725 -725
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +813 -813
- package/services/encode/EncodeRuns.js +1235 -1235
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +758 -758
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2184 -2184
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +416 -416
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1141 -1141
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +254 -254
- package/services/encode/quality/output-format.js +315 -315
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +754 -754
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +456 -456
- package/services/encode/segment-formats/index.js +85 -85
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +515 -515
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +245 -245
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1150 -1150
- package/services/server/ViewerRequests.js +760 -760
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/playback-readiness.js +696 -696
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +992 -992
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1931 -1908
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1240 -1240
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +382 -324
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/episode-naming.js +182 -0
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/torrent-pool.js +2675 -2675
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +765 -765
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/torrent/worker/worker.js +1129 -1129
- package/services/transport/data-channel-handler.js +1369 -1369
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +162 -162
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/episode-naming.test.js +131 -0
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-presentation-clock.test.js +147 -147
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +204 -204
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +401 -401
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -365
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/playback-readiness.test.js +453 -453
- package/test/playback-start-regressions.test.js +99 -99
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1229 -1229
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +102 -102
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +12 -2
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +209 -209
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +407 -407
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,185 +1,185 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file One disk, one owner, three claimants.
|
|
3
|
-
*
|
|
4
|
-
* What these pin is the thing that was wrong: each consumer read the free space
|
|
5
|
-
* as though it were alone, so three ceilings each stood for the whole disk.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import test from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { MachineBudget } from "../services/storage/MachineBudget.js";
|
|
11
|
-
|
|
12
|
-
const MEGABYTE = 1024 * 1024;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {string} name
|
|
16
|
-
* @param {number} held
|
|
17
|
-
* @param {number} wanted
|
|
18
|
-
*/
|
|
19
|
-
function consumerOf(name, held, wanted) {
|
|
20
|
-
const state = { allowed: -1 };
|
|
21
|
-
return {
|
|
22
|
-
state,
|
|
23
|
-
consumer: {
|
|
24
|
-
name,
|
|
25
|
-
held: () => held,
|
|
26
|
-
wanted: () => wanted,
|
|
27
|
-
allow: (bytes) => {
|
|
28
|
-
state.allowed = bytes;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* A budget over ONE resource, which is what this file is about: the division
|
|
36
|
-
* itself, with the several-resources question asked in `one-budget.test.js`.
|
|
37
|
-
*
|
|
38
|
-
* @param {{ readFree: () => Promise<number | null>, logger?: object }} params
|
|
39
|
-
*/
|
|
40
|
-
function diskOf({ readFree, logger }) {
|
|
41
|
-
const budget = new MachineBudget({ logger });
|
|
42
|
-
budget.defineResource({ name: "disk", readFree });
|
|
43
|
-
return {
|
|
44
|
-
register: (consumer) => budget.register({ ...consumer, resource: "disk" }),
|
|
45
|
-
forget: (name) => budget.forget(name),
|
|
46
|
-
describe: () => budget.describe(),
|
|
47
|
-
revise: async () => {
|
|
48
|
-
const readings = await budget.revise();
|
|
49
|
-
return readings.get("disk") ?? { freeBytes: 0, allowanceBytes: 0, shares: [] };
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
test("what everyone may hold together never exceeds what the disk has", async () => {
|
|
55
|
-
const free = 100 * MEGABYTE;
|
|
56
|
-
const space = diskOf({ readFree: async () => free });
|
|
57
|
-
const segments = consumerOf("segments", 10 * MEGABYTE, 1000 * MEGABYTE);
|
|
58
|
-
const pieces = consumerOf("pieces", 20 * MEGABYTE, 1000 * MEGABYTE);
|
|
59
|
-
const diagnostics = consumerOf("diagnostics", 5 * MEGABYTE, 1000 * MEGABYTE);
|
|
60
|
-
space.register(segments.consumer);
|
|
61
|
-
space.register(pieces.consumer);
|
|
62
|
-
space.register(diagnostics.consumer);
|
|
63
|
-
|
|
64
|
-
const { allowanceBytes } = await space.revise();
|
|
65
|
-
|
|
66
|
-
const together = segments.state.allowed + pieces.state.allowed + diagnostics.state.allowed;
|
|
67
|
-
assert.ok(together <= allowanceBytes, "the shares add up to more than the allowance");
|
|
68
|
-
// Free space plus what they already hold: that pair is the ceiling they could
|
|
69
|
-
// reach, which is the same statement the memory allowance makes.
|
|
70
|
-
assert.equal(allowanceBytes, free + 35 * MEGABYTE);
|
|
71
|
-
assert.ok(
|
|
72
|
-
together <= free + 35 * MEGABYTE,
|
|
73
|
-
"three consumers were between them allowed more than the disk has"
|
|
74
|
-
);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("when everyone's ask fits, everyone gets it and the disk never binds", async () => {
|
|
78
|
-
const space = diskOf({ readFree: async () => 1000 * MEGABYTE });
|
|
79
|
-
const small = consumerOf("diagnostics", 0, 5 * MEGABYTE);
|
|
80
|
-
const large = consumerOf("segments", 0, 500 * MEGABYTE);
|
|
81
|
-
space.register(small.consumer);
|
|
82
|
-
space.register(large.consumer);
|
|
83
|
-
|
|
84
|
-
await space.revise();
|
|
85
|
-
|
|
86
|
-
assert.equal(small.state.allowed, 5 * MEGABYTE);
|
|
87
|
-
assert.equal(large.state.allowed, 500 * MEGABYTE);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("when they do not fit, each is cut in proportion to what it asked", async () => {
|
|
91
|
-
// The same rule memory divides by, and the same reason: a share taken from
|
|
92
|
-
// whoever asked most would hand the disk to whoever grew fastest.
|
|
93
|
-
const space = diskOf({ readFree: async () => 200 * MEGABYTE });
|
|
94
|
-
const modest = consumerOf("diagnostics", 0, 100 * MEGABYTE);
|
|
95
|
-
const greedy = consumerOf("segments", 0, 300 * MEGABYTE);
|
|
96
|
-
space.register(modest.consumer);
|
|
97
|
-
space.register(greedy.consumer);
|
|
98
|
-
|
|
99
|
-
await space.revise();
|
|
100
|
-
|
|
101
|
-
assert.equal(modest.state.allowed, 50 * MEGABYTE);
|
|
102
|
-
assert.equal(greedy.state.allowed, 150 * MEGABYTE);
|
|
103
|
-
assert.equal(modest.state.allowed + greedy.state.allowed, 200 * MEGABYTE);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("a disk that fills lowers every share, rather than keeping one taken when it was empty", async () => {
|
|
107
|
-
let free = 1000 * MEGABYTE;
|
|
108
|
-
const space = diskOf({ readFree: async () => free });
|
|
109
|
-
const segments = consumerOf("segments", 0, 10_000 * MEGABYTE);
|
|
110
|
-
const pieces = consumerOf("pieces", 0, 10_000 * MEGABYTE);
|
|
111
|
-
space.register(segments.consumer);
|
|
112
|
-
space.register(pieces.consumer);
|
|
113
|
-
|
|
114
|
-
await space.revise();
|
|
115
|
-
const roomy = segments.state.allowed;
|
|
116
|
-
|
|
117
|
-
free = 40 * MEGABYTE;
|
|
118
|
-
await space.revise();
|
|
119
|
-
|
|
120
|
-
assert.ok(segments.state.allowed < roomy, "the share did not follow the disk down");
|
|
121
|
-
assert.ok(pieces.state.allowed < roomy, "the other share did not follow either");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("what another process took is left for it, not spent", async () => {
|
|
125
|
-
// Between two readings the disk lost 400 MB that none of our consumers took.
|
|
126
|
-
// That is a measurement of somebody else's demand, and it is what the next
|
|
127
|
-
// allowance leaves alone.
|
|
128
|
-
let free = 1000 * MEGABYTE;
|
|
129
|
-
const space = diskOf({ readFree: async () => free });
|
|
130
|
-
const only = consumerOf("segments", 0, 10_000 * MEGABYTE);
|
|
131
|
-
space.register(only.consumer);
|
|
132
|
-
|
|
133
|
-
await space.revise();
|
|
134
|
-
free = 600 * MEGABYTE;
|
|
135
|
-
const { allowanceBytes } = await space.revise();
|
|
136
|
-
|
|
137
|
-
assert.equal(allowanceBytes, 200 * MEGABYTE, "the fall we did not cause was spent anyway");
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("a disk that cannot be read allows nothing to grow", async () => {
|
|
141
|
-
const space = diskOf({ readFree: async () => null });
|
|
142
|
-
const segments = consumerOf("segments", 0, 500 * MEGABYTE);
|
|
143
|
-
space.register(segments.consumer);
|
|
144
|
-
|
|
145
|
-
const { freeBytes, allowanceBytes } = await space.revise();
|
|
146
|
-
|
|
147
|
-
assert.equal(freeBytes, 0);
|
|
148
|
-
assert.equal(allowanceBytes, 0);
|
|
149
|
-
assert.equal(segments.state.allowed, 0, "an unreadable disk was treated as an empty one");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("it says what the disk has and what each claimant may hold", async () => {
|
|
153
|
-
const space = diskOf({ readFree: async () => 100 * MEGABYTE });
|
|
154
|
-
assert.match(space.describe(), /nothing has claimed/);
|
|
155
|
-
space.register(consumerOf("segments", 4 * MEGABYTE, 8 * MEGABYTE).consumer);
|
|
156
|
-
await space.revise();
|
|
157
|
-
assert.match(space.describe(), /disk: 100MB free; segments 4MB of 8MB/);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("it says what it decided, every pass", async () => {
|
|
161
|
-
const lines = [];
|
|
162
|
-
const space = diskOf({
|
|
163
|
-
readFree: async () => 100 * MEGABYTE,
|
|
164
|
-
logger: { info: (line) => lines.push(line) }
|
|
165
|
-
});
|
|
166
|
-
space.register(consumerOf("segments", 4 * MEGABYTE, 8 * MEGABYTE).consumer);
|
|
167
|
-
await space.revise();
|
|
168
|
-
assert.equal(lines.length, 1, "a pass that decided the shares said nothing about them");
|
|
169
|
-
assert.match(lines[0], /disk: 100MB free; segments 4MB of 8MB/);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("the free space is read from the nearest directory that exists", async () => {
|
|
173
|
-
// The segments' directory is made when the first session starts and removed
|
|
174
|
-
// when the proxy stops, so at every start `statfs` on it fails. Field
|
|
175
|
-
// 2026-09-10: the first reading said `disk: 0MB free` on a host with 103 GB,
|
|
176
|
-
// and zero means "no room" to everything that reads it.
|
|
177
|
-
const { freeBytesFor } = await import("../services/storage/free.js");
|
|
178
|
-
const os = await import("node:os");
|
|
179
|
-
const path = await import("node:path");
|
|
180
|
-
const missing = path.join(os.tmpdir(), "no-such-directory-here", "nor-here");
|
|
181
|
-
|
|
182
|
-
const bytes = await freeBytesFor(missing);
|
|
183
|
-
|
|
184
|
-
assert.ok(Number.isFinite(bytes) && bytes > 0, "a directory that does not exist read as no disk");
|
|
185
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file One disk, one owner, three claimants.
|
|
3
|
+
*
|
|
4
|
+
* What these pin is the thing that was wrong: each consumer read the free space
|
|
5
|
+
* as though it were alone, so three ceilings each stood for the whole disk.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { MachineBudget } from "../services/storage/MachineBudget.js";
|
|
11
|
+
|
|
12
|
+
const MEGABYTE = 1024 * 1024;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} name
|
|
16
|
+
* @param {number} held
|
|
17
|
+
* @param {number} wanted
|
|
18
|
+
*/
|
|
19
|
+
function consumerOf(name, held, wanted) {
|
|
20
|
+
const state = { allowed: -1 };
|
|
21
|
+
return {
|
|
22
|
+
state,
|
|
23
|
+
consumer: {
|
|
24
|
+
name,
|
|
25
|
+
held: () => held,
|
|
26
|
+
wanted: () => wanted,
|
|
27
|
+
allow: (bytes) => {
|
|
28
|
+
state.allowed = bytes;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A budget over ONE resource, which is what this file is about: the division
|
|
36
|
+
* itself, with the several-resources question asked in `one-budget.test.js`.
|
|
37
|
+
*
|
|
38
|
+
* @param {{ readFree: () => Promise<number | null>, logger?: object }} params
|
|
39
|
+
*/
|
|
40
|
+
function diskOf({ readFree, logger }) {
|
|
41
|
+
const budget = new MachineBudget({ logger });
|
|
42
|
+
budget.defineResource({ name: "disk", readFree });
|
|
43
|
+
return {
|
|
44
|
+
register: (consumer) => budget.register({ ...consumer, resource: "disk" }),
|
|
45
|
+
forget: (name) => budget.forget(name),
|
|
46
|
+
describe: () => budget.describe(),
|
|
47
|
+
revise: async () => {
|
|
48
|
+
const readings = await budget.revise();
|
|
49
|
+
return readings.get("disk") ?? { freeBytes: 0, allowanceBytes: 0, shares: [] };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test("what everyone may hold together never exceeds what the disk has", async () => {
|
|
55
|
+
const free = 100 * MEGABYTE;
|
|
56
|
+
const space = diskOf({ readFree: async () => free });
|
|
57
|
+
const segments = consumerOf("segments", 10 * MEGABYTE, 1000 * MEGABYTE);
|
|
58
|
+
const pieces = consumerOf("pieces", 20 * MEGABYTE, 1000 * MEGABYTE);
|
|
59
|
+
const diagnostics = consumerOf("diagnostics", 5 * MEGABYTE, 1000 * MEGABYTE);
|
|
60
|
+
space.register(segments.consumer);
|
|
61
|
+
space.register(pieces.consumer);
|
|
62
|
+
space.register(diagnostics.consumer);
|
|
63
|
+
|
|
64
|
+
const { allowanceBytes } = await space.revise();
|
|
65
|
+
|
|
66
|
+
const together = segments.state.allowed + pieces.state.allowed + diagnostics.state.allowed;
|
|
67
|
+
assert.ok(together <= allowanceBytes, "the shares add up to more than the allowance");
|
|
68
|
+
// Free space plus what they already hold: that pair is the ceiling they could
|
|
69
|
+
// reach, which is the same statement the memory allowance makes.
|
|
70
|
+
assert.equal(allowanceBytes, free + 35 * MEGABYTE);
|
|
71
|
+
assert.ok(
|
|
72
|
+
together <= free + 35 * MEGABYTE,
|
|
73
|
+
"three consumers were between them allowed more than the disk has"
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("when everyone's ask fits, everyone gets it and the disk never binds", async () => {
|
|
78
|
+
const space = diskOf({ readFree: async () => 1000 * MEGABYTE });
|
|
79
|
+
const small = consumerOf("diagnostics", 0, 5 * MEGABYTE);
|
|
80
|
+
const large = consumerOf("segments", 0, 500 * MEGABYTE);
|
|
81
|
+
space.register(small.consumer);
|
|
82
|
+
space.register(large.consumer);
|
|
83
|
+
|
|
84
|
+
await space.revise();
|
|
85
|
+
|
|
86
|
+
assert.equal(small.state.allowed, 5 * MEGABYTE);
|
|
87
|
+
assert.equal(large.state.allowed, 500 * MEGABYTE);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("when they do not fit, each is cut in proportion to what it asked", async () => {
|
|
91
|
+
// The same rule memory divides by, and the same reason: a share taken from
|
|
92
|
+
// whoever asked most would hand the disk to whoever grew fastest.
|
|
93
|
+
const space = diskOf({ readFree: async () => 200 * MEGABYTE });
|
|
94
|
+
const modest = consumerOf("diagnostics", 0, 100 * MEGABYTE);
|
|
95
|
+
const greedy = consumerOf("segments", 0, 300 * MEGABYTE);
|
|
96
|
+
space.register(modest.consumer);
|
|
97
|
+
space.register(greedy.consumer);
|
|
98
|
+
|
|
99
|
+
await space.revise();
|
|
100
|
+
|
|
101
|
+
assert.equal(modest.state.allowed, 50 * MEGABYTE);
|
|
102
|
+
assert.equal(greedy.state.allowed, 150 * MEGABYTE);
|
|
103
|
+
assert.equal(modest.state.allowed + greedy.state.allowed, 200 * MEGABYTE);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("a disk that fills lowers every share, rather than keeping one taken when it was empty", async () => {
|
|
107
|
+
let free = 1000 * MEGABYTE;
|
|
108
|
+
const space = diskOf({ readFree: async () => free });
|
|
109
|
+
const segments = consumerOf("segments", 0, 10_000 * MEGABYTE);
|
|
110
|
+
const pieces = consumerOf("pieces", 0, 10_000 * MEGABYTE);
|
|
111
|
+
space.register(segments.consumer);
|
|
112
|
+
space.register(pieces.consumer);
|
|
113
|
+
|
|
114
|
+
await space.revise();
|
|
115
|
+
const roomy = segments.state.allowed;
|
|
116
|
+
|
|
117
|
+
free = 40 * MEGABYTE;
|
|
118
|
+
await space.revise();
|
|
119
|
+
|
|
120
|
+
assert.ok(segments.state.allowed < roomy, "the share did not follow the disk down");
|
|
121
|
+
assert.ok(pieces.state.allowed < roomy, "the other share did not follow either");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("what another process took is left for it, not spent", async () => {
|
|
125
|
+
// Between two readings the disk lost 400 MB that none of our consumers took.
|
|
126
|
+
// That is a measurement of somebody else's demand, and it is what the next
|
|
127
|
+
// allowance leaves alone.
|
|
128
|
+
let free = 1000 * MEGABYTE;
|
|
129
|
+
const space = diskOf({ readFree: async () => free });
|
|
130
|
+
const only = consumerOf("segments", 0, 10_000 * MEGABYTE);
|
|
131
|
+
space.register(only.consumer);
|
|
132
|
+
|
|
133
|
+
await space.revise();
|
|
134
|
+
free = 600 * MEGABYTE;
|
|
135
|
+
const { allowanceBytes } = await space.revise();
|
|
136
|
+
|
|
137
|
+
assert.equal(allowanceBytes, 200 * MEGABYTE, "the fall we did not cause was spent anyway");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("a disk that cannot be read allows nothing to grow", async () => {
|
|
141
|
+
const space = diskOf({ readFree: async () => null });
|
|
142
|
+
const segments = consumerOf("segments", 0, 500 * MEGABYTE);
|
|
143
|
+
space.register(segments.consumer);
|
|
144
|
+
|
|
145
|
+
const { freeBytes, allowanceBytes } = await space.revise();
|
|
146
|
+
|
|
147
|
+
assert.equal(freeBytes, 0);
|
|
148
|
+
assert.equal(allowanceBytes, 0);
|
|
149
|
+
assert.equal(segments.state.allowed, 0, "an unreadable disk was treated as an empty one");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("it says what the disk has and what each claimant may hold", async () => {
|
|
153
|
+
const space = diskOf({ readFree: async () => 100 * MEGABYTE });
|
|
154
|
+
assert.match(space.describe(), /nothing has claimed/);
|
|
155
|
+
space.register(consumerOf("segments", 4 * MEGABYTE, 8 * MEGABYTE).consumer);
|
|
156
|
+
await space.revise();
|
|
157
|
+
assert.match(space.describe(), /disk: 100MB free; segments 4MB of 8MB/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("it says what it decided, every pass", async () => {
|
|
161
|
+
const lines = [];
|
|
162
|
+
const space = diskOf({
|
|
163
|
+
readFree: async () => 100 * MEGABYTE,
|
|
164
|
+
logger: { info: (line) => lines.push(line) }
|
|
165
|
+
});
|
|
166
|
+
space.register(consumerOf("segments", 4 * MEGABYTE, 8 * MEGABYTE).consumer);
|
|
167
|
+
await space.revise();
|
|
168
|
+
assert.equal(lines.length, 1, "a pass that decided the shares said nothing about them");
|
|
169
|
+
assert.match(lines[0], /disk: 100MB free; segments 4MB of 8MB/);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("the free space is read from the nearest directory that exists", async () => {
|
|
173
|
+
// The segments' directory is made when the first session starts and removed
|
|
174
|
+
// when the proxy stops, so at every start `statfs` on it fails. Field
|
|
175
|
+
// 2026-09-10: the first reading said `disk: 0MB free` on a host with 103 GB,
|
|
176
|
+
// and zero means "no room" to everything that reads it.
|
|
177
|
+
const { freeBytesFor } = await import("../services/storage/free.js");
|
|
178
|
+
const os = await import("node:os");
|
|
179
|
+
const path = await import("node:path");
|
|
180
|
+
const missing = path.join(os.tmpdir(), "no-such-directory-here", "nor-here");
|
|
181
|
+
|
|
182
|
+
const bytes = await freeBytesFor(missing);
|
|
183
|
+
|
|
184
|
+
assert.ok(Number.isFinite(bytes) && bytes > 0, "a directory that does not exist read as no disk");
|
|
185
|
+
});
|