@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,123 +1,123 @@
1
- /**
2
- * Matchmaking — provider decision/state (db-core, transport-agnostic).
3
- *
4
- * A {@link MatchmakingProvider} owns the live provider state for one topic: its capability tags, its
5
- * current `capacityBudget`, and a correlation id (registration identity; not bound into the
6
- * matchmaking signature — see {@link providerSigningPayload} option (b)). It builds the
7
- * signed {@link ProviderAppPayloadV1} (and the opaque bytes for the cohort-topic
8
- * `RegisterV1.appPayload` slot) that the db-p2p `provider-manager` registers at cohort-topic tier
9
- * **T2** (`docs/matchmaking.md` §Provider registration).
10
- *
11
- * It is crypto-free: signing is an injected callback (db-p2p supplies the libp2p peer key), matching
12
- * the cohort-topic {@link import("../cohort-topic/service.js").ParticipantSigner} pattern.
13
- *
14
- * Self-throttling (`docs/matchmaking.md` §Provider self-throttling) is expressed here as state:
15
- * - **Signal full** — {@link MatchmakingProvider.signalFull} sets `capacityBudget = 0`; the provider
16
- * stays listed as "available but at capacity". The manager re-registers to push the new payload
17
- * (the cohort-topic `RenewV1` carries no `appPayload`, so a capacity change is a re-register, not a
18
- * ping — see the implement handoff).
19
- * - **Withdraw** — {@link MatchmakingProvider.markWithdrawn} records intent; the manager stops
20
- * renewing so the record ages out by TTL. Withdrawal is an **optimization, not a correctness
21
- * requirement** (§Provider self-throttling, GROUNDING resolution): a non-withdrawn registration is
22
- * bounded by TTL eviction.
23
- */
24
-
25
- import { randomBytes } from "@noble/hashes/utils.js";
26
- import { providerSigningPayload, type ProviderAppPayloadV1, encodeProviderAppPayload } from "./wire.js";
27
-
28
- /** Construction inputs for a {@link MatchmakingProvider}. */
29
- export interface MatchmakingProviderOptions {
30
- /** The matchmaking topic this provider serves (from {@link import("./topic-anchor.js").MatchTopicAnchor}). */
31
- readonly topicId: Uint8Array;
32
- /** Application-defined capability tags. */
33
- readonly capabilities: readonly string[];
34
- /** Initial concurrent-task budget (integer `>= 0`). */
35
- readonly capacityBudget: number;
36
- /** Multiaddr or PeerId-based callback. */
37
- readonly contactHint: string;
38
- /** Optional soft expiry hint (unix ms). */
39
- readonly serviceUntil?: number;
40
- /** Sign the canonical registration image; resolves the base64url signature. */
41
- readonly sign: (payload: Uint8Array) => Promise<string>;
42
- /** 16-byte registration correlation id (not signature-bound); default fresh CSPRNG bytes. */
43
- readonly correlationId?: Uint8Array;
44
- /** CSPRNG source (injectable for deterministic tests). Default `@noble/hashes` `randomBytes`. */
45
- readonly randomBytes?: (n: number) => Uint8Array;
46
- }
47
-
48
- /** Live provider state + signed-payload builder for one matchmaking topic. */
49
- export class MatchmakingProvider {
50
- readonly topicId: Uint8Array;
51
- readonly correlationId: Uint8Array;
52
- private readonly capabilities: readonly string[];
53
- private readonly contactHint: string;
54
- private readonly serviceUntil?: number;
55
- private readonly sign: (payload: Uint8Array) => Promise<string>;
56
- private capacity: number;
57
- private withdrawnFlag = false;
58
-
59
- constructor(options: MatchmakingProviderOptions) {
60
- this.topicId = options.topicId;
61
- this.capabilities = [...options.capabilities];
62
- this.contactHint = options.contactHint;
63
- this.serviceUntil = options.serviceUntil;
64
- this.sign = options.sign;
65
- this.capacity = requireBudget(options.capacityBudget);
66
- const rand = options.randomBytes ?? randomBytes;
67
- this.correlationId = options.correlationId ?? rand(16);
68
- }
69
-
70
- /** Current concurrent-task budget; `0` means "listed but full". */
71
- get capacityBudget(): number {
72
- return this.capacity;
73
- }
74
-
75
- /** True once {@link markWithdrawn} has been called (the manager should stop renewing). */
76
- get withdrawn(): boolean {
77
- return this.withdrawnFlag;
78
- }
79
-
80
- /** Set the live budget (integer `>= 0`). The next built payload reflects it. */
81
- setCapacity(budget: number): void {
82
- this.capacity = requireBudget(budget);
83
- }
84
-
85
- /** Signal "available but at capacity" by setting `capacityBudget = 0` (§Provider self-throttling). */
86
- signalFull(): void {
87
- this.capacity = 0;
88
- }
89
-
90
- /** Record withdrawal intent; the manager stops renewing so the record TTL-expires (optimization). */
91
- markWithdrawn(): void {
92
- this.withdrawnFlag = true;
93
- }
94
-
95
- /** Build the signed {@link ProviderAppPayloadV1} reflecting the current capacity. */
96
- async buildAppPayload(): Promise<ProviderAppPayloadV1> {
97
- const signature = await this.sign(providerSigningPayload(this.topicId, this.capabilities, this.capacity));
98
- const payload: ProviderAppPayloadV1 = {
99
- kind: "match-provider",
100
- capabilities: [...this.capabilities],
101
- capacityBudget: this.capacity,
102
- contactHint: this.contactHint,
103
- signature,
104
- };
105
- if (this.serviceUntil !== undefined) {
106
- payload.serviceUntil = this.serviceUntil;
107
- }
108
- return payload;
109
- }
110
-
111
- /** Build the opaque bytes for the cohort-topic `RegisterV1.appPayload` slot. */
112
- async appPayloadBytes(): Promise<Uint8Array> {
113
- return encodeProviderAppPayload(await this.buildAppPayload());
114
- }
115
- }
116
-
117
- /** Validate a capacity budget is an integer `>= 0`. */
118
- function requireBudget(budget: number): number {
119
- if (!Number.isInteger(budget) || budget < 0) {
120
- throw new RangeError(`matchmaking provider: capacityBudget must be an integer >= 0, got ${budget}`);
121
- }
122
- return budget;
123
- }
1
+ /**
2
+ * Matchmaking — provider decision/state (db-core, transport-agnostic).
3
+ *
4
+ * A {@link MatchmakingProvider} owns the live provider state for one topic: its capability tags, its
5
+ * current `capacityBudget`, and a correlation id (registration identity; not bound into the
6
+ * matchmaking signature — see {@link providerSigningPayload} option (b)). It builds the
7
+ * signed {@link ProviderAppPayloadV1} (and the opaque bytes for the cohort-topic
8
+ * `RegisterV1.appPayload` slot) that the db-p2p `provider-manager` registers at cohort-topic tier
9
+ * **T2** (`docs/matchmaking.md` §Provider registration).
10
+ *
11
+ * It is crypto-free: signing is an injected callback (db-p2p supplies the libp2p peer key), matching
12
+ * the cohort-topic {@link import("../cohort-topic/service.js").ParticipantSigner} pattern.
13
+ *
14
+ * Self-throttling (`docs/matchmaking.md` §Provider self-throttling) is expressed here as state:
15
+ * - **Signal full** — {@link MatchmakingProvider.signalFull} sets `capacityBudget = 0`; the provider
16
+ * stays listed as "available but at capacity". The manager re-registers to push the new payload
17
+ * (the cohort-topic `RenewV1` carries no `appPayload`, so a capacity change is a re-register, not a
18
+ * ping — see the implement handoff).
19
+ * - **Withdraw** — {@link MatchmakingProvider.markWithdrawn} records intent; the manager stops
20
+ * renewing so the record ages out by TTL. Withdrawal is an **optimization, not a correctness
21
+ * requirement** (§Provider self-throttling, GROUNDING resolution): a non-withdrawn registration is
22
+ * bounded by TTL eviction.
23
+ */
24
+
25
+ import { randomBytes } from "@noble/hashes/utils.js";
26
+ import { providerSigningPayload, type ProviderAppPayloadV1, encodeProviderAppPayload } from "./wire.js";
27
+
28
+ /** Construction inputs for a {@link MatchmakingProvider}. */
29
+ export interface MatchmakingProviderOptions {
30
+ /** The matchmaking topic this provider serves (from {@link import("./topic-anchor.js").MatchTopicAnchor}). */
31
+ readonly topicId: Uint8Array;
32
+ /** Application-defined capability tags. */
33
+ readonly capabilities: readonly string[];
34
+ /** Initial concurrent-task budget (integer `>= 0`). */
35
+ readonly capacityBudget: number;
36
+ /** Multiaddr or PeerId-based callback. */
37
+ readonly contactHint: string;
38
+ /** Optional soft expiry hint (unix ms). */
39
+ readonly serviceUntil?: number;
40
+ /** Sign the canonical registration image; resolves the base64url signature. */
41
+ readonly sign: (payload: Uint8Array) => Promise<string>;
42
+ /** 16-byte registration correlation id (not signature-bound); default fresh CSPRNG bytes. */
43
+ readonly correlationId?: Uint8Array;
44
+ /** CSPRNG source (injectable for deterministic tests). Default `@noble/hashes` `randomBytes`. */
45
+ readonly randomBytes?: (n: number) => Uint8Array;
46
+ }
47
+
48
+ /** Live provider state + signed-payload builder for one matchmaking topic. */
49
+ export class MatchmakingProvider {
50
+ readonly topicId: Uint8Array;
51
+ readonly correlationId: Uint8Array;
52
+ private readonly capabilities: readonly string[];
53
+ private readonly contactHint: string;
54
+ private readonly serviceUntil?: number;
55
+ private readonly sign: (payload: Uint8Array) => Promise<string>;
56
+ private capacity: number;
57
+ private withdrawnFlag = false;
58
+
59
+ constructor(options: MatchmakingProviderOptions) {
60
+ this.topicId = options.topicId;
61
+ this.capabilities = [...options.capabilities];
62
+ this.contactHint = options.contactHint;
63
+ this.serviceUntil = options.serviceUntil;
64
+ this.sign = options.sign;
65
+ this.capacity = requireBudget(options.capacityBudget);
66
+ const rand = options.randomBytes ?? randomBytes;
67
+ this.correlationId = options.correlationId ?? rand(16);
68
+ }
69
+
70
+ /** Current concurrent-task budget; `0` means "listed but full". */
71
+ get capacityBudget(): number {
72
+ return this.capacity;
73
+ }
74
+
75
+ /** True once {@link markWithdrawn} has been called (the manager should stop renewing). */
76
+ get withdrawn(): boolean {
77
+ return this.withdrawnFlag;
78
+ }
79
+
80
+ /** Set the live budget (integer `>= 0`). The next built payload reflects it. */
81
+ setCapacity(budget: number): void {
82
+ this.capacity = requireBudget(budget);
83
+ }
84
+
85
+ /** Signal "available but at capacity" by setting `capacityBudget = 0` (§Provider self-throttling). */
86
+ signalFull(): void {
87
+ this.capacity = 0;
88
+ }
89
+
90
+ /** Record withdrawal intent; the manager stops renewing so the record TTL-expires (optimization). */
91
+ markWithdrawn(): void {
92
+ this.withdrawnFlag = true;
93
+ }
94
+
95
+ /** Build the signed {@link ProviderAppPayloadV1} reflecting the current capacity. */
96
+ async buildAppPayload(): Promise<ProviderAppPayloadV1> {
97
+ const signature = await this.sign(providerSigningPayload(this.topicId, this.capabilities, this.capacity));
98
+ const payload: ProviderAppPayloadV1 = {
99
+ kind: "match-provider",
100
+ capabilities: [...this.capabilities],
101
+ capacityBudget: this.capacity,
102
+ contactHint: this.contactHint,
103
+ signature,
104
+ };
105
+ if (this.serviceUntil !== undefined) {
106
+ payload.serviceUntil = this.serviceUntil;
107
+ }
108
+ return payload;
109
+ }
110
+
111
+ /** Build the opaque bytes for the cohort-topic `RegisterV1.appPayload` slot. */
112
+ async appPayloadBytes(): Promise<Uint8Array> {
113
+ return encodeProviderAppPayload(await this.buildAppPayload());
114
+ }
115
+ }
116
+
117
+ /** Validate a capacity budget is an integer `>= 0`. */
118
+ function requireBudget(budget: number): number {
119
+ if (!Number.isInteger(budget) || budget < 0) {
120
+ throw new RangeError(`matchmaking provider: capacityBudget must be an integer >= 0, got ${budget}`);
121
+ }
122
+ return budget;
123
+ }
@@ -1,105 +1,105 @@
1
- /**
2
- * Matchmaking — pure `QueryV1` evaluation (db-core, transport-agnostic).
3
- *
4
- * The cohort-side query handler (`db-p2p/src/matchmaking/query-handler.ts`) decodes its local
5
- * registration records into the {@link LocalProviderRegistration} / {@link LocalSeekerRegistration}
6
- * shapes below and hands them here; this module performs the *advisory* selection that
7
- * `docs/matchmaking.md` §Seeker query / §Capability filter specify:
8
- *
9
- * - Providers are filtered through {@link matchesFilter} (when `includeProviders`).
10
- * - Each included set is truncated to `query.limit` (`<= query_limit_max`, enforced on decode in
11
- * `wire.ts`); {@link QueryEvalResult.truncated} is set when *any* included set had more matches than
12
- * `limit` allowed, so the seeker knows to re-query a sibling cohort.
13
- * - The forwarded entries carry the provider/seeker's own `registrationSig` verbatim, so the seeker
14
- * re-validates each one (`verifyProviderEntry`) — the cohort vouches only for the *set it held*.
15
- *
16
- * Pure: no I/O, no clock, no crypto. The db-p2p handler attaches `topicTraffic` / `cohortEpoch` and
17
- * the primary's reply signature.
18
- */
19
-
20
- import { matchesFilter } from "./capability-filter.js";
21
- import type { ProviderAppPayloadV1, SeekerAppPayloadV1, ProviderEntryV1, SeekerEntryV1, QueryV1 } from "./wire.js";
22
-
23
- /** A decoded local provider registration held by the cohort, ready for {@link evaluateQuery}. */
24
- export interface LocalProviderRegistration {
25
- /** The provider's peer-id string — the entry's `participantId` AND the `registrationSig` signer. */
26
- readonly participantId: string;
27
- /** Unix ms the registration first attached (forwarded verbatim for seeker FCFS ordering). */
28
- readonly attachedAt: number;
29
- /** The decoded, validated provider app payload (capabilities, budget, contact, signature). */
30
- readonly payload: ProviderAppPayloadV1;
31
- }
32
-
33
- /** A decoded local seeker registration held by the cohort (collective-assembly discovery). */
34
- export interface LocalSeekerRegistration {
35
- readonly participantId: string;
36
- readonly attachedAt: number;
37
- readonly payload: SeekerAppPayloadV1;
38
- }
39
-
40
- /** The selected entries for a {@link QueryReplyV1} body (the db-p2p handler signs + frames it). */
41
- export interface QueryEvalResult {
42
- readonly providers?: ProviderEntryV1[];
43
- readonly seekers?: SeekerEntryV1[];
44
- /** `true` when an included set had more matches than `query.limit` allowed (re-query hint). */
45
- readonly truncated: boolean;
46
- }
47
-
48
- /** Build a forwarded {@link ProviderEntryV1} from a local registration (signature forwarded verbatim). */
49
- export function providerEntryOf(reg: LocalProviderRegistration): ProviderEntryV1 {
50
- return {
51
- participantId: reg.participantId,
52
- capabilities: [...reg.payload.capabilities],
53
- capacityBudget: reg.payload.capacityBudget,
54
- contactHint: reg.payload.contactHint,
55
- attachedAt: reg.attachedAt,
56
- registrationSig: reg.payload.signature,
57
- };
58
- }
59
-
60
- /** Build a forwarded {@link SeekerEntryV1} from a local registration (signature forwarded verbatim). */
61
- export function seekerEntryOf(reg: LocalSeekerRegistration): SeekerEntryV1 {
62
- return {
63
- participantId: reg.participantId,
64
- wantCount: reg.payload.wantCount,
65
- contactHint: reg.payload.contactHint,
66
- attachedAt: reg.attachedAt,
67
- registrationSig: reg.payload.signature,
68
- };
69
- }
70
-
71
- /**
72
- * Evaluate `query` against the cohort's locally-held registrations: filter providers, optionally include
73
- * seekers, truncate each included set to `query.limit`. Inputs are returned in `attachedAt` order
74
- * (oldest first) so truncation keeps the longest-waiting registrations — the FCFS bias the arrival-push
75
- * fairness rule also uses (`docs/matchmaking.md` §Fairness).
76
- */
77
- export function evaluateQuery(
78
- query: QueryV1,
79
- providers: readonly LocalProviderRegistration[],
80
- seekers: readonly LocalSeekerRegistration[],
81
- ): QueryEvalResult {
82
- let truncated = false;
83
- const result: { providers?: ProviderEntryV1[]; seekers?: SeekerEntryV1[]; truncated: boolean } = { truncated: false };
84
-
85
- if (query.includeProviders) {
86
- const matched = providers
87
- .filter((reg) => matchesFilter(reg.payload, query.filter))
88
- .sort((a, b) => a.attachedAt - b.attachedAt);
89
- if (matched.length > query.limit) {
90
- truncated = true;
91
- }
92
- result.providers = matched.slice(0, query.limit).map(providerEntryOf);
93
- }
94
-
95
- if (query.includeSeekers) {
96
- const ordered = [...seekers].sort((a, b) => a.attachedAt - b.attachedAt);
97
- if (ordered.length > query.limit) {
98
- truncated = true;
99
- }
100
- result.seekers = ordered.slice(0, query.limit).map(seekerEntryOf);
101
- }
102
-
103
- result.truncated = truncated;
104
- return result;
105
- }
1
+ /**
2
+ * Matchmaking — pure `QueryV1` evaluation (db-core, transport-agnostic).
3
+ *
4
+ * The cohort-side query handler (`db-p2p/src/matchmaking/query-handler.ts`) decodes its local
5
+ * registration records into the {@link LocalProviderRegistration} / {@link LocalSeekerRegistration}
6
+ * shapes below and hands them here; this module performs the *advisory* selection that
7
+ * `docs/matchmaking.md` §Seeker query / §Capability filter specify:
8
+ *
9
+ * - Providers are filtered through {@link matchesFilter} (when `includeProviders`).
10
+ * - Each included set is truncated to `query.limit` (`<= query_limit_max`, enforced on decode in
11
+ * `wire.ts`); {@link QueryEvalResult.truncated} is set when *any* included set had more matches than
12
+ * `limit` allowed, so the seeker knows to re-query a sibling cohort.
13
+ * - The forwarded entries carry the provider/seeker's own `registrationSig` verbatim, so the seeker
14
+ * re-validates each one (`verifyProviderEntry`) — the cohort vouches only for the *set it held*.
15
+ *
16
+ * Pure: no I/O, no clock, no crypto. The db-p2p handler attaches `topicTraffic` / `cohortEpoch` and
17
+ * the primary's reply signature.
18
+ */
19
+
20
+ import { matchesFilter } from "./capability-filter.js";
21
+ import type { ProviderAppPayloadV1, SeekerAppPayloadV1, ProviderEntryV1, SeekerEntryV1, QueryV1 } from "./wire.js";
22
+
23
+ /** A decoded local provider registration held by the cohort, ready for {@link evaluateQuery}. */
24
+ export interface LocalProviderRegistration {
25
+ /** The provider's peer-id string — the entry's `participantId` AND the `registrationSig` signer. */
26
+ readonly participantId: string;
27
+ /** Unix ms the registration first attached (forwarded verbatim for seeker FCFS ordering). */
28
+ readonly attachedAt: number;
29
+ /** The decoded, validated provider app payload (capabilities, budget, contact, signature). */
30
+ readonly payload: ProviderAppPayloadV1;
31
+ }
32
+
33
+ /** A decoded local seeker registration held by the cohort (collective-assembly discovery). */
34
+ export interface LocalSeekerRegistration {
35
+ readonly participantId: string;
36
+ readonly attachedAt: number;
37
+ readonly payload: SeekerAppPayloadV1;
38
+ }
39
+
40
+ /** The selected entries for a {@link QueryReplyV1} body (the db-p2p handler signs + frames it). */
41
+ export interface QueryEvalResult {
42
+ readonly providers?: ProviderEntryV1[];
43
+ readonly seekers?: SeekerEntryV1[];
44
+ /** `true` when an included set had more matches than `query.limit` allowed (re-query hint). */
45
+ readonly truncated: boolean;
46
+ }
47
+
48
+ /** Build a forwarded {@link ProviderEntryV1} from a local registration (signature forwarded verbatim). */
49
+ export function providerEntryOf(reg: LocalProviderRegistration): ProviderEntryV1 {
50
+ return {
51
+ participantId: reg.participantId,
52
+ capabilities: [...reg.payload.capabilities],
53
+ capacityBudget: reg.payload.capacityBudget,
54
+ contactHint: reg.payload.contactHint,
55
+ attachedAt: reg.attachedAt,
56
+ registrationSig: reg.payload.signature,
57
+ };
58
+ }
59
+
60
+ /** Build a forwarded {@link SeekerEntryV1} from a local registration (signature forwarded verbatim). */
61
+ export function seekerEntryOf(reg: LocalSeekerRegistration): SeekerEntryV1 {
62
+ return {
63
+ participantId: reg.participantId,
64
+ wantCount: reg.payload.wantCount,
65
+ contactHint: reg.payload.contactHint,
66
+ attachedAt: reg.attachedAt,
67
+ registrationSig: reg.payload.signature,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Evaluate `query` against the cohort's locally-held registrations: filter providers, optionally include
73
+ * seekers, truncate each included set to `query.limit`. Inputs are returned in `attachedAt` order
74
+ * (oldest first) so truncation keeps the longest-waiting registrations — the FCFS bias the arrival-push
75
+ * fairness rule also uses (`docs/matchmaking.md` §Fairness).
76
+ */
77
+ export function evaluateQuery(
78
+ query: QueryV1,
79
+ providers: readonly LocalProviderRegistration[],
80
+ seekers: readonly LocalSeekerRegistration[],
81
+ ): QueryEvalResult {
82
+ let truncated = false;
83
+ const result: { providers?: ProviderEntryV1[]; seekers?: SeekerEntryV1[]; truncated: boolean } = { truncated: false };
84
+
85
+ if (query.includeProviders) {
86
+ const matched = providers
87
+ .filter((reg) => matchesFilter(reg.payload, query.filter))
88
+ .sort((a, b) => a.attachedAt - b.attachedAt);
89
+ if (matched.length > query.limit) {
90
+ truncated = true;
91
+ }
92
+ result.providers = matched.slice(0, query.limit).map(providerEntryOf);
93
+ }
94
+
95
+ if (query.includeSeekers) {
96
+ const ordered = [...seekers].sort((a, b) => a.attachedAt - b.attachedAt);
97
+ if (ordered.length > query.limit) {
98
+ truncated = true;
99
+ }
100
+ result.seekers = ordered.slice(0, query.limit).map(seekerEntryOf);
101
+ }
102
+
103
+ result.truncated = truncated;
104
+ return result;
105
+ }