@earendil-works/chord 1.4.0 → 1.5.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/CHANGELOG.md +41 -0
- package/README.md +79 -14
- package/dist/cell.d.ts +7 -7
- package/dist/cell.d.ts.map +1 -1
- package/dist/cell.js.map +1 -1
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +1 -1
- package/dist/context/index.js.map +1 -1
- package/dist/contract.d.ts +51 -52
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +19 -24
- package/dist/contract.js.map +1 -1
- package/dist/delta/apply-immutable-trusted.js +3 -4
- package/dist/delta/apply-immutable-trusted.js.map +1 -1
- package/dist/delta/diff.d.ts.map +1 -1
- package/dist/delta/diff.js +5 -3
- package/dist/delta/diff.js.map +1 -1
- package/dist/delta/index.d.ts +9 -1
- package/dist/delta/index.d.ts.map +1 -1
- package/dist/delta/index.js +15 -11
- package/dist/delta/index.js.map +1 -1
- package/dist/delta/pending.d.ts +36 -0
- package/dist/delta/pending.d.ts.map +1 -0
- package/dist/delta/pending.js +143 -0
- package/dist/delta/pending.js.map +1 -0
- package/dist/delta/tracker.d.ts +12 -0
- package/dist/delta/tracker.d.ts.map +1 -1
- package/dist/delta/tracker.js +109 -39
- package/dist/delta/tracker.js.map +1 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +9 -7
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/next/contracts/accepts.d.ts +81 -0
- package/dist/next/contracts/accepts.d.ts.map +1 -0
- package/dist/next/contracts/accepts.js +123 -0
- package/dist/next/contracts/accepts.js.map +1 -0
- package/dist/next/contracts/contract.d.ts +121 -0
- package/dist/next/contracts/contract.d.ts.map +1 -0
- package/dist/next/contracts/contract.js +50 -0
- package/dist/next/contracts/contract.js.map +1 -0
- package/dist/next/contracts/declare.d.ts +107 -0
- package/dist/next/contracts/declare.d.ts.map +1 -0
- package/dist/next/contracts/declare.js +136 -0
- package/dist/next/contracts/declare.js.map +1 -0
- package/dist/next/contracts/evolve.d.ts +92 -0
- package/dist/next/contracts/evolve.d.ts.map +1 -0
- package/dist/next/contracts/evolve.js +179 -0
- package/dist/next/contracts/evolve.js.map +1 -0
- package/dist/next/contracts/fingerprint.d.ts +16 -0
- package/dist/next/contracts/fingerprint.d.ts.map +1 -0
- package/dist/next/contracts/fingerprint.js +50 -0
- package/dist/next/contracts/fingerprint.js.map +1 -0
- package/dist/next/contracts/history.d.ts +71 -0
- package/dist/next/contracts/history.d.ts.map +1 -0
- package/dist/next/contracts/history.js +3 -0
- package/dist/next/contracts/history.js.map +1 -0
- package/dist/next/contracts/host-kind.d.ts +40 -0
- package/dist/next/contracts/host-kind.d.ts.map +1 -0
- package/dist/next/contracts/host-kind.js +37 -0
- package/dist/next/contracts/host-kind.js.map +1 -0
- package/dist/next/contracts/index.d.ts +7 -0
- package/dist/next/contracts/index.d.ts.map +1 -0
- package/dist/next/contracts/index.js +6 -0
- package/dist/next/contracts/index.js.map +1 -0
- package/dist/next/contracts/internal.d.ts +10 -0
- package/dist/next/contracts/internal.d.ts.map +1 -0
- package/dist/next/contracts/internal.js +9 -0
- package/dist/next/contracts/internal.js.map +1 -0
- package/dist/next/contracts/names.d.ts +3 -0
- package/dist/next/contracts/names.d.ts.map +1 -0
- package/dist/next/contracts/names.js +13 -0
- package/dist/next/contracts/names.js.map +1 -0
- package/dist/next/contracts/params.d.ts +28 -0
- package/dist/next/contracts/params.d.ts.map +1 -0
- package/dist/next/contracts/params.js +50 -0
- package/dist/next/contracts/params.js.map +1 -0
- package/dist/next/contracts/schema.d.ts +102 -0
- package/dist/next/contracts/schema.d.ts.map +1 -0
- package/dist/next/contracts/schema.js +103 -0
- package/dist/next/contracts/schema.js.map +1 -0
- package/dist/next/contracts/snapshot.d.ts +16 -0
- package/dist/next/contracts/snapshot.d.ts.map +1 -0
- package/dist/next/contracts/snapshot.js +167 -0
- package/dist/next/contracts/snapshot.js.map +1 -0
- package/dist/next/data/args.d.ts +50 -0
- package/dist/next/data/args.d.ts.map +1 -0
- package/dist/next/data/args.js +83 -0
- package/dist/next/data/args.js.map +1 -0
- package/dist/next/data/errors.d.ts +73 -0
- package/dist/next/data/errors.d.ts.map +1 -0
- package/dist/next/data/errors.js +107 -0
- package/dist/next/data/errors.js.map +1 -0
- package/dist/next/data/failure.d.ts +26 -0
- package/dist/next/data/failure.d.ts.map +1 -0
- package/dist/next/data/failure.js +72 -0
- package/dist/next/data/failure.js.map +1 -0
- package/dist/next/data/index.d.ts +4 -0
- package/dist/next/data/index.d.ts.map +1 -0
- package/dist/next/data/index.js +4 -0
- package/dist/next/data/index.js.map +1 -0
- package/dist/next/data/internal.d.ts +7 -0
- package/dist/next/data/internal.d.ts.map +1 -0
- package/dist/next/data/internal.js +8 -0
- package/dist/next/data/internal.js.map +1 -0
- package/dist/next/data/params.d.ts +20 -0
- package/dist/next/data/params.d.ts.map +1 -0
- package/dist/next/data/params.js +31 -0
- package/dist/next/data/params.js.map +1 -0
- package/dist/next/data/principal.d.ts +17 -0
- package/dist/next/data/principal.d.ts.map +1 -0
- package/dist/next/data/principal.js +41 -0
- package/dist/next/data/principal.js.map +1 -0
- package/dist/next/data/validator.d.ts +12 -0
- package/dist/next/data/validator.d.ts.map +1 -0
- package/dist/next/data/validator.js +34 -0
- package/dist/next/data/validator.js.map +1 -0
- package/dist/next/documents/address-source.d.ts +20 -0
- package/dist/next/documents/address-source.d.ts.map +1 -0
- package/dist/next/documents/address-source.js +44 -0
- package/dist/next/documents/address-source.js.map +1 -0
- package/dist/next/documents/definition.d.ts +84 -0
- package/dist/next/documents/definition.d.ts.map +1 -0
- package/dist/next/documents/definition.js +73 -0
- package/dist/next/documents/definition.js.map +1 -0
- package/dist/next/documents/documents.d.ts +106 -0
- package/dist/next/documents/documents.d.ts.map +1 -0
- package/dist/next/documents/documents.js +224 -0
- package/dist/next/documents/documents.js.map +1 -0
- package/dist/next/documents/fence.d.ts +9 -0
- package/dist/next/documents/fence.d.ts.map +1 -0
- package/dist/next/documents/fence.js +15 -0
- package/dist/next/documents/fence.js.map +1 -0
- package/dist/next/documents/handles.d.ts +76 -0
- package/dist/next/documents/handles.d.ts.map +1 -0
- package/dist/next/documents/handles.js +66 -0
- package/dist/next/documents/handles.js.map +1 -0
- package/dist/next/documents/index.d.ts +4 -0
- package/dist/next/documents/index.d.ts.map +1 -0
- package/dist/next/documents/index.js +3 -0
- package/dist/next/documents/index.js.map +1 -0
- package/dist/next/documents/internal.d.ts +9 -0
- package/dist/next/documents/internal.d.ts.map +1 -0
- package/dist/next/documents/internal.js +9 -0
- package/dist/next/documents/internal.js.map +1 -0
- package/dist/next/documents/keyed-slot.d.ts +103 -0
- package/dist/next/documents/keyed-slot.d.ts.map +1 -0
- package/dist/next/documents/keyed-slot.js +395 -0
- package/dist/next/documents/keyed-slot.js.map +1 -0
- package/dist/next/documents/line.d.ts +7 -0
- package/dist/next/documents/line.d.ts.map +1 -0
- package/dist/next/documents/line.js +14 -0
- package/dist/next/documents/line.js.map +1 -0
- package/dist/next/documents/persistence.d.ts +104 -0
- package/dist/next/documents/persistence.d.ts.map +1 -0
- package/dist/next/documents/persistence.js +128 -0
- package/dist/next/documents/persistence.js.map +1 -0
- package/dist/next/documents/residency.d.ts +31 -0
- package/dist/next/documents/residency.d.ts.map +1 -0
- package/dist/next/documents/residency.js +93 -0
- package/dist/next/documents/residency.js.map +1 -0
- package/dist/next/documents/slot.d.ts +109 -0
- package/dist/next/documents/slot.d.ts.map +1 -0
- package/dist/next/documents/slot.js +181 -0
- package/dist/next/documents/slot.js.map +1 -0
- package/dist/next/documents/transaction.d.ts +133 -0
- package/dist/next/documents/transaction.d.ts.map +1 -0
- package/dist/next/documents/transaction.js +409 -0
- package/dist/next/documents/transaction.js.map +1 -0
- package/dist/next/host/activity.d.ts +31 -0
- package/dist/next/host/activity.d.ts.map +1 -0
- package/dist/next/host/activity.js +40 -0
- package/dist/next/host/activity.js.map +1 -0
- package/dist/next/host/adapted.d.ts +49 -0
- package/dist/next/host/adapted.d.ts.map +1 -0
- package/dist/next/host/adapted.js +214 -0
- package/dist/next/host/adapted.js.map +1 -0
- package/dist/next/host/attempt.d.ts +278 -0
- package/dist/next/host/attempt.d.ts.map +1 -0
- package/dist/next/host/attempt.js +1072 -0
- package/dist/next/host/attempt.js.map +1 -0
- package/dist/next/host/backoff.d.ts +29 -0
- package/dist/next/host/backoff.d.ts.map +1 -0
- package/dist/next/host/backoff.js +69 -0
- package/dist/next/host/backoff.js.map +1 -0
- package/dist/next/host/ctx.d.ts +47 -0
- package/dist/next/host/ctx.d.ts.map +1 -0
- package/dist/next/host/ctx.js +108 -0
- package/dist/next/host/ctx.js.map +1 -0
- package/dist/next/host/deadline.d.ts +12 -0
- package/dist/next/host/deadline.d.ts.map +1 -0
- package/dist/next/host/deadline.js +40 -0
- package/dist/next/host/deadline.js.map +1 -0
- package/dist/next/host/dependencies.d.ts +65 -0
- package/dist/next/host/dependencies.d.ts.map +1 -0
- package/dist/next/host/dependencies.js +115 -0
- package/dist/next/host/dependencies.js.map +1 -0
- package/dist/next/host/destination.d.ts +48 -0
- package/dist/next/host/destination.d.ts.map +1 -0
- package/dist/next/host/destination.js +101 -0
- package/dist/next/host/destination.js.map +1 -0
- package/dist/next/host/facet.d.ts +79 -0
- package/dist/next/host/facet.d.ts.map +1 -0
- package/dist/next/host/facet.js +55 -0
- package/dist/next/host/facet.js.map +1 -0
- package/dist/next/host/follow.d.ts +52 -0
- package/dist/next/host/follow.d.ts.map +1 -0
- package/dist/next/host/follow.js +207 -0
- package/dist/next/host/follow.js.map +1 -0
- package/dist/next/host/host.d.ts +495 -0
- package/dist/next/host/host.d.ts.map +1 -0
- package/dist/next/host/host.js +1075 -0
- package/dist/next/host/host.js.map +1 -0
- package/dist/next/host/index.d.ts +17 -0
- package/dist/next/host/index.d.ts.map +1 -0
- package/dist/next/host/index.js +6 -0
- package/dist/next/host/index.js.map +1 -0
- package/dist/next/host/internal.d.ts +10 -0
- package/dist/next/host/internal.d.ts.map +1 -0
- package/dist/next/host/internal.js +8 -0
- package/dist/next/host/internal.js.map +1 -0
- package/dist/next/host/members.d.ts +50 -0
- package/dist/next/host/members.d.ts.map +1 -0
- package/dist/next/host/members.js +107 -0
- package/dist/next/host/members.js.map +1 -0
- package/dist/next/host/native.d.ts +55 -0
- package/dist/next/host/native.d.ts.map +1 -0
- package/dist/next/host/native.js +92 -0
- package/dist/next/host/native.js.map +1 -0
- package/dist/next/host/object.d.ts +290 -0
- package/dist/next/host/object.d.ts.map +1 -0
- package/dist/next/host/object.js +1127 -0
- package/dist/next/host/object.js.map +1 -0
- package/dist/next/host/parked.d.ts +23 -0
- package/dist/next/host/parked.d.ts.map +1 -0
- package/dist/next/host/parked.js +79 -0
- package/dist/next/host/parked.js.map +1 -0
- package/dist/next/host/presence.d.ts +34 -0
- package/dist/next/host/presence.d.ts.map +1 -0
- package/dist/next/host/presence.js +117 -0
- package/dist/next/host/presence.js.map +1 -0
- package/dist/next/host/ref-state.d.ts +53 -0
- package/dist/next/host/ref-state.d.ts.map +1 -0
- package/dist/next/host/ref-state.js +83 -0
- package/dist/next/host/ref-state.js.map +1 -0
- package/dist/next/host/reference-keys.d.ts +36 -0
- package/dist/next/host/reference-keys.d.ts.map +1 -0
- package/dist/next/host/reference-keys.js +168 -0
- package/dist/next/host/reference-keys.js.map +1 -0
- package/dist/next/host/reference-source.d.ts +65 -0
- package/dist/next/host/reference-source.d.ts.map +1 -0
- package/dist/next/host/reference-source.js +285 -0
- package/dist/next/host/reference-source.js.map +1 -0
- package/dist/next/host/reference.d.ts +308 -0
- package/dist/next/host/reference.d.ts.map +1 -0
- package/dist/next/host/reference.js +775 -0
- package/dist/next/host/reference.js.map +1 -0
- package/dist/next/host/retry-on.d.ts +28 -0
- package/dist/next/host/retry-on.d.ts.map +1 -0
- package/dist/next/host/retry-on.js +52 -0
- package/dist/next/host/retry-on.js.map +1 -0
- package/dist/next/host/revocation.d.ts +16 -0
- package/dist/next/host/revocation.d.ts.map +1 -0
- package/dist/next/host/revocation.js +27 -0
- package/dist/next/host/revocation.js.map +1 -0
- package/dist/next/host/root.d.ts +27 -0
- package/dist/next/host/root.d.ts.map +1 -0
- package/dist/next/host/root.js +68 -0
- package/dist/next/host/root.js.map +1 -0
- package/dist/next/host/route.d.ts +64 -0
- package/dist/next/host/route.d.ts.map +1 -0
- package/dist/next/host/route.js +190 -0
- package/dist/next/host/route.js.map +1 -0
- package/dist/next/host/routines.d.ts +76 -0
- package/dist/next/host/routines.d.ts.map +1 -0
- package/dist/next/host/routines.js +350 -0
- package/dist/next/host/routines.js.map +1 -0
- package/dist/next/host/sleep.d.ts +9 -0
- package/dist/next/host/sleep.d.ts.map +1 -0
- package/dist/next/host/sleep.js +40 -0
- package/dist/next/host/sleep.js.map +1 -0
- package/dist/next/host/staging.d.ts +94 -0
- package/dist/next/host/staging.d.ts.map +1 -0
- package/dist/next/host/staging.js +302 -0
- package/dist/next/host/staging.js.map +1 -0
- package/dist/next/host/unbound.d.ts +30 -0
- package/dist/next/host/unbound.d.ts.map +1 -0
- package/dist/next/host/unbound.js +67 -0
- package/dist/next/host/unbound.js.map +1 -0
- package/dist/next/host/uuid.d.ts +3 -0
- package/dist/next/host/uuid.d.ts.map +1 -0
- package/dist/next/host/uuid.js +38 -0
- package/dist/next/host/uuid.js.map +1 -0
- package/dist/next/index.d.ts +28 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +18 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/jobs/definition.d.ts +96 -0
- package/dist/next/jobs/definition.d.ts.map +1 -0
- package/dist/next/jobs/definition.js +83 -0
- package/dist/next/jobs/definition.js.map +1 -0
- package/dist/next/jobs/index.d.ts +5 -0
- package/dist/next/jobs/index.d.ts.map +1 -0
- package/dist/next/jobs/index.js +3 -0
- package/dist/next/jobs/index.js.map +1 -0
- package/dist/next/jobs/internal.d.ts +3 -0
- package/dist/next/jobs/internal.d.ts.map +1 -0
- package/dist/next/jobs/internal.js +4 -0
- package/dist/next/jobs/internal.js.map +1 -0
- package/dist/next/jobs/job.d.ts +152 -0
- package/dist/next/jobs/job.d.ts.map +1 -0
- package/dist/next/jobs/job.js +320 -0
- package/dist/next/jobs/job.js.map +1 -0
- package/dist/next/jobs/persisted.d.ts +38 -0
- package/dist/next/jobs/persisted.d.ts.map +1 -0
- package/dist/next/jobs/persisted.js +95 -0
- package/dist/next/jobs/persisted.js.map +1 -0
- package/dist/next/jobs/records.d.ts +231 -0
- package/dist/next/jobs/records.d.ts.map +1 -0
- package/dist/next/jobs/records.js +869 -0
- package/dist/next/jobs/records.js.map +1 -0
- package/dist/next/jobs/run-states.d.ts +69 -0
- package/dist/next/jobs/run-states.d.ts.map +1 -0
- package/dist/next/jobs/run-states.js +228 -0
- package/dist/next/jobs/run-states.js.map +1 -0
- package/dist/next/jobs/run.d.ts +113 -0
- package/dist/next/jobs/run.d.ts.map +1 -0
- package/dist/next/jobs/run.js +200 -0
- package/dist/next/jobs/run.js.map +1 -0
- package/dist/next/jobs/running.d.ts +44 -0
- package/dist/next/jobs/running.d.ts.map +1 -0
- package/dist/next/jobs/running.js +117 -0
- package/dist/next/jobs/running.js.map +1 -0
- package/dist/next/jobs/stored.d.ts +63 -0
- package/dist/next/jobs/stored.d.ts.map +1 -0
- package/dist/next/jobs/stored.js +55 -0
- package/dist/next/jobs/stored.js.map +1 -0
- package/dist/next/link/binary.d.ts +4 -0
- package/dist/next/link/binary.d.ts.map +1 -0
- package/dist/next/link/binary.js +534 -0
- package/dist/next/link/binary.js.map +1 -0
- package/dist/next/link/channel.d.ts +63 -0
- package/dist/next/link/channel.d.ts.map +1 -0
- package/dist/next/link/channel.js +202 -0
- package/dist/next/link/channel.js.map +1 -0
- package/dist/next/link/errors.d.ts +7 -0
- package/dist/next/link/errors.d.ts.map +1 -0
- package/dist/next/link/errors.js +21 -0
- package/dist/next/link/errors.js.map +1 -0
- package/dist/next/link/index.d.ts +8 -0
- package/dist/next/link/index.d.ts.map +1 -0
- package/dist/next/link/index.js +8 -0
- package/dist/next/link/index.js.map +1 -0
- package/dist/next/link/internal.d.ts +9 -0
- package/dist/next/link/internal.d.ts.map +1 -0
- package/dist/next/link/internal.js +10 -0
- package/dist/next/link/internal.js.map +1 -0
- package/dist/next/link/ipc.d.ts +22 -0
- package/dist/next/link/ipc.d.ts.map +1 -0
- package/dist/next/link/ipc.js +64 -0
- package/dist/next/link/ipc.js.map +1 -0
- package/dist/next/link/link.d.ts +34 -0
- package/dist/next/link/link.d.ts.map +1 -0
- package/dist/next/link/link.js +87 -0
- package/dist/next/link/link.js.map +1 -0
- package/dist/next/link/message.d.ts +249 -0
- package/dist/next/link/message.d.ts.map +1 -0
- package/dist/next/link/message.js +123 -0
- package/dist/next/link/message.js.map +1 -0
- package/dist/next/link/part.d.ts +80 -0
- package/dist/next/link/part.d.ts.map +1 -0
- package/dist/next/link/part.js +164 -0
- package/dist/next/link/part.js.map +1 -0
- package/dist/next/link/text.d.ts +6 -0
- package/dist/next/link/text.d.ts.map +1 -0
- package/dist/next/link/text.js +335 -0
- package/dist/next/link/text.js.map +1 -0
- package/dist/next/link/values.d.ts +40 -0
- package/dist/next/link/values.d.ts.map +1 -0
- package/dist/next/link/values.js +117 -0
- package/dist/next/link/values.js.map +1 -0
- package/dist/next/link/websocket.d.ts +26 -0
- package/dist/next/link/websocket.d.ts.map +1 -0
- package/dist/next/link/websocket.js +91 -0
- package/dist/next/link/websocket.js.map +1 -0
- package/dist/next/registries/contribution.d.ts +50 -0
- package/dist/next/registries/contribution.d.ts.map +1 -0
- package/dist/next/registries/contribution.js +129 -0
- package/dist/next/registries/contribution.js.map +1 -0
- package/dist/next/registries/entries.d.ts +18 -0
- package/dist/next/registries/entries.d.ts.map +1 -0
- package/dist/next/registries/entries.js +42 -0
- package/dist/next/registries/entries.js.map +1 -0
- package/dist/next/registries/index.d.ts +3 -0
- package/dist/next/registries/index.d.ts.map +1 -0
- package/dist/next/registries/index.js +5 -0
- package/dist/next/registries/index.js.map +1 -0
- package/dist/next/registries/internal.d.ts +4 -0
- package/dist/next/registries/internal.d.ts.map +1 -0
- package/dist/next/registries/internal.js +5 -0
- package/dist/next/registries/internal.js.map +1 -0
- package/dist/next/registries/record.d.ts +94 -0
- package/dist/next/registries/record.d.ts.map +1 -0
- package/dist/next/registries/record.js +253 -0
- package/dist/next/registries/record.js.map +1 -0
- package/dist/next/registries/registry.d.ts +74 -0
- package/dist/next/registries/registry.d.ts.map +1 -0
- package/dist/next/registries/registry.js +14 -0
- package/dist/next/registries/registry.js.map +1 -0
- package/dist/next/registries/value.d.ts +48 -0
- package/dist/next/registries/value.d.ts.map +1 -0
- package/dist/next/registries/value.js +199 -0
- package/dist/next/registries/value.js.map +1 -0
- package/dist/next/remote/calls.d.ts +25 -0
- package/dist/next/remote/calls.d.ts.map +1 -0
- package/dist/next/remote/calls.js +62 -0
- package/dist/next/remote/calls.js.map +1 -0
- package/dist/next/remote/consumer.d.ts +42 -0
- package/dist/next/remote/consumer.d.ts.map +1 -0
- package/dist/next/remote/consumer.js +343 -0
- package/dist/next/remote/consumer.js.map +1 -0
- package/dist/next/remote/hold.d.ts +130 -0
- package/dist/next/remote/hold.d.ts.map +1 -0
- package/dist/next/remote/hold.js +537 -0
- package/dist/next/remote/hold.js.map +1 -0
- package/dist/next/remote/host-link.d.ts +77 -0
- package/dist/next/remote/host-link.d.ts.map +1 -0
- package/dist/next/remote/host-link.js +182 -0
- package/dist/next/remote/host-link.js.map +1 -0
- package/dist/next/remote/internal.d.ts +6 -0
- package/dist/next/remote/internal.d.ts.map +1 -0
- package/dist/next/remote/internal.js +6 -0
- package/dist/next/remote/internal.js.map +1 -0
- package/dist/next/remote/path.d.ts +46 -0
- package/dist/next/remote/path.d.ts.map +1 -0
- package/dist/next/remote/path.js +73 -0
- package/dist/next/remote/path.js.map +1 -0
- package/dist/next/remote/provider.d.ts +76 -0
- package/dist/next/remote/provider.d.ts.map +1 -0
- package/dist/next/remote/provider.js +506 -0
- package/dist/next/remote/provider.js.map +1 -0
- package/dist/next/remote/relay.d.ts +58 -0
- package/dist/next/remote/relay.d.ts.map +1 -0
- package/dist/next/remote/relay.js +117 -0
- package/dist/next/remote/relay.js.map +1 -0
- package/dist/next/remote/remote-object.d.ts +133 -0
- package/dist/next/remote/remote-object.d.ts.map +1 -0
- package/dist/next/remote/remote-object.js +411 -0
- package/dist/next/remote/remote-object.js.map +1 -0
- package/dist/next/remote/replica.d.ts +59 -0
- package/dist/next/remote/replica.d.ts.map +1 -0
- package/dist/next/remote/replica.js +125 -0
- package/dist/next/remote/replica.js.map +1 -0
- package/dist/next/state/batch.d.ts +64 -0
- package/dist/next/state/batch.d.ts.map +1 -0
- package/dist/next/state/batch.js +163 -0
- package/dist/next/state/batch.js.map +1 -0
- package/dist/next/state/derive.d.ts +52 -0
- package/dist/next/state/derive.d.ts.map +1 -0
- package/dist/next/state/derive.js +182 -0
- package/dist/next/state/derive.js.map +1 -0
- package/dist/next/state/index.d.ts +6 -0
- package/dist/next/state/index.d.ts.map +1 -0
- package/dist/next/state/index.js +4 -0
- package/dist/next/state/index.js.map +1 -0
- package/dist/next/state/internal.d.ts +6 -0
- package/dist/next/state/internal.d.ts.map +1 -0
- package/dist/next/state/internal.js +8 -0
- package/dist/next/state/internal.js.map +1 -0
- package/dist/next/state/native-source.d.ts +106 -0
- package/dist/next/state/native-source.d.ts.map +1 -0
- package/dist/next/state/native-source.js +240 -0
- package/dist/next/state/native-source.js.map +1 -0
- package/dist/next/state/watchable.d.ts +114 -0
- package/dist/next/state/watchable.d.ts.map +1 -0
- package/dist/next/state/watchable.js +156 -0
- package/dist/next/state/watchable.js.map +1 -0
- package/dist/next/state/writable.d.ts +32 -0
- package/dist/next/state/writable.d.ts.map +1 -0
- package/dist/next/state/writable.js +49 -0
- package/dist/next/state/writable.js.map +1 -0
- package/dist/next/store/adapter.d.ts +67 -0
- package/dist/next/store/adapter.d.ts.map +1 -0
- package/dist/next/store/adapter.js +118 -0
- package/dist/next/store/adapter.js.map +1 -0
- package/dist/next/store/allocator.d.ts +17 -0
- package/dist/next/store/allocator.d.ts.map +1 -0
- package/dist/next/store/allocator.js +30 -0
- package/dist/next/store/allocator.js.map +1 -0
- package/dist/next/store/applier.d.ts +8 -0
- package/dist/next/store/applier.d.ts.map +1 -0
- package/dist/next/store/applier.js +375 -0
- package/dist/next/store/applier.js.map +1 -0
- package/dist/next/store/apply-batch.d.ts +12 -0
- package/dist/next/store/apply-batch.d.ts.map +1 -0
- package/dist/next/store/apply-batch.js +83 -0
- package/dist/next/store/apply-batch.js.map +1 -0
- package/dist/next/store/archive.d.ts +32 -0
- package/dist/next/store/archive.d.ts.map +1 -0
- package/dist/next/store/archive.js +129 -0
- package/dist/next/store/archive.js.map +1 -0
- package/dist/next/store/backend.d.ts +10 -0
- package/dist/next/store/backend.d.ts.map +1 -0
- package/dist/next/store/backend.js +15 -0
- package/dist/next/store/backend.js.map +1 -0
- package/dist/next/store/catalog.d.ts +24 -0
- package/dist/next/store/catalog.d.ts.map +1 -0
- package/dist/next/store/catalog.js +17 -0
- package/dist/next/store/catalog.js.map +1 -0
- package/dist/next/store/claims.d.ts +23 -0
- package/dist/next/store/claims.d.ts.map +1 -0
- package/dist/next/store/claims.js +65 -0
- package/dist/next/store/claims.js.map +1 -0
- package/dist/next/store/columns.d.ts +77 -0
- package/dist/next/store/columns.d.ts.map +1 -0
- package/dist/next/store/columns.js +77 -0
- package/dist/next/store/columns.js.map +1 -0
- package/dist/next/store/engine.d.ts +176 -0
- package/dist/next/store/engine.d.ts.map +1 -0
- package/dist/next/store/engine.js +283 -0
- package/dist/next/store/engine.js.map +1 -0
- package/dist/next/store/evaluate.d.ts +17 -0
- package/dist/next/store/evaluate.d.ts.map +1 -0
- package/dist/next/store/evaluate.js +128 -0
- package/dist/next/store/evaluate.js.map +1 -0
- package/dist/next/store/export.d.ts +17 -0
- package/dist/next/store/export.d.ts.map +1 -0
- package/dist/next/store/export.js +200 -0
- package/dist/next/store/export.js.map +1 -0
- package/dist/next/store/expression.d.ts +150 -0
- package/dist/next/store/expression.d.ts.map +1 -0
- package/dist/next/store/expression.js +268 -0
- package/dist/next/store/expression.js.map +1 -0
- package/dist/next/store/import.d.ts +10 -0
- package/dist/next/store/import.d.ts.map +1 -0
- package/dist/next/store/import.js +276 -0
- package/dist/next/store/import.js.map +1 -0
- package/dist/next/store/index.d.ts +16 -0
- package/dist/next/store/index.d.ts.map +1 -0
- package/dist/next/store/index.js +13 -0
- package/dist/next/store/index.js.map +1 -0
- package/dist/next/store/json-column.d.ts +33 -0
- package/dist/next/store/json-column.d.ts.map +1 -0
- package/dist/next/store/json-column.js +61 -0
- package/dist/next/store/json-column.js.map +1 -0
- package/dist/next/store/jsonl.d.ts +9 -0
- package/dist/next/store/jsonl.d.ts.map +1 -0
- package/dist/next/store/jsonl.js +61 -0
- package/dist/next/store/jsonl.js.map +1 -0
- package/dist/next/store/outbox.d.ts +26 -0
- package/dist/next/store/outbox.d.ts.map +1 -0
- package/dist/next/store/outbox.js +80 -0
- package/dist/next/store/outbox.js.map +1 -0
- package/dist/next/store/query.d.ts +39 -0
- package/dist/next/store/query.d.ts.map +1 -0
- package/dist/next/store/query.js +97 -0
- package/dist/next/store/query.js.map +1 -0
- package/dist/next/store/refusal.d.ts +15 -0
- package/dist/next/store/refusal.d.ts.map +1 -0
- package/dist/next/store/refusal.js +36 -0
- package/dist/next/store/refusal.js.map +1 -0
- package/dist/next/store/rows.d.ts +80 -0
- package/dist/next/store/rows.d.ts.map +1 -0
- package/dist/next/store/rows.js +183 -0
- package/dist/next/store/rows.js.map +1 -0
- package/dist/next/store/schema.d.ts +25 -0
- package/dist/next/store/schema.d.ts.map +1 -0
- package/dist/next/store/schema.js +115 -0
- package/dist/next/store/schema.js.map +1 -0
- package/dist/next/store/sql.d.ts +73 -0
- package/dist/next/store/sql.d.ts.map +1 -0
- package/dist/next/store/sql.js +234 -0
- package/dist/next/store/sql.js.map +1 -0
- package/dist/next/store/step.d.ts +48 -0
- package/dist/next/store/step.d.ts.map +1 -0
- package/dist/next/store/step.js +50 -0
- package/dist/next/store/step.js.map +1 -0
- package/dist/next/store/store.d.ts +161 -0
- package/dist/next/store/store.d.ts.map +1 -0
- package/dist/next/store/store.js +627 -0
- package/dist/next/store/store.js.map +1 -0
- package/dist/next/store/table-changes.d.ts +29 -0
- package/dist/next/store/table-changes.d.ts.map +1 -0
- package/dist/next/store/table-changes.js +102 -0
- package/dist/next/store/table-changes.js.map +1 -0
- package/dist/next/store/table-evolve.d.ts +74 -0
- package/dist/next/store/table-evolve.d.ts.map +1 -0
- package/dist/next/store/table-evolve.js +201 -0
- package/dist/next/store/table-evolve.js.map +1 -0
- package/dist/next/store/table-handle.d.ts +67 -0
- package/dist/next/store/table-handle.d.ts.map +1 -0
- package/dist/next/store/table-handle.js +101 -0
- package/dist/next/store/table-handle.js.map +1 -0
- package/dist/next/store/table-history.d.ts +75 -0
- package/dist/next/store/table-history.d.ts.map +1 -0
- package/dist/next/store/table-history.js +42 -0
- package/dist/next/store/table-history.js.map +1 -0
- package/dist/next/store/table-migration.d.ts +16 -0
- package/dist/next/store/table-migration.d.ts.map +1 -0
- package/dist/next/store/table-migration.js +202 -0
- package/dist/next/store/table-migration.js.map +1 -0
- package/dist/next/store/table-writes.d.ts +125 -0
- package/dist/next/store/table-writes.d.ts.map +1 -0
- package/dist/next/store/table-writes.js +165 -0
- package/dist/next/store/table-writes.js.map +1 -0
- package/dist/next/store/table.d.ts +80 -0
- package/dist/next/store/table.d.ts.map +1 -0
- package/dist/next/store/table.js +103 -0
- package/dist/next/store/table.js.map +1 -0
- package/dist/next/store/text.d.ts +14 -0
- package/dist/next/store/text.d.ts.map +1 -0
- package/dist/next/store/text.js +51 -0
- package/dist/next/store/text.js.map +1 -0
- package/dist/next/store/writes.d.ts +5 -0
- package/dist/next/store/writes.d.ts.map +1 -0
- package/dist/next/store/writes.js +4 -0
- package/dist/next/store/writes.js.map +1 -0
- package/dist/next-node/compression.d.ts +7 -0
- package/dist/next-node/compression.d.ts.map +1 -0
- package/dist/next-node/compression.js +24 -0
- package/dist/next-node/compression.js.map +1 -0
- package/dist/next-node/http/admission.d.ts +63 -0
- package/dist/next-node/http/admission.d.ts.map +1 -0
- package/dist/next-node/http/admission.js +134 -0
- package/dist/next-node/http/admission.js.map +1 -0
- package/dist/next-node/http/authenticate.d.ts +21 -0
- package/dist/next-node/http/authenticate.d.ts.map +1 -0
- package/dist/next-node/http/authenticate.js +34 -0
- package/dist/next-node/http/authenticate.js.map +1 -0
- package/dist/next-node/http/cookies.d.ts +24 -0
- package/dist/next-node/http/cookies.d.ts.map +1 -0
- package/dist/next-node/http/cookies.js +62 -0
- package/dist/next-node/http/cookies.js.map +1 -0
- package/dist/next-node/http/file.d.ts +23 -0
- package/dist/next-node/http/file.d.ts.map +1 -0
- package/dist/next-node/http/file.js +94 -0
- package/dist/next-node/http/file.js.map +1 -0
- package/dist/next-node/http/headers.d.ts +37 -0
- package/dist/next-node/http/headers.d.ts.map +1 -0
- package/dist/next-node/http/headers.js +129 -0
- package/dist/next-node/http/headers.js.map +1 -0
- package/dist/next-node/http/http.d.ts +100 -0
- package/dist/next-node/http/http.d.ts.map +1 -0
- package/dist/next-node/http/http.js +462 -0
- package/dist/next-node/http/http.js.map +1 -0
- package/dist/next-node/http/incoming.d.ts +19 -0
- package/dist/next-node/http/incoming.d.ts.map +1 -0
- package/dist/next-node/http/incoming.js +31 -0
- package/dist/next-node/http/incoming.js.map +1 -0
- package/dist/next-node/http/index.d.ts +11 -0
- package/dist/next-node/http/index.d.ts.map +1 -0
- package/dist/next-node/http/index.js +12 -0
- package/dist/next-node/http/index.js.map +1 -0
- package/dist/next-node/http/node.d.ts +78 -0
- package/dist/next-node/http/node.d.ts.map +1 -0
- package/dist/next-node/http/node.js +291 -0
- package/dist/next-node/http/node.js.map +1 -0
- package/dist/next-node/http/request.d.ts +54 -0
- package/dist/next-node/http/request.d.ts.map +1 -0
- package/dist/next-node/http/request.js +116 -0
- package/dist/next-node/http/request.js.map +1 -0
- package/dist/next-node/http/response.d.ts +63 -0
- package/dist/next-node/http/response.d.ts.map +1 -0
- package/dist/next-node/http/response.js +202 -0
- package/dist/next-node/http/response.js.map +1 -0
- package/dist/next-node/http/router.d.ts +114 -0
- package/dist/next-node/http/router.d.ts.map +1 -0
- package/dist/next-node/http/router.js +261 -0
- package/dist/next-node/http/router.js.map +1 -0
- package/dist/next-node/http/upgrades.d.ts +58 -0
- package/dist/next-node/http/upgrades.d.ts.map +1 -0
- package/dist/next-node/http/upgrades.js +184 -0
- package/dist/next-node/http/upgrades.js.map +1 -0
- package/dist/next-node/plugins/build.d.ts +57 -0
- package/dist/next-node/plugins/build.d.ts.map +1 -0
- package/dist/next-node/plugins/build.js +334 -0
- package/dist/next-node/plugins/build.js.map +1 -0
- package/dist/next-node/plugins/discover.d.ts +36 -0
- package/dist/next-node/plugins/discover.d.ts.map +1 -0
- package/dist/next-node/plugins/discover.js +91 -0
- package/dist/next-node/plugins/discover.js.map +1 -0
- package/dist/next-node/plugins/evaluate.d.ts +33 -0
- package/dist/next-node/plugins/evaluate.d.ts.map +1 -0
- package/dist/next-node/plugins/evaluate.js +98 -0
- package/dist/next-node/plugins/evaluate.js.map +1 -0
- package/dist/next-node/plugins/index.d.ts +9 -0
- package/dist/next-node/plugins/index.d.ts.map +1 -0
- package/dist/next-node/plugins/index.js +11 -0
- package/dist/next-node/plugins/index.js.map +1 -0
- package/dist/next-node/plugins/install.d.ts +13 -0
- package/dist/next-node/plugins/install.d.ts.map +1 -0
- package/dist/next-node/plugins/install.js +37 -0
- package/dist/next-node/plugins/install.js.map +1 -0
- package/dist/next-node/plugins/plugins.d.ts +135 -0
- package/dist/next-node/plugins/plugins.d.ts.map +1 -0
- package/dist/next-node/plugins/plugins.js +206 -0
- package/dist/next-node/plugins/plugins.js.map +1 -0
- package/dist/next-node/plugins/runtime.d.ts +41 -0
- package/dist/next-node/plugins/runtime.d.ts.map +1 -0
- package/dist/next-node/plugins/runtime.js +136 -0
- package/dist/next-node/plugins/runtime.js.map +1 -0
- package/dist/next-node/plugins/stack.d.ts +16 -0
- package/dist/next-node/plugins/stack.d.ts.map +1 -0
- package/dist/next-node/plugins/stack.js +61 -0
- package/dist/next-node/plugins/stack.js.map +1 -0
- package/dist/next-node/plugins/watcher.d.ts +58 -0
- package/dist/next-node/plugins/watcher.d.ts.map +1 -0
- package/dist/next-node/plugins/watcher.js +281 -0
- package/dist/next-node/plugins/watcher.js.map +1 -0
- package/dist/next-node/workers/control.d.ts +83 -0
- package/dist/next-node/workers/control.d.ts.map +1 -0
- package/dist/next-node/workers/control.js +77 -0
- package/dist/next-node/workers/control.js.map +1 -0
- package/dist/next-node/workers/index.d.ts +5 -0
- package/dist/next-node/workers/index.d.ts.map +1 -0
- package/dist/next-node/workers/index.js +7 -0
- package/dist/next-node/workers/index.js.map +1 -0
- package/dist/next-node/workers/lock.d.ts +21 -0
- package/dist/next-node/workers/lock.d.ts.map +1 -0
- package/dist/next-node/workers/lock.js +137 -0
- package/dist/next-node/workers/lock.js.map +1 -0
- package/dist/next-node/workers/main.d.ts +46 -0
- package/dist/next-node/workers/main.d.ts.map +1 -0
- package/dist/next-node/workers/main.js +230 -0
- package/dist/next-node/workers/main.js.map +1 -0
- package/dist/next-node/workers/process.d.ts +42 -0
- package/dist/next-node/workers/process.d.ts.map +1 -0
- package/dist/next-node/workers/process.js +125 -0
- package/dist/next-node/workers/process.js.map +1 -0
- package/dist/next-node/workers/workers.d.ts +80 -0
- package/dist/next-node/workers/workers.d.ts.map +1 -0
- package/dist/next-node/workers/workers.js +572 -0
- package/dist/next-node/workers/workers.js.map +1 -0
- package/dist/object.d.ts +4 -4
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +3 -4
- package/dist/object.js.map +1 -1
- package/dist/output.d.ts +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- package/dist/output.js.map +1 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +2 -2
- package/dist/remote.js.map +1 -1
- package/dist/schema.d.ts +8 -4
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +180 -5
- package/dist/schema.js.map +1 -1
- package/dist/sqlite/cloudflare.d.ts +2 -0
- package/dist/sqlite/cloudflare.d.ts.map +1 -1
- package/dist/sqlite/cloudflare.js +10 -0
- package/dist/sqlite/cloudflare.js.map +1 -1
- package/dist/sqlite/database.d.ts +5 -2
- package/dist/sqlite/database.d.ts.map +1 -1
- package/dist/sqlite/database.js.map +1 -1
- package/dist/sqlite/node.d.ts +4 -1
- package/dist/sqlite/node.d.ts.map +1 -1
- package/dist/sqlite/node.js +22 -5
- package/dist/sqlite/node.js.map +1 -1
- package/dist/watch.d.ts +194 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +383 -0
- package/dist/watch.js.map +1 -0
- package/dist/wire.d.ts +174 -193
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +81 -80
- package/dist/wire.js.map +1 -1
- package/docs/guide.md +15 -15
- package/docs/next/benchmarks.md +959 -0
- package/docs/next/decisions.md +2957 -0
- package/docs/next/dependencies/book.md +1671 -0
- package/docs/next/dependencies/index.html +2641 -0
- package/docs/next/dependencies/model.md +330 -0
- package/docs/next/design.md +2554 -0
- package/docs/next/durable-observers/book.md +432 -0
- package/docs/next/durable-observers/index.html +517 -0
- package/docs/next/flow-control/book.md +333 -0
- package/docs/next/flow-control/fold/index.html +265 -0
- package/docs/next/flow-control/index.html +548 -0
- package/docs/next/flow-control/window/index.html +228 -0
- package/docs/next/guide-plugins-hosts/book.md +607 -0
- package/docs/next/guide-plugins-hosts/index.html +652 -0
- package/docs/next/http-workers/book.md +378 -0
- package/docs/next/http-workers/index.html +515 -0
- package/docs/next/layer-1.md +311 -0
- package/docs/next/layer-2.md +216 -0
- package/docs/next/plugin-model/book.md +186 -0
- package/docs/next/plugin-model/index.html +310 -0
- package/docs/next/plugin-primer/book.md +273 -0
- package/docs/next/plugin-primer/index.html +430 -0
- package/docs/next/slice-1.md +236 -0
- package/docs/next/slice-10.md +285 -0
- package/docs/next/slice-11.md +93 -0
- package/docs/next/slice-2.md +174 -0
- package/docs/next/slice-3a.md +296 -0
- package/docs/next/slice-3b.md +197 -0
- package/docs/next/slice-4.md +146 -0
- package/docs/next/slice-4a.md +418 -0
- package/docs/next/slice-4b.md +208 -0
- package/docs/next/slice-5.md +281 -0
- package/docs/next/slice-6a.md +676 -0
- package/docs/next/slice-6b.md +332 -0
- package/docs/next/slice-7a.md +130 -0
- package/docs/next/slice-7b/book.md +608 -0
- package/docs/next/slice-7b/index.html +842 -0
- package/docs/next/slice-7b.md +255 -0
- package/docs/next/slice-7c.md +165 -0
- package/docs/next/slice-8.md +194 -0
- package/docs/next/slices.md +230 -0
- package/docs/next/store/book.md +665 -0
- package/docs/next/store/index.html +861 -0
- package/docs/next/store/walkthrough.md +193 -0
- package/docs/spec.md +9 -9
- package/docs/tutorial.md +26 -25
- package/package.json +22 -6
- package/src/delta/README.md +69 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
3
|
+
<title>Flow control for Chord links</title>
|
|
4
|
+
<style>
|
|
5
|
+
|
|
6
|
+
:root{--paper:#fbf8f2;--ink:#2a2723;--muted:#7b7468;--rule:#e3dbcc;--accent:#b4472f;--blue:#2e6a8a;--sand:#c2b18c;--soft:#f4eee2}
|
|
7
|
+
*{box-sizing:border-box}
|
|
8
|
+
html{background:#efe9dd}
|
|
9
|
+
body{margin:0;color:var(--ink);font:19px/1.68 'Iowan Old Style','Palatino Linotype',Palatino,'Book Antiqua',Georgia,serif;
|
|
10
|
+
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
|
|
11
|
+
.book{max-width:860px;margin:0 auto;background:var(--paper);box-shadow:0 0 0 1px #e1d9c8,0 30px 80px -40px rgba(60,40,10,.35);padding:0 0 4rem}
|
|
12
|
+
.inner{padding:0 90px}
|
|
13
|
+
h1,h2,h3{font-weight:400;line-height:1.15}
|
|
14
|
+
h2{font-size:2.1rem;margin:.2em 0 .3em;letter-spacing:-.01em}
|
|
15
|
+
h3{font-size:1.05rem;text-transform:uppercase;letter-spacing:.14em;margin:2.4em 0 .6em;color:var(--ink);font-weight:600;
|
|
16
|
+
font-family:'Avenir Next','Helvetica Neue',sans-serif}
|
|
17
|
+
p{margin:0 0 1.05em;hyphens:auto;text-align:left}
|
|
18
|
+
code{font:.8em/1 'SF Mono',Menlo,Consolas,monospace;background:#f1ebde;padding:.1em .35em;border-radius:3px}
|
|
19
|
+
pre{font:.74rem/1.55 'SF Mono',Menlo,Consolas,monospace;background:#f4eee2;border-left:3px solid var(--sand);padding:1rem 1.2rem;margin:1.4rem 0 1.8rem;
|
|
20
|
+
overflow-x:auto;border-radius:0 4px 4px 0;white-space:pre;tab-size:2}
|
|
21
|
+
pre code{background:none;padding:0;font-size:1em}
|
|
22
|
+
em{font-style:italic}
|
|
23
|
+
a{color:var(--blue)}
|
|
24
|
+
.cover{min-height:92vh;display:flex;flex-direction:column;justify-content:center;padding:90px;background:
|
|
25
|
+
radial-gradient(circle at 85% 15%,rgba(180,71,47,.13),transparent 40%),radial-gradient(circle at 10% 90%,rgba(46,106,138,.12),transparent 45%),var(--paper);
|
|
26
|
+
border-bottom:1px solid var(--rule)}
|
|
27
|
+
.kicker{font:600 .78rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.22em;text-transform:uppercase;color:var(--accent)}
|
|
28
|
+
.cover h1{font-size:5.2rem;line-height:.98;margin:.35em 0 .25em;letter-spacing:-.025em}
|
|
29
|
+
.subtitle{font-size:1.45rem;font-style:italic;color:#4a453e;max-width:30em;text-align:left}
|
|
30
|
+
.cover-stats,.stats{display:flex;flex-wrap:wrap;gap:1.2rem 2.4rem;margin:2.2rem 0}
|
|
31
|
+
.stats{margin:1.6rem 0 1.8rem;padding:1.1rem 0;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
|
|
32
|
+
.stat b{display:block;font-size:2.1rem;font-weight:400;line-height:1.1;font-variant-numeric:lining-nums}
|
|
33
|
+
.stat span{font:.78rem/1.3 'Avenir Next','Helvetica Neue',sans-serif;color:var(--muted);letter-spacing:.03em;display:block;max-width:14em}
|
|
34
|
+
.cover-note{font-size:.9rem;color:var(--muted);max-width:34em;text-align:left;margin-top:2rem}
|
|
35
|
+
.toc{padding:4rem 0 2rem}
|
|
36
|
+
.toc ol{list-style:none;padding:0;margin:1rem 0;columns:2;column-gap:3rem}
|
|
37
|
+
.toc li{padding:.3rem 0;border-bottom:1px dotted var(--rule);break-inside:avoid}
|
|
38
|
+
.toc a{color:var(--ink);text-decoration:none}
|
|
39
|
+
.toc a:hover{color:var(--accent)}
|
|
40
|
+
.preface{padding-top:2rem}
|
|
41
|
+
.findings{padding-left:1.4em}
|
|
42
|
+
.findings li{margin:.55em 0;padding-left:.3em}
|
|
43
|
+
.findings li::marker{color:var(--accent);font-weight:600}
|
|
44
|
+
.chapter{padding-top:4.5rem;margin-top:3rem;border-top:1px solid var(--rule)}
|
|
45
|
+
.opener{text-align:center;margin-bottom:2.6rem}
|
|
46
|
+
.numeral{font-size:4.2rem;color:var(--accent);line-height:1;font-variant:small-caps;letter-spacing:.05em}
|
|
47
|
+
.opener h2{font-size:2.5rem;margin:.25em 0}
|
|
48
|
+
.lede{font-style:italic;color:#5a544a;font-size:1.12rem;max-width:30em;margin:0 auto;text-align:center}
|
|
49
|
+
.dropcap::first-letter{float:left;font-size:4.1em;line-height:.82;padding:.06em .09em 0 0;color:var(--accent)}
|
|
50
|
+
.fig{margin:2rem 0 2.2rem}
|
|
51
|
+
.fig.wide{margin-left:-50px;margin-right:-50px}
|
|
52
|
+
.fig svg{display:block;margin:0 auto}
|
|
53
|
+
figcaption{font:.8rem/1.45 'Avenir Next','Helvetica Neue',sans-serif;color:var(--muted);margin-top:.7rem;padding-left:.8rem;border-left:2px solid var(--rule)}
|
|
54
|
+
.fignum{color:var(--ink);font-weight:600}
|
|
55
|
+
.note{font:.86rem/1.55 'Avenir Next','Helvetica Neue',sans-serif;color:#5b5549;background:#f4eee2;border-left:3px solid var(--sand);
|
|
56
|
+
padding:.9rem 1.1rem;margin:1.8rem 0;border-radius:0 4px 4px 0}
|
|
57
|
+
.note.review{border-left-color:var(--accent)}
|
|
58
|
+
.note b.tag{font-weight:600;letter-spacing:.12em;text-transform:uppercase;font-size:.72rem;color:var(--accent);display:block;margin-bottom:.3rem}
|
|
59
|
+
.note code{font-size:.85em}
|
|
60
|
+
table{width:100%;border-collapse:collapse;margin:1.4rem 0 1.8rem;font:.8rem/1.4 'Avenir Next','Helvetica Neue',sans-serif}
|
|
61
|
+
th{text-align:left;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;font-size:.68rem;border-bottom:1.5px solid var(--ink);padding:.45rem .5rem}
|
|
62
|
+
td{padding:.42rem .5rem;border-bottom:1px solid var(--rule);vertical-align:top}
|
|
63
|
+
td code{font-size:.92em}
|
|
64
|
+
table.num td:not(:first-child),table.num th:not(:first-child){text-align:right}
|
|
65
|
+
.epilogue p{font-size:1.04em}
|
|
66
|
+
.appendix{font-size:.92em}
|
|
67
|
+
.appendix ul,.chapter ul{padding-left:1.2em}
|
|
68
|
+
.appendix li,.chapter li{margin:.4em 0}
|
|
69
|
+
.colophon{text-align:center;font:.78rem 'Avenir Next','Helvetica Neue',sans-serif;color:var(--muted);margin-top:4rem;letter-spacing:.05em}
|
|
70
|
+
@media (max-width:760px){.inner{padding:0 22px}.cover{padding:40px 22px}.cover h1{font-size:3.2rem}.fig.wide{margin-left:0;margin-right:0}
|
|
71
|
+
.toc ol{columns:1}p{text-align:left}body{font-size:17px}}
|
|
72
|
+
@media print{html{background:#fff}.book{box-shadow:none}.chapter{break-before:page}.fig{break-inside:avoid}}
|
|
73
|
+
.transcript p b{font:600 .78rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.08em;text-transform:uppercase;color:var(--accent);margin-right:.3em}
|
|
74
|
+
pre .tk-c{color:#8d8576;font-style:italic}pre .tk-s{color:#5f7a3d}pre .tk-n{color:#a0662a}pre .tk-k{color:#b4472f}pre .tk-t{color:#2e6a8a}pre .tk-f{color:#7a3f6a}
|
|
75
|
+
h4{font:600 .82rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.06em;margin:1.8em 0 .5em;color:var(--muted)}
|
|
76
|
+
.inner ul,.inner ol:not(.findings){padding-left:1.2em}
|
|
77
|
+
.inner li{margin:.35em 0}
|
|
78
|
+
blockquote{margin:1.4rem 0;padding:.2rem 0 .2rem 1.2rem;border-left:3px solid var(--rule);color:#5a544a;font-style:italic}
|
|
79
|
+
.fig svg text{dominant-baseline:auto}
|
|
80
|
+
.fig .replay{font:600 .7rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.1em;text-transform:uppercase;color:var(--accent);
|
|
81
|
+
background:none;border:1px solid var(--rule);border-radius:3px;padding:.2rem .55rem;margin:0 0 0 1rem;cursor:pointer;float:right}
|
|
82
|
+
.fig .replay:hover{border-color:var(--accent)}
|
|
83
|
+
.anim.armed .step{opacity:0}
|
|
84
|
+
.anim.armed.play .step{animation:stepIn .45s ease-out both;animation-delay:calc(var(--i) * var(--pace, .45s))}
|
|
85
|
+
.anim.armed.play .step.edge{animation-name:edgeIn}
|
|
86
|
+
@keyframes stepIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
87
|
+
@keyframes edgeIn{from{opacity:0}to{opacity:1}}
|
|
88
|
+
.steps{margin:2rem 0 2.2rem;border:1px solid var(--rule);border-radius:6px;background:#fffdf8}
|
|
89
|
+
.steps-head{display:flex;align-items:center;gap:.8rem;padding:.6rem 1rem;border-bottom:1px solid var(--rule);
|
|
90
|
+
font:600 .72rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.12em;text-transform:uppercase;color:var(--accent)}
|
|
91
|
+
.steps-head .title{flex:1}
|
|
92
|
+
.steps-head .count{color:var(--muted);letter-spacing:.06em}
|
|
93
|
+
.steps-head button{font:600 .72rem 'Avenir Next','Helvetica Neue',sans-serif;color:var(--ink);background:var(--soft);border:1px solid var(--rule);
|
|
94
|
+
border-radius:3px;padding:.25rem .65rem;cursor:pointer}
|
|
95
|
+
.steps-head button:disabled{opacity:.35;cursor:default}
|
|
96
|
+
.step-panel{padding:1rem 1.2rem .4rem}
|
|
97
|
+
.step-panel>h4:first-child{margin-top:.2rem;color:var(--ink)}
|
|
98
|
+
.steps.js .step-panel{display:none}
|
|
99
|
+
.steps.js .step-panel.on{display:block}
|
|
100
|
+
.steps:not(.js) .steps-head button,.steps:not(.js) .steps-head .count{display:none}
|
|
101
|
+
.steps:not(.js) .step-panel+.step-panel{border-top:1px dashed var(--rule)}
|
|
102
|
+
.fig figcaption{display:flow-root}
|
|
103
|
+
.fig svg{max-width:100%;height:auto}
|
|
104
|
+
table{max-width:100%}
|
|
105
|
+
@media (max-width:760px){.inner{padding:0 20px}.cover{padding:48px 22px}.cover h1{font-size:3.2rem}.fig.wide{margin-left:0;margin-right:0}table{display:block;overflow-x:auto}.toc ol{columns:1}}
|
|
106
|
+
</style>
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
</head>
|
|
110
|
+
<body><div class="book">
|
|
111
|
+
<section class="cover">
|
|
112
|
+
<div class="kicker">A design note · Chord v2 slice 4 · 2026-10-22</div>
|
|
113
|
+
<h1>Slow<br>Links</h1>
|
|
114
|
+
<p class="subtitle">How a phone on a bad train connection keeps seeing the newest state of a streamed reply, and why the transport's own backpressure cannot do that</p>
|
|
115
|
+
<div class="cover-stats"><div class="stat"><b>9.4 s</b><span>how old the newest state was with transport backpressure only</span></div><div class="stat"><b>0.0 s</b><span>with an acknowledged window of two messages</span></div><div class="stat"><b>345 ms</b><span>another client's p95 during a call flood, without a call window</span></div><div class="stat"><b>~200</b><span>lines for the converged design</span></div></div>
|
|
116
|
+
<div class="cover-note"><p>Written from the measurements and the cross-checked reports of the reviewers Astra and Fable (2026-10-22): Node 24.4.1,
|
|
117
|
+
<code>ws</code> 8.22, separate server and client processes, a throttling proxy at 64 KiB/s. The numbers are single synthetic
|
|
118
|
+
runs: they show failure modes, not production capacity. Relays: first decided as per-hop acknowledgements (b) on
|
|
119
|
+
2026-10-23, then replaced by flow control per origin on 2026-10-24 after the slice 4a benchmarks (chapter VI).</p>
|
|
120
|
+
</div>
|
|
121
|
+
</section><div class="inner">
|
|
122
|
+
<nav class="toc"><h2>Contents</h2><ol>
|
|
123
|
+
<li><a href="#before-we-begin">Before we begin</a></li>
|
|
124
|
+
<li><a href="#hidden">I. Where the Bytes Hide</a></li>
|
|
125
|
+
<li><a href="#acks">II. Acknowledged Messages</a></li>
|
|
126
|
+
<li><a href="#merge">III. Fold the Backlog</a></li>
|
|
127
|
+
<li><a href="#calls">IV. Calls Get Their Own Window</a></li>
|
|
128
|
+
<li><a href="#relays">V. Relays</a></li>
|
|
129
|
+
<li><a href="#what-changes-in-the-design">What Changes in the Design</a></li>
|
|
130
|
+
<li><a href="#measurements">Appendix A: Measurements</a></li>
|
|
131
|
+
</ol></nav>
|
|
132
|
+
|
|
133
|
+
<section class="preface" id="before-we-begin">
|
|
134
|
+
<h2>Before we begin</h2>
|
|
135
|
+
<p class="dropcap">Mario is on a train. His phone shows a p3 session in which the agent is writing a long reply. Every token is a small
|
|
136
|
+
state change: the worker commits it, the server relays it, the phone applies it. The train enters a tunnel and the
|
|
137
|
+
phone's link drops to a trickle, 64 KB per second. The worker keeps producing tokens at full speed.</p>
|
|
138
|
+
<p>Without flow control, every token still gets sent. The bytes pile up somewhere between the worker and the phone, and
|
|
139
|
+
when the train leaves the tunnel the phone has to work through a backlog of old tokens before it sees the current text.
|
|
140
|
+
It is not only slow: what it shows is minutes old, and nothing tells it so.</p>
|
|
141
|
+
<p>This note explains what keeps the phone current, why the obvious answer (let the transport push back) fails, and the
|
|
142
|
+
design Astra and Fable converged on after measuring it.</p>
|
|
143
|
+
<h3 id="six-things-to-know">Six things to know</h3>
|
|
144
|
+
<ol class="findings">
|
|
145
|
+
<li><strong>Transport backpressure arrives too late.</strong> The kernel's and a proxy's buffers take megabytes before a WebSocket
|
|
146
|
+
reports anything buffered; the phone sees state 4 to 10 seconds old.</li>
|
|
147
|
+
<li><strong>Acknowledgements keep state fresh.</strong> The receiving application acknowledges each message; at most two are in
|
|
148
|
+
flight; the newest state was 0.0 seconds old on the same 64 KB/s link.</li>
|
|
149
|
+
<li><strong>Count messages, not bytes.</strong> Each message carries everything that piled up since the last acknowledgement, so
|
|
150
|
+
throughput adapts to the link by itself.</li>
|
|
151
|
+
<li><strong>Merge ops first, snapshot last.</strong> 2,000 merged token appends cost 26 KB, sent one by one 46 KB; a snapshot of a
|
|
152
|
+
member with 500 KB of history would cost 508 KB.</li>
|
|
153
|
+
<li><strong>Calls need their own window.</strong> A tab firing 100,000 calls pushed another client's p95 to 345 ms; with a window of
|
|
154
|
+
64 respected by the caller it stayed at 0.66 ms and the flood waited in the buggy tab.</li>
|
|
155
|
+
<li><strong>Nothing is refused for being too many</strong> (decision K16): extra state is merged, extra calls wait.</li>
|
|
156
|
+
</ol>
|
|
157
|
+
<h3 id="words-used-throughout">Words used throughout</h3>
|
|
158
|
+
<p>A <strong>link</strong> connects two hosts. The <strong>provider</strong> owns an object and sends its state; the <strong>consumer</strong> holds a
|
|
159
|
+
reference to it. A <strong>hold</strong> is one reference over a link, with its own stream of <strong>ops</strong> (Delta's changes). A
|
|
160
|
+
<strong>snapshot</strong> is a hold's whole state. An <strong>acknowledgement</strong> (<code>ack</code>) is a tiny message the consumer sends after it
|
|
161
|
+
applied a state message. <strong>In flight</strong> means sent and not yet acknowledged. <strong>Coalescing</strong> means merging the ops that
|
|
162
|
+
wait to be sent. A <strong>relay</strong> is a host in the middle (the server between worker and tab).</p>
|
|
163
|
+
</section>
|
|
164
|
+
|
|
165
|
+
<section class="chapter" id="hidden"><header class="opener"><div class="numeral">I</div><h2>Where the Bytes Hide</h2><p class="lede">Why the sender cannot see a slow phone.</p></header>
|
|
166
|
+
<p class="dropcap">A WebSocket in Node reports <code>bufferedAmount</code>: bytes the library still holds. It looks like the perfect signal: stop
|
|
167
|
+
sending when it grows. The experiment says otherwise.</p>
|
|
168
|
+
<figure class="fig anim"><svg viewBox="0 0 720 307" width="100%" role="img" aria-label="A flood of state messages to a slow phone through a proxy. The sender's buffer stays small; the kernel's and the proxy's do not." xmlns="http://www.w3.org/2000/svg" style="font-family:'Avenir Next','Helvetica Neue',Helvetica,Arial,sans-serif;max-width:760px"><defs><marker id="f1m" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#7b7468"/></marker><marker id="f1a" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#b4472f"/></marker><marker id="f1b" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#2e6a8a"/></marker></defs><line x1="102" y1="52" x2="102" y2="299" stroke="#c9bfae" stroke-dasharray="3 3"/><rect x="23" y="10" width="158" height="42" rx="4" fill="#fff" stroke="#b4472f" stroke-width="1.5"/><text x="102" y="27" font-size="11" fill="#2a2723" text-anchor="middle">worker</text><text x="102" y="41" font-size="9.5" fill="#7b7468" text-anchor="middle">commits tokens</text><line x1="274" y1="52" x2="274" y2="299" stroke="#c9bfae" stroke-dasharray="3 3"/><rect x="195" y="10" width="158" height="42" rx="4" fill="#fff" stroke="#c9bfae"/><text x="274" y="27" font-size="11" fill="#2a2723" text-anchor="middle">server WebSocket</text><text x="274" y="41" font-size="9.5" fill="#7b7468" text-anchor="middle">bufferedAmount</text><line x1="446" y1="52" x2="446" y2="299" stroke="#c9bfae" stroke-dasharray="3 3"/><rect x="367" y="10" width="158" height="42" rx="4" fill="#efe9dd" stroke="#c9bfae"/><text x="446" y="30" font-size="11" fill="#2a2723" text-anchor="middle">kernel + proxy buffers</text><line x1="618" y1="52" x2="618" y2="299" stroke="#c9bfae" stroke-dasharray="3 3"/><rect x="539" y="10" width="158" height="42" rx="4" fill="#fff" stroke="#2e6a8a" stroke-width="1.5"/><text x="618" y="27" font-size="11" fill="#2a2723" text-anchor="middle">phone</text><text x="618" y="41" font-size="9.5" fill="#7b7468" text-anchor="middle">64 KB/s</text><g class="step edge" style="--i:1"><line x1="102" y1="78" x2="271" y2="78" stroke="#7b7468" marker-end="url(#f1m)"/><text x="188" y="72" font-size="10" fill="#2a2723" text-anchor="middle">token 1 … token 9,000</text></g><g class="step edge" style="--i:2"><line x1="274" y1="110" x2="443" y2="110" stroke="#7b7468" marker-end="url(#f1m)"/><text x="360" y="104" font-size="10" fill="#2a2723" text-anchor="middle">written at once (bufferedAmount stays near 0)</text></g><g class="step edge" style="--i:3"><line x1="446" y1="142" x2="615" y2="142" stroke="#7b7468" marker-end="url(#f1m)"/><text x="532" y="136" font-size="10" fill="#2a2723" text-anchor="middle">drains at 64 KB/s</text></g><g class="step" style="--i:4"><rect x="370" y="160" width="152" height="23" rx="3" fill="#f4eee2" stroke="#c2b18c"/><text x="446" y="174" font-size="10" fill="#5b5549" text-anchor="middle">megabytes waiting</text></g><g class="step edge" style="--i:5"><line x1="102" y1="211" x2="271" y2="211" stroke="#7b7468" marker-end="url(#f1m)"/><text x="188" y="205" font-size="10" fill="#2a2723" text-anchor="middle">token 9,001 (the newest)</text></g><g class="step edge" style="--i:6"><line x1="274" y1="243" x2="443" y2="243" stroke="#7b7468" marker-end="url(#f1m)"/><text x="360" y="237" font-size="10" fill="#2a2723" text-anchor="middle">queued behind everything</text></g><g class="step edge" style="--i:7"><line x1="446" y1="275" x2="615" y2="275" stroke="#7b7468" stroke-dasharray="4 3" marker-end="url(#f1m)"/><text x="532" y="269" font-size="10" fill="#2a2723" text-anchor="middle">arrives seconds later</text></g></svg><figcaption><button class="replay" type="button">Replay</button><span class="fignum">Figure 1.</span> A flood of state messages to a slow phone through a proxy. The sender's buffer stays small; the kernel's and the proxy's do not.</figcaption></figure>
|
|
169
|
+
<table>
|
|
170
|
+
<thead>
|
|
171
|
+
<tr>
|
|
172
|
+
<th>Flood, ~9,800 × 128 B per second, 64 KB/s link</th>
|
|
173
|
+
<th>Sender memory</th>
|
|
174
|
+
<th>Newest state the phone saw</th>
|
|
175
|
+
</tr>
|
|
176
|
+
</thead>
|
|
177
|
+
<tbody><tr>
|
|
178
|
+
<td>no flow control</td>
|
|
179
|
+
<td>7.0 MB queued in the WebSocket, heap +18.5 MB</td>
|
|
180
|
+
<td>9.4 s old</td>
|
|
181
|
+
</tr>
|
|
182
|
+
<tr>
|
|
183
|
+
<td>cap <code>bufferedAmount</code> at 64 KiB, merge pending state</td>
|
|
184
|
+
<td>+0.46 MB</td>
|
|
185
|
+
<td><strong>still 9.4 s old</strong></td>
|
|
186
|
+
</tr>
|
|
187
|
+
<tr>
|
|
188
|
+
<td>cap at 1 MiB (Fable's rerun)</td>
|
|
189
|
+
<td>small</td>
|
|
190
|
+
<td>3.8 s, growing to 10.4 s</td>
|
|
191
|
+
</tr>
|
|
192
|
+
<tr>
|
|
193
|
+
<td>at most 1 to 4 acknowledged messages in flight, merge pending state</td>
|
|
194
|
+
<td>+0.4 MB</td>
|
|
195
|
+
<td><strong>0.0 s</strong></td>
|
|
196
|
+
</tr>
|
|
197
|
+
</tbody></table>
|
|
198
|
+
<p>The transport's backpressure protects the sender's memory, but freshness depends on buffers the sender cannot see:
|
|
199
|
+
the operating system's socket buffer, autotuned to hundreds of kilobytes, and every proxy on the way. A normal
|
|
200
|
+
streamed reply (50 tokens per second, 23 bytes each) never fills them; a burst or a tunnel does. Only the receiving
|
|
201
|
+
application knows what it has applied.</p>
|
|
202
|
+
</section>
|
|
203
|
+
|
|
204
|
+
<section class="chapter" id="acks"><header class="opener"><div class="numeral">II</div><h2>Acknowledged Messages</h2><p class="lede">A window of two messages in flight, and why it adapts to any link.</p></header>
|
|
205
|
+
<p class="dropcap">The rule: a provider has at most <strong>K = 2</strong> state messages in flight per link. The consumer sends <code>ack</code> (three bytes in
|
|
206
|
+
the binary codec) after it applied one. While two are in flight, new commits do not produce messages; their ops wait,
|
|
207
|
+
merged per hold, and go out in the next message once an <code>ack</code> arrives.</p>
|
|
208
|
+
<div class="steps"><div class="steps-head"><span class="title">Two messages in flight on a slow link</span><span class="count"></span><button class="prev" type="button">Back</button><button class="next" type="button">Next</button></div><div class="step-panel"><h4>Fast link</h4><p>Commits 1, 2, 3 each become a message; each is acknowledged before the next commit. Nothing waits: the window never
|
|
209
|
+
fills, so a normal stream behaves exactly as without flow control.</p>
|
|
210
|
+
</div><div class="step-panel"><h4>The link slows</h4><p>Messages 1 and 2 are in flight. Commits 3 to 400 arrive; their ops are folded into the pending queue of each member
|
|
211
|
+
they touch (chapter III). Memory: the net change, not the history; at most 10,000 ops per member.</p>
|
|
212
|
+
</div><div class="step-panel"><h4>An ack arrives</h4><p><code>ack</code> for message 1. The provider sends message 3, carrying the folded ops of commits 3 to 400, encoded once.
|
|
213
|
+
The phone applies them as one batch and shows the newest text.</p>
|
|
214
|
+
</div><div class="step-panel"><h4>Self-clocking</h4><p>The slower the link, the more each message carries; the faster, the smaller and more frequent. Nobody measures
|
|
215
|
+
bandwidth: the acknowledgements pace the sender.</p>
|
|
216
|
+
</div></div>
|
|
217
|
+
<div id="fc-sim" style="font-family: var(--sans, system-ui); margin: 1.5rem 0; padding: 1rem; border: 1px solid #d8d2c6; border-radius: 6px; background: #fbf9f4;">
|
|
218
|
+
<div style="display:flex; gap:1rem; flex-wrap:wrap; align-items:center; font-size:0.9rem;">
|
|
219
|
+
<label>Link <input id="fc-rate" type="range" min="2" max="60" value="8"> <span id="fc-rate-v"></span> msgs/s</label>
|
|
220
|
+
<label>Commits <input id="fc-prod" type="range" min="5" max="120" value="60"> <span id="fc-prod-v"></span> /s</label>
|
|
221
|
+
<button id="fc-reset" style="font:inherit; padding:0.2rem 0.6rem;">Restart</button>
|
|
222
|
+
</div>
|
|
223
|
+
<canvas id="fc-canvas" width="640" height="220" style="width:100%; max-width:640px; display:block; margin-top:0.8rem;"></canvas>
|
|
224
|
+
<div id="fc-stats" style="font-size:0.85rem; margin-top:0.4rem; color:#444;"></div>
|
|
225
|
+
</div>
|
|
226
|
+
<script>
|
|
227
|
+
(() => {
|
|
228
|
+
const c = document.getElementById("fc-canvas"), g = c.getContext("2d");
|
|
229
|
+
const rate = document.getElementById("fc-rate"), prod = document.getElementById("fc-prod");
|
|
230
|
+
const rv = document.getElementById("fc-rate-v"), pv = document.getElementById("fc-prod-v");
|
|
231
|
+
const stats = document.getElementById("fc-stats");
|
|
232
|
+
let s;
|
|
233
|
+
function reset() {
|
|
234
|
+
s = { t: 0, commit: 0, naive: { queue: [], shown: 0, sent: 0 }, ack: { inflight: [], pending: 0, shown: 0, sent: 0, merged: 0 } };
|
|
235
|
+
}
|
|
236
|
+
function step(dt) {
|
|
237
|
+
const r = +rate.value, p = +prod.value; rv.textContent = r; pv.textContent = p;
|
|
238
|
+
s.t += dt;
|
|
239
|
+
const commits = Math.floor((s.t) * p) - s.commit;
|
|
240
|
+
for (let i = 0; i < commits; i++) {
|
|
241
|
+
s.commit++;
|
|
242
|
+
s.naive.queue.push(s.commit);
|
|
243
|
+
s.ack.pending = s.commit;
|
|
244
|
+
}
|
|
245
|
+
// link capacity: r messages per second, shared model: each delivers one message per 1/r s
|
|
246
|
+
s.naive.budget = (s.naive.budget || 0) + dt * r;
|
|
247
|
+
while (s.naive.budget >= 1 && s.naive.queue.length) { s.naive.budget -= 1; s.naive.shown = s.naive.queue.shift(); s.naive.sent++; }
|
|
248
|
+
s.ack.budget = (s.ack.budget || 0) + dt * r;
|
|
249
|
+
while (s.ack.budget >= 1 && s.ack.inflight.length) { s.ack.budget -= 1; s.ack.shown = s.ack.inflight.shift(); }
|
|
250
|
+
while (s.ack.inflight.length < 2 && s.ack.pending > (s.ack.inflight.at(-1) ?? s.ack.shown)) {
|
|
251
|
+
s.ack.merged += s.ack.pending - (s.ack.inflight.at(-1) ?? s.ack.shown) - 1;
|
|
252
|
+
s.ack.inflight.push(s.ack.pending); s.ack.sent++;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function lane(y, label, queue, shown, color) {
|
|
256
|
+
g.fillStyle = "#222"; g.font = "13px system-ui"; g.fillText(label, 8, y - 26);
|
|
257
|
+
g.fillStyle = "#e9e3d6"; g.fillRect(8, y - 18, 520, 26);
|
|
258
|
+
const n = Math.min(queue, 130);
|
|
259
|
+
g.fillStyle = color;
|
|
260
|
+
for (let i = 0; i < n; i++) g.fillRect(10 + i * 4, y - 15, 3, 20);
|
|
261
|
+
g.fillStyle = "#222"; g.fillText(`waiting: ${queue}`, 540, y);
|
|
262
|
+
const age = s.commit - shown;
|
|
263
|
+
g.fillText(`phone shows commit ${shown}, ${age} behind`, 8, y + 28);
|
|
264
|
+
}
|
|
265
|
+
function draw() {
|
|
266
|
+
g.clearRect(0, 0, c.width, c.height);
|
|
267
|
+
lane(48, "No flow control: every commit is sent and queued", s.naive.queue.length, s.naive.shown, "#b5523b");
|
|
268
|
+
lane(150, "Acknowledged window (K = 2), pending ops merged", s.ack.inflight.length, s.ack.shown, "#3b6fb5");
|
|
269
|
+
stats.textContent = `newest commit ${s.commit} · window: ${s.ack.sent} messages carried ${s.commit} commits (${s.ack.merged} merged)`;
|
|
270
|
+
}
|
|
271
|
+
let last = performance.now();
|
|
272
|
+
function frame(now) { step(Math.min((now - last) / 1000, 0.1)); last = now; draw(); requestAnimationFrame(frame); }
|
|
273
|
+
document.getElementById("fc-reset").onclick = reset;
|
|
274
|
+
reset(); requestAnimationFrame(frame);
|
|
275
|
+
})();
|
|
276
|
+
</script>
|
|
277
|
+
<p>The simulation above shows the same effect in miniature. Drag the link below the commit rate: the upper lane's queue
|
|
278
|
+
grows without bound and the phone falls further behind every second; the lower lane never has more than two messages
|
|
279
|
+
waiting, and the phone is at most a message behind.</p>
|
|
280
|
+
</section>
|
|
281
|
+
|
|
282
|
+
<section class="chapter" id="merge"><header class="opener"><div class="numeral">III</div><h2>Fold the Backlog</h2><p class="lede">What a waiting hold keeps while the window is full, and why it never measures sizes.</p></header>
|
|
283
|
+
<p class="dropcap">While messages are in flight, each hold member keeps the ops of new commits in a <strong>pending queue</strong> and <strong>folds</strong> them:
|
|
284
|
+
a new op merges into the last pending op on the same path when that is safe, so a backlog keeps the net change, not
|
|
285
|
+
the history. When an acknowledgement frees a slot, the queue is encoded once into one message.</p>
|
|
286
|
+
<table>
|
|
287
|
+
<thead>
|
|
288
|
+
<tr>
|
|
289
|
+
<th>Last pending op</th>
|
|
290
|
+
<th>New op</th>
|
|
291
|
+
<th>Kept</th>
|
|
292
|
+
</tr>
|
|
293
|
+
</thead>
|
|
294
|
+
<tbody><tr>
|
|
295
|
+
<td><code>a text "Hel"</code></td>
|
|
296
|
+
<td><code>a text "lo"</code></td>
|
|
297
|
+
<td>one <code>a text "Hello"</code></td>
|
|
298
|
+
</tr>
|
|
299
|
+
<tr>
|
|
300
|
+
<td><code>s count 41</code> or an append</td>
|
|
301
|
+
<td><code>s count 42</code></td>
|
|
302
|
+
<td>the <code>s</code>: it overwrites what came before</td>
|
|
303
|
+
</tr>
|
|
304
|
+
<tr>
|
|
305
|
+
<td>anything</td>
|
|
306
|
+
<td><code>r value</code></td>
|
|
307
|
+
<td>only the <code>r</code>: a replacement makes everything before it moot</td>
|
|
308
|
+
</tr>
|
|
309
|
+
<tr>
|
|
310
|
+
<td>anything on a path in the index</td>
|
|
311
|
+
<td>a structural op (<code>p</code>, <code>m</code>, <code>d</code>, <code>t</code>, <code>s</code> of an object)</td>
|
|
312
|
+
<td>appended; the index is cleared</td>
|
|
313
|
+
</tr>
|
|
314
|
+
</tbody></table>
|
|
315
|
+
<p>The merge partner is found through a <strong>leaf index</strong>: a map from path to the last pending append or primitive set on
|
|
316
|
+
that path. Leaf ops on different paths commute, so a new op may merge into an earlier one, not only into its
|
|
317
|
+
neighbour; a status field changing between two tokens no longer stops the reply from folding. Any structural op
|
|
318
|
+
clears the index, because after a splice <code>entries[7]</code> may name another entry. Past 10,000 pending ops the queue keeps
|
|
319
|
+
only "replace with the current value", which then costs nothing while the consumer stays slow.</p>
|
|
320
|
+
<p><strong>Why no size rule.</strong> The first idea was to send a snapshot once the merged ops outgrow the member. The reviewers
|
|
321
|
+
measured it on real p2 data and it failed three ways: "snapshot bytes plus sent op bytes" is no bound (an interned path
|
|
322
|
+
lets a value grow faster than its ops: 5,404 B of snapshot after 832 B of ops), it drifts 1,254 times too high under
|
|
323
|
+
churn, and an exact size needs a size on every node of the value. Folding alone came within about 10% of an exact
|
|
324
|
+
oracle, with no measuring at all.</p>
|
|
325
|
+
<table>
|
|
326
|
+
<thead>
|
|
327
|
+
<tr>
|
|
328
|
+
<th>60 s stall, all workloads</th>
|
|
329
|
+
<th>Uncompressed</th>
|
|
330
|
+
<th>Compressed</th>
|
|
331
|
+
</tr>
|
|
332
|
+
</thead>
|
|
333
|
+
<tbody><tr>
|
|
334
|
+
<td>merged ops, no folding</td>
|
|
335
|
+
<td>466 KB</td>
|
|
336
|
+
<td>166 KB</td>
|
|
337
|
+
</tr>
|
|
338
|
+
<tr>
|
|
339
|
+
<td>adjacent folding</td>
|
|
340
|
+
<td>399 KB</td>
|
|
341
|
+
<td>147 KB</td>
|
|
342
|
+
</tr>
|
|
343
|
+
<tr>
|
|
344
|
+
<td>leaf index (the decision)</td>
|
|
345
|
+
<td>372 KB</td>
|
|
346
|
+
<td>145 KB</td>
|
|
347
|
+
</tr>
|
|
348
|
+
<tr>
|
|
349
|
+
<td>exact oracle (not buildable)</td>
|
|
350
|
+
<td>335 KB</td>
|
|
351
|
+
<td>136 KB</td>
|
|
352
|
+
</tr>
|
|
353
|
+
</tbody></table>
|
|
354
|
+
<p><strong>One queue for every slow consumer.</strong> The same queue serves Chord's shared watch handle: a listener still busy with
|
|
355
|
+
the last frame gets the commits made meanwhile as one call with the newest value and the folded ops, instead of
|
|
356
|
+
today's "after 100 frames, replace everything". With a listener taking one second, a conversation view's watch went
|
|
357
|
+
from 402 calls and 17.9 MB of ops to 312 calls and 257 KB. Durable's event stream keeps a queue of its own, because
|
|
358
|
+
events are not state: folding would drop them.</p>
|
|
359
|
+
<p>Animation of the fold: <a href="https://radius.earendil.com/artifact/01m5nc9s24e9kscppen2fxgwye">How folding works</a>.</p>
|
|
360
|
+
</section>
|
|
361
|
+
|
|
362
|
+
<section class="chapter" id="calls"><header class="opener"><div class="numeral">IV</div><h2>Calls Get Their Own Window</h2><p class="lede">Why acknowledged state does not protect against a call flood.</p></header>
|
|
363
|
+
<p class="dropcap">Calls are small and the provider acknowledges nothing until they finish, so a byte or message window on state says
|
|
364
|
+
nothing about how many handlers run. A tab with a bug:</p>
|
|
365
|
+
<pre><code><span class="tk-k">for</span> (<span class="tk-k">const</span> note <span class="tk-k">of</span> notes) ref.<span class="tk-f">add</span>(note, ctx); <span class="tk-c">// 100,000 calls, none awaited</span></code></pre>
|
|
366
|
+
<table>
|
|
367
|
+
<thead>
|
|
368
|
+
<tr>
|
|
369
|
+
<th>100,000 calls, handlers awaiting 10 ms</th>
|
|
370
|
+
<th>Finished in</th>
|
|
371
|
+
<th>Provider memory</th>
|
|
372
|
+
<th>Another client's p95 round trip</th>
|
|
373
|
+
</tr>
|
|
374
|
+
</thead>
|
|
375
|
+
<tbody><tr>
|
|
376
|
+
<td>no limit</td>
|
|
377
|
+
<td>0.66 s</td>
|
|
378
|
+
<td>+56.5 MB, 43,824 running</td>
|
|
379
|
+
<td>129 ms (345 ms with trivial handlers)</td>
|
|
380
|
+
</tr>
|
|
381
|
+
<tr>
|
|
382
|
+
<td>provider-side semaphore of 64</td>
|
|
383
|
+
<td>15.9 s</td>
|
|
384
|
+
<td>+42.5 MB, 96,736 queued on the provider</td>
|
|
385
|
+
<td>0.38 ms</td>
|
|
386
|
+
</tr>
|
|
387
|
+
<tr>
|
|
388
|
+
<td>caller-side window of 64</td>
|
|
389
|
+
<td>17.1 s</td>
|
|
390
|
+
<td><strong>+4 MB, nothing queued on the provider</strong></td>
|
|
391
|
+
<td>0.66 ms</td>
|
|
392
|
+
</tr>
|
|
393
|
+
<tr>
|
|
394
|
+
<td>handlers doing 10 ms of CPU work</td>
|
|
395
|
+
<td></td>
|
|
396
|
+
<td></td>
|
|
397
|
+
<td>blocked about 1 s, with or without a limit</td>
|
|
398
|
+
</tr>
|
|
399
|
+
</tbody></table>
|
|
400
|
+
<p>The provider's <code>hello</code> announces a call window <strong>N</strong> (256 by default) per origin (chapter VI). A consumer keeps at
|
|
401
|
+
most N of its own calls unanswered per link; further calls wait in the link's call queue, in order. Nothing is refused (K16); a peer that sends
|
|
402
|
+
more than N is a protocol error. The flood waits in the tab that caused it. CPU-heavy handlers block the event loop
|
|
403
|
+
whatever the window: that stays the handler author's responsibility.</p>
|
|
404
|
+
</section>
|
|
405
|
+
|
|
406
|
+
<section class="chapter" id="relays"><header class="opener"><div class="numeral">V</div><h2>Relays</h2><p class="lede">One link to a worker carries many tabs. Decided: flow control per origin.</p></header>
|
|
407
|
+
<p class="dropcap">The server relays every tab's holds to a session's worker over one link. Three ways to apply the acknowledged window
|
|
408
|
+
across that hop were considered:</p>
|
|
409
|
+
<figure class="fig wide anim"><svg viewBox="0 0 720 378" width="100%" role="img" aria-label="(a) End-to-end acknowledgements: the server acknowledges the worker only after the tab did." xmlns="http://www.w3.org/2000/svg" style="font-family:'Avenir Next','Helvetica Neue',Helvetica,Arial,sans-serif;max-width:760px"><defs><marker id="f2m" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#7b7468"/></marker><marker id="f2a" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#b4472f"/></marker><marker id="f2b" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#2e6a8a"/></marker></defs><text x="16" y="22" font-size="10" fill="#b4472f" text-anchor="start" font-weight="600" letter-spacing="2">WORKER</text><text x="16" y="144" font-size="10" fill="#7b7468" text-anchor="start" font-weight="600" letter-spacing="2">SERVER</text><text x="16" y="266" font-size="10" fill="#2e6a8a" text-anchor="start" font-weight="600" letter-spacing="2">TABS</text><g class="step" style="--i:1"><rect x="255" y="36" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="360" y="55.3" font-size="11" fill="#2a2723" text-anchor="middle">worker</text><text x="360" y="69.3" font-size="9.5" fill="#7b7468" text-anchor="middle">K = 2 per link</text></g><g class="step" style="--i:3"><rect x="255" y="158" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="360" y="177.3" font-size="11" fill="#2a2723" text-anchor="middle">server</text><text x="360" y="191.3" font-size="9.5" fill="#7b7468" text-anchor="middle">acks upstream after downstream</text></g><g class="step" style="--i:5"><rect x="137" y="280" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="242" y="299.3" font-size="11" fill="#2a2723" text-anchor="middle">fast tab</text><text x="242" y="313.3" font-size="9.5" fill="#7b7468" text-anchor="middle">desktop</text></g><g class="step" style="--i:7"><rect x="373" y="280" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae" stroke-dasharray="3 3"/><text x="478" y="299.3" font-size="11" fill="#2a2723" text-anchor="middle">slow tab</text><text x="478" y="313.3" font-size="9.5" fill="#7b7468" text-anchor="middle">phone in a tunnel</text></g><g class="step edge" style="--i:2"><path d="M351 80 L351 156" fill="none" stroke="#7b7468" marker-end="url(#f2m)"/><rect x="312.7" y="112" width="34.3" height="13" fill="#fbf8f2" opacity=".92"/><text x="343" y="122" font-size="9.5" fill="#7b7468" text-anchor="end">state</text></g><g class="step edge" style="--i:4"><path d="M360 202 L242 278" fill="none" stroke="#7b7468" marker-end="url(#f2m)"/><rect x="305" y="234" width="34.3" height="13" fill="#fbf8f2" opacity=".92"/><text x="309" y="244" font-size="9.5" fill="#7b7468" text-anchor="start">state</text></g><g class="step edge" style="--i:6"><path d="M351 202 L469 278" fill="none" stroke="#7b7468" marker-end="url(#f2m)"/><rect x="414" y="234" width="65.9" height="13" fill="#fbf8f2" opacity=".92"/><text x="418" y="244" font-size="9.5" fill="#7b7468" text-anchor="start">state, slow</text></g><g class="step edge" style="--i:8"><path d="M487 280 L369 204" fill="none" stroke="#7b7468" stroke-dasharray="4 3" marker-end="url(#f2m)"/><rect x="432" y="236" width="60.7" height="13" fill="#fbf8f2" opacity=".92"/><text x="436" y="246" font-size="9.5" fill="#7b7468" text-anchor="start">ack (late)</text></g><g class="step edge" style="--i:9"><path d="M369 158 L369 82" fill="none" stroke="#b4472f" marker-end="url(#f2a)"/><rect x="373" y="114" width="160.8" height="13" fill="#fbf8f2" opacity=".92"/><text x="377" y="124" font-size="9.5" fill="#b4472f" text-anchor="start">ack only when every tab acked</text></g><text x="16" y="348" font-size="10.5" fill="#7b7468" text-anchor="start">Simplest, but the slow phone holds the worker's one window, so the fast tab's updates arrive in coarser, later batches</text><text x="16" y="363" font-size="10.5" fill="#7b7468" text-anchor="start">too.</text></svg><figcaption><button class="replay" type="button">Replay</button><span class="fignum">Figure 2.</span> (a) End-to-end acknowledgements: the server acknowledges the worker only after the tab did.</figcaption></figure>
|
|
410
|
+
<figure class="fig wide anim"><svg viewBox="0 0 720 378" width="100%" role="img" aria-label="(b) Acknowledgements per hop: the server acknowledges at once and resets a slow tab." xmlns="http://www.w3.org/2000/svg" style="font-family:'Avenir Next','Helvetica Neue',Helvetica,Arial,sans-serif;max-width:760px"><defs><marker id="f3m" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#7b7468"/></marker><marker id="f3a" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#b4472f"/></marker><marker id="f3b" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#2e6a8a"/></marker></defs><text x="16" y="22" font-size="10" fill="#b4472f" text-anchor="start" font-weight="600" letter-spacing="2">WORKER</text><text x="16" y="144" font-size="10" fill="#7b7468" text-anchor="start" font-weight="600" letter-spacing="2">SERVER</text><text x="16" y="266" font-size="10" fill="#2e6a8a" text-anchor="start" font-weight="600" letter-spacing="2">TABS</text><g class="step" style="--i:1"><rect x="255" y="36" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="360" y="55.3" font-size="11" fill="#2a2723" text-anchor="middle">worker</text><text x="360" y="69.3" font-size="9.5" fill="#7b7468" text-anchor="middle">K = 2 per link</text></g><g class="step" style="--i:3"><rect x="255" y="158" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="360" y="177.3" font-size="11" fill="#2a2723" text-anchor="middle">server</text><text x="360" y="191.3" font-size="9.5" fill="#7b7468" text-anchor="middle">acks at once; window per tab</text></g><g class="step" style="--i:6"><rect x="137" y="280" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="242" y="299.3" font-size="11" fill="#2a2723" text-anchor="middle">fast tab</text><text x="242" y="313.3" font-size="9.5" fill="#7b7468" text-anchor="middle">desktop</text></g><g class="step" style="--i:8"><rect x="373" y="280" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae" stroke-dasharray="3 3"/><text x="478" y="299.3" font-size="11" fill="#2a2723" text-anchor="middle">slow tab</text><text x="478" y="313.3" font-size="9.5" fill="#7b7468" text-anchor="middle">phone in a tunnel</text></g><g class="step edge" style="--i:2"><path d="M351 80 L351 156" fill="none" stroke="#7b7468" marker-end="url(#f3m)"/><rect x="312.7" y="112" width="34.3" height="13" fill="#fbf8f2" opacity=".92"/><text x="343" y="122" font-size="9.5" fill="#7b7468" text-anchor="end">state</text></g><g class="step edge" style="--i:4"><path d="M369 158 L369 82" fill="none" stroke="#b4472f" marker-end="url(#f3a)"/><rect x="373" y="114" width="65.9" height="13" fill="#fbf8f2" opacity=".92"/><text x="377" y="124" font-size="9.5" fill="#b4472f" text-anchor="start">ack at once</text></g><g class="step edge" style="--i:5"><path d="M360 202 L242 278" fill="none" stroke="#7b7468" marker-end="url(#f3m)"/><rect x="305" y="234" width="81.8" height="13" fill="#fbf8f2" opacity=".92"/><text x="309" y="244" font-size="9.5" fill="#7b7468" text-anchor="start">state, current</text></g><g class="step edge" style="--i:7"><path d="M360 202 L478 278" fill="none" stroke="#7b7468" stroke-dasharray="4 3" marker-end="url(#f3m)"/><rect x="423" y="234" width="166" height="13" fill="#fbf8f2" opacity=".92"/><text x="427" y="244" font-size="9.5" fill="#7b7468" text-anchor="start">window full: its parts dropped</text></g><g class="step edge" style="--i:9"><path d="M369 158 L369 82" fill="none" stroke="#b4472f" marker-end="url(#f3a)"/><rect x="373" y="114" width="123.9" height="13" fill="#fbf8f2" opacity=".92"/><text x="377" y="124" font-size="9.5" fill="#b4472f" text-anchor="start">reset: fresh snapshots</text></g><text x="16" y="348" font-size="10.5" fill="#7b7468" text-anchor="start">The server cannot fold opaque parts, so it drops them and asks for snapshots. Built and measured in slice 4a: a 1 s</text><text x="16" y="363" font-size="10.5" fill="#7b7468" text-anchor="start">stall cost three full snapshots (~1.5 MiB) and ~24 s to catch up under streaming.</text></svg><figcaption><button class="replay" type="button">Replay</button><span class="fignum">Figure 3.</span> (b) Acknowledgements per hop: the server acknowledges at once and resets a slow tab.</figcaption></figure>
|
|
411
|
+
<figure class="fig wide anim"><svg viewBox="0 0 720 393" width="100%" role="img" aria-label="(c) Flow control per origin (decided 2026-10-24): the worker keeps a window per tab; the relay only forwards." xmlns="http://www.w3.org/2000/svg" style="font-family:'Avenir Next','Helvetica Neue',Helvetica,Arial,sans-serif;max-width:760px"><defs><marker id="f4m" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#7b7468"/></marker><marker id="f4a" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#b4472f"/></marker><marker id="f4b" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#2e6a8a"/></marker></defs><text x="16" y="22" font-size="10" fill="#b4472f" text-anchor="start" font-weight="600" letter-spacing="2">WORKER</text><text x="16" y="144" font-size="10" fill="#7b7468" text-anchor="start" font-weight="600" letter-spacing="2">SERVER</text><text x="16" y="266" font-size="10" fill="#2e6a8a" text-anchor="start" font-weight="600" letter-spacing="2">TABS</text><g class="step" style="--i:1"><rect x="255" y="36" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="360" y="55.3" font-size="11" fill="#2a2723" text-anchor="middle">worker</text><text x="360" y="69.3" font-size="9.5" fill="#7b7468" text-anchor="middle">K = 2 per (link, origin)</text></g><g class="step" style="--i:3"><rect x="255" y="158" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="360" y="177.3" font-size="11" fill="#2a2723" text-anchor="middle">server</text><text x="360" y="191.3" font-size="9.5" fill="#7b7468" text-anchor="middle">forwards; FIFO of senders</text></g><g class="step" style="--i:5"><rect x="137" y="280" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae"/><text x="242" y="299.3" font-size="11" fill="#2a2723" text-anchor="middle">fast tab</text><text x="242" y="313.3" font-size="9.5" fill="#7b7468" text-anchor="middle">origin 1</text></g><g class="step" style="--i:7"><rect x="373" y="280" width="210" height="44" rx="4" fill="#fff" stroke="#c9bfae" stroke-dasharray="3 3"/><text x="478" y="299.3" font-size="11" fill="#2a2723" text-anchor="middle">slow tab</text><text x="478" y="313.3" font-size="9.5" fill="#7b7468" text-anchor="middle">origin 2, in a tunnel</text></g><g class="step edge" style="--i:2"><path d="M351 80 L351 156" fill="none" stroke="#7b7468" marker-end="url(#f4m)"/><rect x="244.2" y="112" width="102.8" height="13" fill="#fbf8f2" opacity=".92"/><text x="343" y="122" font-size="9.5" fill="#7b7468" text-anchor="end">state @1, state @2</text></g><g class="step edge" style="--i:4"><path d="M360 202 L242 278" fill="none" stroke="#7b7468" marker-end="url(#f4m)"/><rect x="305" y="234" width="81.8" height="13" fill="#fbf8f2" opacity=".92"/><text x="309" y="244" font-size="9.5" fill="#7b7468" text-anchor="start">state, current</text></g><g class="step edge" style="--i:6"><path d="M351 202 L469 278" fill="none" stroke="#7b7468" stroke-dasharray="4 3" marker-end="url(#f4m)"/><rect x="414" y="234" width="65.9" height="13" fill="#fbf8f2" opacity=".92"/><text x="418" y="244" font-size="9.5" fill="#7b7468" text-anchor="start">state, slow</text></g><g class="step edge" style="--i:8"><path d="M487 280 L369 204" fill="none" stroke="#7b7468" stroke-dasharray="4 3" marker-end="url(#f4m)"/><rect x="432" y="236" width="60.7" height="13" fill="#fbf8f2" opacity=".92"/><text x="436" y="246" font-size="9.5" fill="#7b7468" text-anchor="start">ack (late)</text></g><g class="step edge" style="--i:9"><path d="M369 158 L369 82" fill="none" stroke="#b4472f" marker-end="url(#f4a)"/><rect x="373" y="114" width="139.7" height="13" fill="#fbf8f2" opacity=".92"/><text x="377" y="124" font-size="9.5" fill="#b4472f" text-anchor="start">ack @2 when the tab acked</text></g><text x="16" y="348" font-size="10.5" fill="#7b7468" text-anchor="start">The relay numbers each tab (an origin) and forwards at once; each tab's ack goes back to the worker that sent the</text><text x="16" y="363" font-size="10.5" fill="#7b7468" text-anchor="start">message. The worker folds the slow tab's commits in that origin's pending queues, exactly as for a direct tab; the</text><text x="16" y="378" font-size="10.5" fill="#7b7468" text-anchor="start">fast tab is another origin with its own window.</text></svg><figcaption><button class="replay" type="button">Replay</button><span class="fignum">Figure 4.</span> (c) Flow control per origin (decided 2026-10-24): the worker keeps a window per tab; the relay only forwards.</figcaption></figure>
|
|
412
|
+
<p>How (c) works:</p>
|
|
413
|
+
<ul>
|
|
414
|
+
<li><strong>Origins.</strong> The relay gives each downstream link a number, never reused; every message upstream carries it
|
|
415
|
+
(<code>origin</code>), and the worker keys holds, calls, windows and acks by (link, origin, id). Ids are not renumbered.</li>
|
|
416
|
+
<li><strong>What the relay keeps.</strong> Per relayed hold its upstream link and its original <code>hold</code> message (to route calls and
|
|
417
|
+
hold again after a worker restart); per tab a FIFO of which worker sent each message in flight, so each in-order
|
|
418
|
+
<code>ack</code> goes back to the right worker. Nothing is parsed, folded, dropped or reset.</li>
|
|
419
|
+
<li><strong>Calls.</strong> N is per origin at the worker; the relay forwards calls at once, each tab bounded by its own window.</li>
|
|
420
|
+
<li><strong>A tab leaves.</strong> Its link closes; the relay sends <code>closed { origin }</code> to each worker it relayed to, which releases
|
|
421
|
+
that origin's holds and aborts its calls.</li>
|
|
422
|
+
<li><strong>Cost.</strong> A tab served by W workers may have 2W messages in flight (plus the relay's own 2). Two bytes of origin per
|
|
423
|
+
message on the worker link.</li>
|
|
424
|
+
</ul>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
427
|
+
<section class="chapter epilogue" id="what-changes-in-the-design"><header class="opener"><div class="numeral">∴</div><h2>What Changes in the Design</h2><p class="lede">The converged scheme, what it replaces, and the relay decision.</p></header>
|
|
428
|
+
<p class="dropcap"><strong>Converged</strong> (Astra and Fable, after cross-checking each other's experiments): at most K = 2 acknowledged state
|
|
429
|
+
messages in flight; pending ops folded per hold member through a leaf index, capped at 10,000; a call window N
|
|
430
|
+
announced in <code>hello</code>, extra calls queued by the caller; holds unlimited; size limits stay a separate check.</p>
|
|
431
|
+
<p><strong>Replaced</strong> from the design (§3, §22): the byte window and its credit messages, snapshot admission and debt (R19, Q3),
|
|
432
|
+
cut C12's call-credit accounting, any adapter backlog API. IPC and WebSocket behave the same, because acknowledgements
|
|
433
|
+
are messages.</p>
|
|
434
|
+
<p><strong>Relays (owner):</strong> per-hop acknowledgements (b) were decided on 2026-10-23 and built in slice 4a; the benchmarks
|
|
435
|
+
showed a slow tab recovering through resets far worse than a direct tab (24 s and three snapshots after a 1 s stall).
|
|
436
|
+
On 2026-10-24 they were replaced by <strong>flow control per origin</strong> (c): the worker keeps window, pending queues and calls
|
|
437
|
+
per (link, origin), the relay only forwards and returns acks. Measured after the rework: a slow tab through the relay
|
|
438
|
+
catches up exactly like a direct one. The circuit breaker stayed undecided; the heartbeat (15 s ping, 30 s close)
|
|
439
|
+
was built instead.</p>
|
|
440
|
+
</section>
|
|
441
|
+
|
|
442
|
+
<section class="chapter appendix" id="measurements"><header class="opener"><div class="numeral">A</div><h2>Measurements</h2></header>
|
|
443
|
+
<table>
|
|
444
|
+
<thead>
|
|
445
|
+
<tr>
|
|
446
|
+
<th>Experiment</th>
|
|
447
|
+
<th>Setup</th>
|
|
448
|
+
<th>Result</th>
|
|
449
|
+
</tr>
|
|
450
|
+
</thead>
|
|
451
|
+
<tbody><tr>
|
|
452
|
+
<td>normal stream</td>
|
|
453
|
+
<td>50 × 23 B/s through 64 KiB/s</td>
|
|
454
|
+
<td>no buffering, frames ≤ 2 ms old</td>
|
|
455
|
+
</tr>
|
|
456
|
+
<tr>
|
|
457
|
+
<td>flood, no control</td>
|
|
458
|
+
<td>~9,800 × 128 B/s, 10 s</td>
|
|
459
|
+
<td>7.0 MB in <code>bufferedAmount</code>, heap +18.5 MB, newest state 9.4 s old</td>
|
|
460
|
+
</tr>
|
|
461
|
+
<tr>
|
|
462
|
+
<td>flood, <code>bufferedAmount</code> ≤ 64 KiB + merging</td>
|
|
463
|
+
<td>same</td>
|
|
464
|
+
<td>heap +0.46 MB, newest state 9.4 s old</td>
|
|
465
|
+
</tr>
|
|
466
|
+
<tr>
|
|
467
|
+
<td>flood, 1 to 4 acked messages + merging</td>
|
|
468
|
+
<td>same</td>
|
|
469
|
+
<td>heap +0.4 MB, newest state 0.0 s old, 98 % fewer messages</td>
|
|
470
|
+
</tr>
|
|
471
|
+
<tr>
|
|
472
|
+
<td>client stops reading (loopback)</td>
|
|
473
|
+
<td>20k × 90 B/s, 15 s</td>
|
|
474
|
+
<td>23.4 MiB buffered, RSS +121 MB; merging above 1 MiB: flat</td>
|
|
475
|
+
</tr>
|
|
476
|
+
<tr>
|
|
477
|
+
<td>IPC to a busy child</td>
|
|
478
|
+
<td>623k sends</td>
|
|
479
|
+
<td><code>send()</code> false from ~64 KB, parent +1,035 MB; the callback acknowledges the write, not the handler</td>
|
|
480
|
+
</tr>
|
|
481
|
+
<tr>
|
|
482
|
+
<td>call flood</td>
|
|
483
|
+
<td>100,000 calls, see chapter IV</td>
|
|
484
|
+
<td>caller-side window: +4 MB, other client p95 0.66 ms</td>
|
|
485
|
+
</tr>
|
|
486
|
+
<tr>
|
|
487
|
+
<td>slow tab through a relay, (b) resets</td>
|
|
488
|
+
<td>64 KiB/s, 100 tokens/s into a 500 KB view, 1 s stall</td>
|
|
489
|
+
<td>~24 s to catch up, three full snapshots (~1.5 MiB)</td>
|
|
490
|
+
</tr>
|
|
491
|
+
<tr>
|
|
492
|
+
<td>slow tab through a relay, (c) per origin</td>
|
|
493
|
+
<td>same, 1 s / 10 s / 60 s stall</td>
|
|
494
|
+
<td>caught up after 0.02 / 0.08 / 0.38 s, 485 B / 4.1 KiB / 23.7 KiB (same as direct)</td>
|
|
495
|
+
</tr>
|
|
496
|
+
</tbody></table>
|
|
497
|
+
</section>
|
|
498
|
+
|
|
499
|
+
<p class="colophon">Flow control for Chord links · fourth edition · 2026-10-24 · based on the reviewers' experiments and design §3, §22</p>
|
|
500
|
+
</div></div>
|
|
501
|
+
<script>(function activate(root) {
|
|
502
|
+
const replay = (f) => {
|
|
503
|
+
f.classList.remove("play");
|
|
504
|
+
void f.getBoundingClientRect();
|
|
505
|
+
f.classList.add("play");
|
|
506
|
+
};
|
|
507
|
+
const io = new IntersectionObserver(
|
|
508
|
+
(es) => {
|
|
509
|
+
for (const e of es)
|
|
510
|
+
if (e.isIntersecting) {
|
|
511
|
+
e.target.classList.add("play");
|
|
512
|
+
io.unobserve(e.target);
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
{ threshold: 0.3 },
|
|
516
|
+
);
|
|
517
|
+
root.querySelectorAll(".anim:not(.armed)").forEach((f) => {
|
|
518
|
+
f.classList.add("armed");
|
|
519
|
+
io.observe(f);
|
|
520
|
+
const b = f.querySelector(".replay");
|
|
521
|
+
if (b) b.onclick = () => replay(f);
|
|
522
|
+
});
|
|
523
|
+
root.querySelectorAll(".steps:not(.js)").forEach((s) => {
|
|
524
|
+
s.classList.add("js");
|
|
525
|
+
const ps = [...s.querySelectorAll(":scope>.step-panel")];
|
|
526
|
+
let i = 0;
|
|
527
|
+
const c = s.querySelector(".count"),
|
|
528
|
+
p = s.querySelector(".prev"),
|
|
529
|
+
n = s.querySelector(".next");
|
|
530
|
+
const show = () => {
|
|
531
|
+
ps.forEach((x, j) => x.classList.toggle("on", j === i));
|
|
532
|
+
c.textContent = `${i + 1} / ${ps.length}`;
|
|
533
|
+
p.disabled = i === 0;
|
|
534
|
+
n.disabled = i === ps.length - 1;
|
|
535
|
+
ps[i].querySelectorAll(".anim").forEach(replay);
|
|
536
|
+
};
|
|
537
|
+
p.onclick = () => {
|
|
538
|
+
i--;
|
|
539
|
+
show();
|
|
540
|
+
};
|
|
541
|
+
n.onclick = () => {
|
|
542
|
+
i++;
|
|
543
|
+
show();
|
|
544
|
+
};
|
|
545
|
+
show();
|
|
546
|
+
});
|
|
547
|
+
})(document)</script>
|
|
548
|
+
</body></html>
|