@optimystic/db-core 0.21.0 → 0.24.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.
Files changed (185) hide show
  1. package/README.md +336 -336
  2. package/dist/src/btree/btree.d.ts +2 -1
  3. package/dist/src/btree/btree.d.ts.map +1 -1
  4. package/dist/src/btree/btree.js +1 -1
  5. package/dist/src/btree/btree.js.map +1 -1
  6. package/dist/src/chain/chain.d.ts +1 -1
  7. package/dist/src/chain/chain.d.ts.map +1 -1
  8. package/dist/src/chain/chain.js +1 -1
  9. package/dist/src/chain/chain.js.map +1 -1
  10. package/dist/src/cluster/structs.d.ts +39 -1
  11. package/dist/src/cluster/structs.d.ts.map +1 -1
  12. package/dist/src/cluster/structs.js +24 -0
  13. package/dist/src/cluster/structs.js.map +1 -1
  14. package/dist/src/collection/collection.d.ts +20 -1
  15. package/dist/src/collection/collection.d.ts.map +1 -1
  16. package/dist/src/collection/collection.js +31 -4
  17. package/dist/src/collection/collection.js.map +1 -1
  18. package/dist/src/collections/diary/diary.d.ts.map +1 -1
  19. package/dist/src/collections/diary/diary.js +2 -1
  20. package/dist/src/collections/diary/diary.js.map +1 -1
  21. package/dist/src/collections/diary/struct.js +1 -1
  22. package/dist/src/collections/diary/struct.js.map +1 -1
  23. package/dist/src/collections/tree/collection-trunk.js +1 -1
  24. package/dist/src/collections/tree/collection-trunk.js.map +1 -1
  25. package/dist/src/collections/tree/struct.d.ts +1 -1
  26. package/dist/src/collections/tree/struct.d.ts.map +1 -1
  27. package/dist/src/collections/tree/struct.js +2 -1
  28. package/dist/src/collections/tree/struct.js.map +1 -1
  29. package/dist/src/collections/tree/tree.d.ts +5 -0
  30. package/dist/src/collections/tree/tree.d.ts.map +1 -1
  31. package/dist/src/collections/tree/tree.js +7 -0
  32. package/dist/src/collections/tree/tree.js.map +1 -1
  33. package/dist/src/log/log.d.ts +1 -1
  34. package/dist/src/log/log.d.ts.map +1 -1
  35. package/dist/src/log/log.js +2 -2
  36. package/dist/src/log/log.js.map +1 -1
  37. package/dist/src/network/i-peer-network.d.ts +16 -0
  38. package/dist/src/network/i-peer-network.d.ts.map +1 -1
  39. package/dist/src/network/struct.d.ts +39 -2
  40. package/dist/src/network/struct.d.ts.map +1 -1
  41. package/dist/src/network/struct.js +18 -0
  42. package/dist/src/network/struct.js.map +1 -1
  43. package/dist/src/testing/test-transactor.d.ts +95 -8
  44. package/dist/src/testing/test-transactor.d.ts.map +1 -1
  45. package/dist/src/testing/test-transactor.js +133 -12
  46. package/dist/src/testing/test-transactor.js.map +1 -1
  47. package/dist/src/transaction/coordinator.d.ts.map +1 -1
  48. package/dist/src/transaction/coordinator.js +2 -1
  49. package/dist/src/transaction/coordinator.js.map +1 -1
  50. package/dist/src/transaction/transaction.d.ts +1 -1
  51. package/dist/src/transaction/transaction.js +1 -1
  52. package/dist/src/transactor/network-transactor.d.ts.map +1 -1
  53. package/dist/src/transactor/network-transactor.js +57 -18
  54. package/dist/src/transactor/network-transactor.js.map +1 -1
  55. package/dist/src/transactor/transactor-source.d.ts.map +1 -1
  56. package/dist/src/transactor/transactor-source.js +25 -2
  57. package/dist/src/transactor/transactor-source.js.map +1 -1
  58. package/dist/src/transform/cache-source.js +1 -1
  59. package/dist/src/transform/cache-source.js.map +1 -1
  60. package/dist/src/transform/helpers.d.ts +6 -1
  61. package/dist/src/transform/helpers.d.ts.map +1 -1
  62. package/dist/src/transform/helpers.js +7 -6
  63. package/dist/src/transform/helpers.js.map +1 -1
  64. package/dist/src/transform/tracker.d.ts.map +1 -1
  65. package/dist/src/transform/tracker.js +2 -1
  66. package/dist/src/transform/tracker.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/btree/btree.ts +2 -1
  69. package/src/chain/chain.ts +2 -1
  70. package/src/cluster/membership.ts +85 -85
  71. package/src/cluster/structs.ts +43 -4
  72. package/src/cohort-topic/addressing.ts +120 -120
  73. package/src/cohort-topic/antidos/bootstrap-evidence-envelope.ts +253 -253
  74. package/src/cohort-topic/antidos/bootstrap-evidence.ts +106 -106
  75. package/src/cohort-topic/antidos/index.ts +5 -5
  76. package/src/cohort-topic/antidos/rate-limiter.ts +210 -210
  77. package/src/cohort-topic/antidos/replay-guard.ts +146 -146
  78. package/src/cohort-topic/antidos/topic-budget.ts +160 -160
  79. package/src/cohort-topic/antiflood/index.ts +2 -2
  80. package/src/cohort-topic/antiflood/invariants.ts +108 -108
  81. package/src/cohort-topic/antiflood/jitter.ts +117 -117
  82. package/src/cohort-topic/coldstart.ts +237 -237
  83. package/src/cohort-topic/dmax.ts +88 -88
  84. package/src/cohort-topic/gossip/bus.ts +254 -254
  85. package/src/cohort-topic/gossip/index.ts +3 -3
  86. package/src/cohort-topic/gossip/records.ts +45 -45
  87. package/src/cohort-topic/gossip/view.ts +91 -91
  88. package/src/cohort-topic/index.ts +20 -20
  89. package/src/cohort-topic/load/barometer.ts +134 -134
  90. package/src/cohort-topic/load/index.ts +1 -1
  91. package/src/cohort-topic/member-engine.ts +430 -430
  92. package/src/cohort-topic/membership/index.ts +3 -3
  93. package/src/cohort-topic/membership/publisher.ts +163 -163
  94. package/src/cohort-topic/membership/source.ts +41 -41
  95. package/src/cohort-topic/membership/verifier.ts +461 -461
  96. package/src/cohort-topic/ports.ts +157 -157
  97. package/src/cohort-topic/promotion.ts +405 -405
  98. package/src/cohort-topic/registration/bytes.ts +37 -37
  99. package/src/cohort-topic/registration/handoff.ts +154 -154
  100. package/src/cohort-topic/registration/index.ts +6 -6
  101. package/src/cohort-topic/registration/renewal.ts +495 -495
  102. package/src/cohort-topic/registration/sharding.ts +61 -61
  103. package/src/cohort-topic/registration/store.ts +81 -81
  104. package/src/cohort-topic/registration/types.ts +91 -91
  105. package/src/cohort-topic/ring-hash.ts +50 -50
  106. package/src/cohort-topic/service.ts +416 -416
  107. package/src/cohort-topic/sig/index.ts +2 -2
  108. package/src/cohort-topic/sig/payloads.ts +59 -59
  109. package/src/cohort-topic/sig/threshold.ts +64 -64
  110. package/src/cohort-topic/tiers.ts +74 -74
  111. package/src/cohort-topic/traffic.ts +233 -233
  112. package/src/cohort-topic/walk.ts +326 -326
  113. package/src/cohort-topic/willingness.ts +237 -237
  114. package/src/cohort-topic/wire/codec.ts +216 -216
  115. package/src/cohort-topic/wire/index.ts +18 -18
  116. package/src/cohort-topic/wire/payloads.ts +126 -126
  117. package/src/cohort-topic/wire/primitives.ts +188 -188
  118. package/src/cohort-topic/wire/types.ts +475 -475
  119. package/src/cohort-topic/wire/validate.ts +512 -512
  120. package/src/collection/collection-type-registry.ts +37 -37
  121. package/src/collection/collection.ts +32 -4
  122. package/src/collections/diary/diary.ts +68 -67
  123. package/src/collections/diary/struct.ts +1 -1
  124. package/src/collections/tree/collection-trunk.ts +1 -1
  125. package/src/collections/tree/readme.md +4 -0
  126. package/src/collections/tree/struct.ts +3 -1
  127. package/src/collections/tree/tree.ts +320 -312
  128. package/src/log/log.ts +2 -2
  129. package/src/matchmaking/capability-filter.ts +45 -45
  130. package/src/matchmaking/config.ts +98 -98
  131. package/src/matchmaking/index.ts +21 -21
  132. package/src/matchmaking/multi-cohort-seeker.ts +234 -234
  133. package/src/matchmaking/provider.ts +123 -123
  134. package/src/matchmaking/query-eval.ts +105 -105
  135. package/src/matchmaking/seeker-walk.ts +127 -127
  136. package/src/matchmaking/seeker.ts +86 -86
  137. package/src/matchmaking/topic-anchor.ts +90 -90
  138. package/src/matchmaking/voting-quorum.ts +394 -394
  139. package/src/matchmaking/wire.ts +603 -603
  140. package/src/network/i-peer-network.ts +17 -0
  141. package/src/network/stale-failure.ts +43 -43
  142. package/src/network/struct.ts +41 -2
  143. package/src/network/types.ts +37 -37
  144. package/src/reactivity/backfill.ts +220 -220
  145. package/src/reactivity/backpressure.ts +191 -191
  146. package/src/reactivity/checkpoint.ts +308 -308
  147. package/src/reactivity/config.ts +172 -172
  148. package/src/reactivity/dedupe.ts +132 -132
  149. package/src/reactivity/forwarder.ts +87 -87
  150. package/src/reactivity/index.ts +34 -34
  151. package/src/reactivity/notification.ts +123 -123
  152. package/src/reactivity/policy.ts +79 -79
  153. package/src/reactivity/push-state.ts +310 -310
  154. package/src/reactivity/recover.ts +153 -153
  155. package/src/reactivity/replay-buffer.ts +141 -141
  156. package/src/reactivity/resume.ts +549 -549
  157. package/src/reactivity/rotation.ts +415 -415
  158. package/src/reactivity/subscriber.ts +132 -132
  159. package/src/reactivity/subscription.ts +66 -66
  160. package/src/reactivity/topic-anchor.ts +71 -71
  161. package/src/reactivity/verify.ts +73 -73
  162. package/src/reactivity/wire-validate.ts +13 -13
  163. package/src/reactivity/wire.ts +224 -224
  164. package/src/testing/async-wait.ts +65 -65
  165. package/src/testing/index.ts +2 -2
  166. package/src/testing/test-transactor.ts +638 -489
  167. package/src/transaction/coordinator.ts +2 -1
  168. package/src/transaction/errors.ts +91 -91
  169. package/src/transaction/operations-hash.ts +196 -196
  170. package/src/transaction/read-dependency-collector.ts +78 -78
  171. package/src/transaction/transaction.ts +1 -1
  172. package/src/transactor/change-notifier.ts +80 -80
  173. package/src/transactor/index.ts +5 -5
  174. package/src/transactor/network-transactor.ts +58 -19
  175. package/src/transactor/transactor-source.ts +25 -2
  176. package/src/transform/atomic-proxy.ts +92 -92
  177. package/src/transform/cache-source.ts +1 -1
  178. package/src/transform/helpers.ts +159 -158
  179. package/src/transform/tracker.ts +2 -1
  180. package/src/utility/backoff.ts +95 -95
  181. package/src/utility/batch-coordinator.ts +191 -191
  182. package/dist/src/transaction/context.d.ts +0 -60
  183. package/dist/src/transaction/context.d.ts.map +0 -1
  184. package/dist/src/transaction/context.js +0 -91
  185. package/dist/src/transaction/context.js.map +0 -1
@@ -1,132 +1,132 @@
1
- /**
2
- * Reactivity — subscriber delivery path (`docs/reactivity.md` §Delivery).
3
- *
4
- * A subscriber receiving a notification:
5
- * 1. **verifies** `sig` against the cached tail-cohort `MembershipCertV1` (the {@link NotificationVerifier}
6
- * owns the **one fetch-and-retry** on a stale cache);
7
- * 2. checks `revision == lastRevision + 1`. If not, requests a backfill for the gap
8
- * `[lastRevision + 1, revision]` via the {@link requestBackfill} seam (the `BackfillV1` request shape
9
- * lands in [reactivity-backfill-resume-checkpoints]; this ticket only *detects* the gap and calls the
10
- * hook);
11
- * 3. updates `lastRevision` once revisions are contiguous;
12
- * 4. surfaces the notification to the application layer.
13
- *
14
- * Subscribers dedupe by `(collectionId, revision)`; duplicates from forwarder retries are discarded.
15
- * A fresh subscription (`lastKnownRev == 0`, uninitialized) adopts the first verified notification as its
16
- * baseline rather than demanding a backfill from revision 1.
17
- */
18
-
19
- import type { NotificationV1 } from "./wire.js";
20
- import type { NotificationVerifier } from "./verify.js";
21
-
22
- /** The delivery outcome for one inbound notification. */
23
- export type DeliveryOutcome =
24
- /** Verified and contiguous: surfaced to the application, `lastRevision` advanced. */
25
- | "delivered"
26
- /** `(collectionId, revision)` already delivered: discarded. */
27
- | "duplicate"
28
- /** Verified but ahead of `lastRevision + 1`: a backfill was requested; not yet surfaced. */
29
- | "gap"
30
- /** Signature did not verify (even after the one refetch): dropped. */
31
- | "untrusted"
32
- /** For a different collection than this subscription: ignored. */
33
- | "foreign";
34
-
35
- /** Application sink + backfill seam for a {@link ReactivitySubscriber}. */
36
- export interface ReactivitySubscriberDeps {
37
- /** Collection this subscription tracks, base64url (matches {@link NotificationV1.collectionId}). */
38
- readonly collectionId: string;
39
- /** Verifies inbound notifications (owns the single stale-cache refetch). */
40
- readonly verifier: NotificationVerifier;
41
- /** Surface a verified, contiguous notification to the application. */
42
- readonly deliver: (n: NotificationV1) => void;
43
- /**
44
- * Request a backfill for the inclusive revision gap `[from, to]`. The `BackfillV1` request/response
45
- * lands in [reactivity-backfill-resume-checkpoints]; the returned entries are fed back through
46
- * {@link ReactivitySubscriber.onNotification} to close the gap. Absent ⇒ the gap is recorded only.
47
- */
48
- readonly requestBackfill?: (from: number, to: number) => void;
49
- /** Last revision already held; `0` (default) ⇒ fresh subscribe, adopt the first notification as baseline. */
50
- readonly lastKnownRev?: number;
51
- }
52
-
53
- /** Drives the subscriber-side verify → contiguity → deliver path for one collection. */
54
- export interface ReactivitySubscriber {
55
- /** Last contiguously-delivered revision (`lastKnownRev` until the first delivery). */
56
- readonly lastRevision: number;
57
- /** Process one inbound notification; returns the {@link DeliveryOutcome}. */
58
- onNotification(n: NotificationV1): Promise<DeliveryOutcome>;
59
- /**
60
- * Advance the contiguity head to `revision` because a **verified parent checkpoint** summarized every
61
- * revision up to it (`docs/reactivity.md` §Parent checkpoint summaries). The subscriber then treats
62
- * `revision + 1` as the next contiguous revision, so the checkpoint's `recentEntries` replay without a
63
- * spurious gap/backfill for the digest-covered range. Only advances — never rewinds past delivered
64
- * state. The merged digest itself is applied by the application; this just moves the contiguity head.
65
- */
66
- rebaseline(revision: number): void;
67
- }
68
-
69
- class CollectionSubscriber implements ReactivitySubscriber {
70
- private last: number;
71
- private initialized: boolean;
72
-
73
- constructor(private readonly deps: ReactivitySubscriberDeps) {
74
- const lastKnownRev = deps.lastKnownRev ?? 0;
75
- this.last = lastKnownRev;
76
- // A fresh subscribe (lastKnownRev == 0) has no baseline yet; the first verified notification sets it.
77
- this.initialized = lastKnownRev > 0;
78
- }
79
-
80
- get lastRevision(): number {
81
- return this.last;
82
- }
83
-
84
- rebaseline(revision: number): void {
85
- // A verified checkpoint summarized everything up to `revision`; jump the head forward (never back).
86
- if (!this.initialized || revision > this.last) {
87
- this.last = revision;
88
- this.initialized = true;
89
- }
90
- }
91
-
92
- async onNotification(n: NotificationV1): Promise<DeliveryOutcome> {
93
- if (n.collectionId !== this.deps.collectionId) {
94
- return "foreign";
95
- }
96
- // Verify first (one fetch-and-retry is internal to the verifier). An untrusted notification is
97
- // dropped before any (collectionId, revision) dedupe or lastRevision update.
98
- const verdict = await this.deps.verifier.verify(n);
99
- if (verdict !== "verified") {
100
- return "untrusted";
101
- }
102
-
103
- // Fresh subscription: adopt the first verified notification as the contiguity baseline.
104
- if (!this.initialized) {
105
- this.initialized = true;
106
- this.last = n.revision;
107
- this.deps.deliver(n);
108
- return "delivered";
109
- }
110
-
111
- // Dedupe by (collectionId, revision): anything at or below the contiguous head is already delivered.
112
- if (n.revision <= this.last) {
113
- return "duplicate";
114
- }
115
-
116
- // Contiguity check.
117
- if (n.revision === this.last + 1) {
118
- this.last = n.revision;
119
- this.deps.deliver(n);
120
- return "delivered";
121
- }
122
-
123
- // Gap: request the missing range (inclusive of `revision`); the backfilled entries re-enter here.
124
- this.deps.requestBackfill?.(this.last + 1, n.revision);
125
- return "gap";
126
- }
127
- }
128
-
129
- /** Build a {@link ReactivitySubscriber} for one collection. */
130
- export function createReactivitySubscriber(deps: ReactivitySubscriberDeps): ReactivitySubscriber {
131
- return new CollectionSubscriber(deps);
132
- }
1
+ /**
2
+ * Reactivity — subscriber delivery path (`docs/reactivity.md` §Delivery).
3
+ *
4
+ * A subscriber receiving a notification:
5
+ * 1. **verifies** `sig` against the cached tail-cohort `MembershipCertV1` (the {@link NotificationVerifier}
6
+ * owns the **one fetch-and-retry** on a stale cache);
7
+ * 2. checks `revision == lastRevision + 1`. If not, requests a backfill for the gap
8
+ * `[lastRevision + 1, revision]` via the {@link requestBackfill} seam (the `BackfillV1` request shape
9
+ * lands in [reactivity-backfill-resume-checkpoints]; this ticket only *detects* the gap and calls the
10
+ * hook);
11
+ * 3. updates `lastRevision` once revisions are contiguous;
12
+ * 4. surfaces the notification to the application layer.
13
+ *
14
+ * Subscribers dedupe by `(collectionId, revision)`; duplicates from forwarder retries are discarded.
15
+ * A fresh subscription (`lastKnownRev == 0`, uninitialized) adopts the first verified notification as its
16
+ * baseline rather than demanding a backfill from revision 1.
17
+ */
18
+
19
+ import type { NotificationV1 } from "./wire.js";
20
+ import type { NotificationVerifier } from "./verify.js";
21
+
22
+ /** The delivery outcome for one inbound notification. */
23
+ export type DeliveryOutcome =
24
+ /** Verified and contiguous: surfaced to the application, `lastRevision` advanced. */
25
+ | "delivered"
26
+ /** `(collectionId, revision)` already delivered: discarded. */
27
+ | "duplicate"
28
+ /** Verified but ahead of `lastRevision + 1`: a backfill was requested; not yet surfaced. */
29
+ | "gap"
30
+ /** Signature did not verify (even after the one refetch): dropped. */
31
+ | "untrusted"
32
+ /** For a different collection than this subscription: ignored. */
33
+ | "foreign";
34
+
35
+ /** Application sink + backfill seam for a {@link ReactivitySubscriber}. */
36
+ export interface ReactivitySubscriberDeps {
37
+ /** Collection this subscription tracks, base64url (matches {@link NotificationV1.collectionId}). */
38
+ readonly collectionId: string;
39
+ /** Verifies inbound notifications (owns the single stale-cache refetch). */
40
+ readonly verifier: NotificationVerifier;
41
+ /** Surface a verified, contiguous notification to the application. */
42
+ readonly deliver: (n: NotificationV1) => void;
43
+ /**
44
+ * Request a backfill for the inclusive revision gap `[from, to]`. The `BackfillV1` request/response
45
+ * lands in [reactivity-backfill-resume-checkpoints]; the returned entries are fed back through
46
+ * {@link ReactivitySubscriber.onNotification} to close the gap. Absent ⇒ the gap is recorded only.
47
+ */
48
+ readonly requestBackfill?: (from: number, to: number) => void;
49
+ /** Last revision already held; `0` (default) ⇒ fresh subscribe, adopt the first notification as baseline. */
50
+ readonly lastKnownRev?: number;
51
+ }
52
+
53
+ /** Drives the subscriber-side verify → contiguity → deliver path for one collection. */
54
+ export interface ReactivitySubscriber {
55
+ /** Last contiguously-delivered revision (`lastKnownRev` until the first delivery). */
56
+ readonly lastRevision: number;
57
+ /** Process one inbound notification; returns the {@link DeliveryOutcome}. */
58
+ onNotification(n: NotificationV1): Promise<DeliveryOutcome>;
59
+ /**
60
+ * Advance the contiguity head to `revision` because a **verified parent checkpoint** summarized every
61
+ * revision up to it (`docs/reactivity.md` §Parent checkpoint summaries). The subscriber then treats
62
+ * `revision + 1` as the next contiguous revision, so the checkpoint's `recentEntries` replay without a
63
+ * spurious gap/backfill for the digest-covered range. Only advances — never rewinds past delivered
64
+ * state. The merged digest itself is applied by the application; this just moves the contiguity head.
65
+ */
66
+ rebaseline(revision: number): void;
67
+ }
68
+
69
+ class CollectionSubscriber implements ReactivitySubscriber {
70
+ private last: number;
71
+ private initialized: boolean;
72
+
73
+ constructor(private readonly deps: ReactivitySubscriberDeps) {
74
+ const lastKnownRev = deps.lastKnownRev ?? 0;
75
+ this.last = lastKnownRev;
76
+ // A fresh subscribe (lastKnownRev == 0) has no baseline yet; the first verified notification sets it.
77
+ this.initialized = lastKnownRev > 0;
78
+ }
79
+
80
+ get lastRevision(): number {
81
+ return this.last;
82
+ }
83
+
84
+ rebaseline(revision: number): void {
85
+ // A verified checkpoint summarized everything up to `revision`; jump the head forward (never back).
86
+ if (!this.initialized || revision > this.last) {
87
+ this.last = revision;
88
+ this.initialized = true;
89
+ }
90
+ }
91
+
92
+ async onNotification(n: NotificationV1): Promise<DeliveryOutcome> {
93
+ if (n.collectionId !== this.deps.collectionId) {
94
+ return "foreign";
95
+ }
96
+ // Verify first (one fetch-and-retry is internal to the verifier). An untrusted notification is
97
+ // dropped before any (collectionId, revision) dedupe or lastRevision update.
98
+ const verdict = await this.deps.verifier.verify(n);
99
+ if (verdict !== "verified") {
100
+ return "untrusted";
101
+ }
102
+
103
+ // Fresh subscription: adopt the first verified notification as the contiguity baseline.
104
+ if (!this.initialized) {
105
+ this.initialized = true;
106
+ this.last = n.revision;
107
+ this.deps.deliver(n);
108
+ return "delivered";
109
+ }
110
+
111
+ // Dedupe by (collectionId, revision): anything at or below the contiguous head is already delivered.
112
+ if (n.revision <= this.last) {
113
+ return "duplicate";
114
+ }
115
+
116
+ // Contiguity check.
117
+ if (n.revision === this.last + 1) {
118
+ this.last = n.revision;
119
+ this.deps.deliver(n);
120
+ return "delivered";
121
+ }
122
+
123
+ // Gap: request the missing range (inclusive of `revision`); the backfilled entries re-enter here.
124
+ this.deps.requestBackfill?.(this.last + 1, n.revision);
125
+ return "gap";
126
+ }
127
+ }
128
+
129
+ /** Build a {@link ReactivitySubscriber} for one collection. */
130
+ export function createReactivitySubscriber(deps: ReactivitySubscriberDeps): ReactivitySubscriber {
131
+ return new CollectionSubscriber(deps);
132
+ }
@@ -1,66 +1,66 @@
1
- /**
2
- * Reactivity — subscriber-side subscription state (`docs/reactivity.md` §Subscription).
3
- *
4
- * Subscribing to a collection is an **ordinary cohort-topic registration** at tier T3 with a reactivity
5
- * `appPayload`: `topicId = H(currentTailId(C) ‖ "reactivity")`, the configured TTL (Edge 60 s / Core 90 s),
6
- * and the cohort-topic walk-toward-root / willingness / promotion / TTL-renewal all reused unchanged.
7
- * This module owns only the subscriber-side bookkeeping struct and the `appPayload` builder; the db-p2p
8
- * subscription manager drives the cohort-topic `RegisterV1`.
9
- *
10
- * `tailIdAtAttach` is the subscriber-side detector for tail rotation (the whole-tree migration the
11
- * rotation ticket handles); `cohortEpoch` detects membership drift within the topic.
12
- */
13
-
14
- import { encodeSubscribeAppPayload, type SubscribeAppPayloadV1 } from "./wire.js";
15
-
16
- /** Subscriber-side live state for one active subscription (`docs/reactivity.md` §Subscriber-side state). */
17
- export interface ActiveSubscription {
18
- /** Stable collection identity. */
19
- readonly collectionId: Uint8Array;
20
- /** Current tail-anchored topic id. */
21
- readonly topicId: Uint8Array;
22
- /** Tail block id at registration time — detects tail rotation. */
23
- readonly tailIdAtAttach: Uint8Array;
24
- /** Serving cohort member. */
25
- primary: Uint8Array;
26
- /** Warm-failover cohort members. */
27
- backups: Uint8Array[];
28
- /** Cohort member hint set for fast re-attach. */
29
- cohortHint: Uint8Array[];
30
- /** Cohort epoch for membership-drift detection. */
31
- cohortEpoch: Uint8Array;
32
- /** Last contiguously-delivered revision. */
33
- lastRevision: number;
34
- /** Unix ms of the last delivery. */
35
- lastDeliveredAt: number;
36
- /** Unix ms the subscription attached. */
37
- attachedAt: number;
38
- }
39
-
40
- /** Parameters for building a subscribe `appPayload`. */
41
- export interface SubscribeParams {
42
- /** Collection id, base64url. */
43
- readonly collectionId: string;
44
- /** Tail block id at attach time, base64url. */
45
- readonly tailIdAtAttach: string;
46
- /** Last revision already held; `0` for a fresh subscribe. */
47
- readonly lastKnownRev?: number;
48
- /** Max delta bytes accepted; `0` declines deltas (Edge). */
49
- readonly deltaMaxBytes: number;
50
- }
51
-
52
- /** Build the validated {@link SubscribeAppPayloadV1} for a subscription. */
53
- export function buildSubscribeAppPayload(params: SubscribeParams): SubscribeAppPayloadV1 {
54
- return {
55
- kind: "reactivity",
56
- collectionId: params.collectionId,
57
- tailIdAtAttach: params.tailIdAtAttach,
58
- lastKnownRev: params.lastKnownRev ?? 0,
59
- deltaMaxBytes: params.deltaMaxBytes,
60
- };
61
- }
62
-
63
- /** Build the opaque `RegisterV1.appPayload` bytes for a subscription. */
64
- export function subscribeAppPayloadBytes(params: SubscribeParams): Uint8Array {
65
- return encodeSubscribeAppPayload(buildSubscribeAppPayload(params));
66
- }
1
+ /**
2
+ * Reactivity — subscriber-side subscription state (`docs/reactivity.md` §Subscription).
3
+ *
4
+ * Subscribing to a collection is an **ordinary cohort-topic registration** at tier T3 with a reactivity
5
+ * `appPayload`: `topicId = H(currentTailId(C) ‖ "reactivity")`, the configured TTL (Edge 60 s / Core 90 s),
6
+ * and the cohort-topic walk-toward-root / willingness / promotion / TTL-renewal all reused unchanged.
7
+ * This module owns only the subscriber-side bookkeeping struct and the `appPayload` builder; the db-p2p
8
+ * subscription manager drives the cohort-topic `RegisterV1`.
9
+ *
10
+ * `tailIdAtAttach` is the subscriber-side detector for tail rotation (the whole-tree migration the
11
+ * rotation ticket handles); `cohortEpoch` detects membership drift within the topic.
12
+ */
13
+
14
+ import { encodeSubscribeAppPayload, type SubscribeAppPayloadV1 } from "./wire.js";
15
+
16
+ /** Subscriber-side live state for one active subscription (`docs/reactivity.md` §Subscriber-side state). */
17
+ export interface ActiveSubscription {
18
+ /** Stable collection identity. */
19
+ readonly collectionId: Uint8Array;
20
+ /** Current tail-anchored topic id. */
21
+ readonly topicId: Uint8Array;
22
+ /** Tail block id at registration time — detects tail rotation. */
23
+ readonly tailIdAtAttach: Uint8Array;
24
+ /** Serving cohort member. */
25
+ primary: Uint8Array;
26
+ /** Warm-failover cohort members. */
27
+ backups: Uint8Array[];
28
+ /** Cohort member hint set for fast re-attach. */
29
+ cohortHint: Uint8Array[];
30
+ /** Cohort epoch for membership-drift detection. */
31
+ cohortEpoch: Uint8Array;
32
+ /** Last contiguously-delivered revision. */
33
+ lastRevision: number;
34
+ /** Unix ms of the last delivery. */
35
+ lastDeliveredAt: number;
36
+ /** Unix ms the subscription attached. */
37
+ attachedAt: number;
38
+ }
39
+
40
+ /** Parameters for building a subscribe `appPayload`. */
41
+ export interface SubscribeParams {
42
+ /** Collection id, base64url. */
43
+ readonly collectionId: string;
44
+ /** Tail block id at attach time, base64url. */
45
+ readonly tailIdAtAttach: string;
46
+ /** Last revision already held; `0` for a fresh subscribe. */
47
+ readonly lastKnownRev?: number;
48
+ /** Max delta bytes accepted; `0` declines deltas (Edge). */
49
+ readonly deltaMaxBytes: number;
50
+ }
51
+
52
+ /** Build the validated {@link SubscribeAppPayloadV1} for a subscription. */
53
+ export function buildSubscribeAppPayload(params: SubscribeParams): SubscribeAppPayloadV1 {
54
+ return {
55
+ kind: "reactivity",
56
+ collectionId: params.collectionId,
57
+ tailIdAtAttach: params.tailIdAtAttach,
58
+ lastKnownRev: params.lastKnownRev ?? 0,
59
+ deltaMaxBytes: params.deltaMaxBytes,
60
+ };
61
+ }
62
+
63
+ /** Build the opaque `RegisterV1.appPayload` bytes for a subscription. */
64
+ export function subscribeAppPayloadBytes(params: SubscribeParams): Uint8Array {
65
+ return encodeSubscribeAppPayload(buildSubscribeAppPayload(params));
66
+ }
@@ -1,71 +1,71 @@
1
- /**
2
- * Reactivity — rotating, tail-anchored topic.
3
- *
4
- * Transcribed from `docs/reactivity.md` §Anchor:
5
- *
6
- * ```
7
- * topicId(collection C, tail T) = H(T.blockId ‖ "reactivity")
8
- * ```
9
- *
10
- * Unlike matchmaking (whose anchor is a stable `(kind, label)`), the reactivity anchor **rotates** with
11
- * the collection's tail block: when the tail fills and a new tail block is born, `topicId` changes and
12
- * the cohort-topic layer treats it as an entirely new topic. The whole-tree migration that follows is
13
- * the rotation ticket's concern; this module owns only the pure derivation.
14
- *
15
- * The resulting `topicId` is fed verbatim into cohort-topic tier addressing (`coord_d(self, topicId)`),
16
- * so it is derived with the **same** {@link IRingHash} primitive cohort-topic uses for `coord_d` input —
17
- * db-core's own SHA-256 truncated to the ring width, **not** a FRET import. The trailing `"reactivity"`
18
- * literal domain-separates the reactivity tree from any other application anchoring on the same tail id.
19
- *
20
- * Concatenation is delimiter-free, exactly as the spec writes it; the constant suffix is unambiguous
21
- * because no tail-id byte string can alias a `(tailId, "reactivity")` pair of a different length.
22
- *
23
- * `reactivityTopicId` is the small pure helper the ticket asks for once and the rotation ticket
24
- * ([reactivity-rotation-backpressure-policy]) reuses per-emission — define it here, not at the call site.
25
- */
26
-
27
- import { createRingHash } from "../cohort-topic/ring-hash.js";
28
- import type { IRingHash } from "../cohort-topic/ports.js";
29
-
30
- /** Domain-separation suffix mixed into every reactivity anchor. */
31
- const REACTIVITY_SUFFIX = "reactivity";
32
-
33
- const utf8 = new TextEncoder();
34
-
35
- /**
36
- * `H(tailId ‖ "reactivity")` over the injected ring hash — the cohort-topic `topicId` for the
37
- * reactivity tree anchored on `tailId` (the collection's current tail block id, as raw bytes).
38
- *
39
- * The shared helper consumed by subscriber attachment, origination, and (per-emission) the rotation
40
- * ticket. `hash` defaults to db-core's own 256-bit SHA-256, byte-identical to the cohort-topic host's.
41
- */
42
- export function reactivityTopicId(tailId: Uint8Array, hash: IRingHash = createRingHash()): Uint8Array {
43
- const suffixBytes = utf8.encode(REACTIVITY_SUFFIX);
44
- const input = new Uint8Array(tailId.length + suffixBytes.length);
45
- input.set(tailId, 0);
46
- input.set(suffixBytes, tailId.length);
47
- return hash.H(input);
48
- }
49
-
50
- /** Derives the rotating `topicId` for a collection's current tail. */
51
- export interface ReactivityTopicAnchor {
52
- /** `H(tailId ‖ "reactivity")` for the given tail block id (bytes). */
53
- topicId(tailId: Uint8Array): Uint8Array;
54
- }
55
-
56
- class HashReactivityTopicAnchor implements ReactivityTopicAnchor {
57
- constructor(private readonly hash: IRingHash) {}
58
-
59
- topicId(tailId: Uint8Array): Uint8Array {
60
- return reactivityTopicId(tailId, this.hash);
61
- }
62
- }
63
-
64
- /**
65
- * Build a {@link ReactivityTopicAnchor} over the injected hash. db-p2p passes the same {@link IRingHash}
66
- * it binds to FRET's `RING_BITS` so the anchor and cohort-topic routing keys line up; the default
67
- * constructs db-core's own {@link createRingHash} (256-bit SHA-256).
68
- */
69
- export function createReactivityTopicAnchor(hash: IRingHash = createRingHash()): ReactivityTopicAnchor {
70
- return new HashReactivityTopicAnchor(hash);
71
- }
1
+ /**
2
+ * Reactivity — rotating, tail-anchored topic.
3
+ *
4
+ * Transcribed from `docs/reactivity.md` §Anchor:
5
+ *
6
+ * ```
7
+ * topicId(collection C, tail T) = H(T.blockId ‖ "reactivity")
8
+ * ```
9
+ *
10
+ * Unlike matchmaking (whose anchor is a stable `(kind, label)`), the reactivity anchor **rotates** with
11
+ * the collection's tail block: when the tail fills and a new tail block is born, `topicId` changes and
12
+ * the cohort-topic layer treats it as an entirely new topic. The whole-tree migration that follows is
13
+ * the rotation ticket's concern; this module owns only the pure derivation.
14
+ *
15
+ * The resulting `topicId` is fed verbatim into cohort-topic tier addressing (`coord_d(self, topicId)`),
16
+ * so it is derived with the **same** {@link IRingHash} primitive cohort-topic uses for `coord_d` input —
17
+ * db-core's own SHA-256 truncated to the ring width, **not** a FRET import. The trailing `"reactivity"`
18
+ * literal domain-separates the reactivity tree from any other application anchoring on the same tail id.
19
+ *
20
+ * Concatenation is delimiter-free, exactly as the spec writes it; the constant suffix is unambiguous
21
+ * because no tail-id byte string can alias a `(tailId, "reactivity")` pair of a different length.
22
+ *
23
+ * `reactivityTopicId` is the small pure helper the ticket asks for once and the rotation ticket
24
+ * ([reactivity-rotation-backpressure-policy]) reuses per-emission — define it here, not at the call site.
25
+ */
26
+
27
+ import { createRingHash } from "../cohort-topic/ring-hash.js";
28
+ import type { IRingHash } from "../cohort-topic/ports.js";
29
+
30
+ /** Domain-separation suffix mixed into every reactivity anchor. */
31
+ const REACTIVITY_SUFFIX = "reactivity";
32
+
33
+ const utf8 = new TextEncoder();
34
+
35
+ /**
36
+ * `H(tailId ‖ "reactivity")` over the injected ring hash — the cohort-topic `topicId` for the
37
+ * reactivity tree anchored on `tailId` (the collection's current tail block id, as raw bytes).
38
+ *
39
+ * The shared helper consumed by subscriber attachment, origination, and (per-emission) the rotation
40
+ * ticket. `hash` defaults to db-core's own 256-bit SHA-256, byte-identical to the cohort-topic host's.
41
+ */
42
+ export function reactivityTopicId(tailId: Uint8Array, hash: IRingHash = createRingHash()): Uint8Array {
43
+ const suffixBytes = utf8.encode(REACTIVITY_SUFFIX);
44
+ const input = new Uint8Array(tailId.length + suffixBytes.length);
45
+ input.set(tailId, 0);
46
+ input.set(suffixBytes, tailId.length);
47
+ return hash.H(input);
48
+ }
49
+
50
+ /** Derives the rotating `topicId` for a collection's current tail. */
51
+ export interface ReactivityTopicAnchor {
52
+ /** `H(tailId ‖ "reactivity")` for the given tail block id (bytes). */
53
+ topicId(tailId: Uint8Array): Uint8Array;
54
+ }
55
+
56
+ class HashReactivityTopicAnchor implements ReactivityTopicAnchor {
57
+ constructor(private readonly hash: IRingHash) {}
58
+
59
+ topicId(tailId: Uint8Array): Uint8Array {
60
+ return reactivityTopicId(tailId, this.hash);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Build a {@link ReactivityTopicAnchor} over the injected hash. db-p2p passes the same {@link IRingHash}
66
+ * it binds to FRET's `RING_BITS` so the anchor and cohort-topic routing keys line up; the default
67
+ * constructs db-core's own {@link createRingHash} (256-bit SHA-256).
68
+ */
69
+ export function createReactivityTopicAnchor(hash: IRingHash = createRingHash()): ReactivityTopicAnchor {
70
+ return new HashReactivityTopicAnchor(hash);
71
+ }