@snaha/swarm-id 0.0.1

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 (223) hide show
  1. package/README.md +431 -0
  2. package/dist/chunk/bmt.d.ts +17 -0
  3. package/dist/chunk/bmt.d.ts.map +1 -0
  4. package/dist/chunk/cac.d.ts +18 -0
  5. package/dist/chunk/cac.d.ts.map +1 -0
  6. package/dist/chunk/constants.d.ts +10 -0
  7. package/dist/chunk/constants.d.ts.map +1 -0
  8. package/dist/chunk/encrypted-cac.d.ts +48 -0
  9. package/dist/chunk/encrypted-cac.d.ts.map +1 -0
  10. package/dist/chunk/encryption.d.ts +86 -0
  11. package/dist/chunk/encryption.d.ts.map +1 -0
  12. package/dist/chunk/index.d.ts +6 -0
  13. package/dist/chunk/index.d.ts.map +1 -0
  14. package/dist/index.d.ts +46 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/proxy/act/act.d.ts +78 -0
  17. package/dist/proxy/act/act.d.ts.map +1 -0
  18. package/dist/proxy/act/crypto.d.ts +44 -0
  19. package/dist/proxy/act/crypto.d.ts.map +1 -0
  20. package/dist/proxy/act/grantee-list.d.ts +82 -0
  21. package/dist/proxy/act/grantee-list.d.ts.map +1 -0
  22. package/dist/proxy/act/history.d.ts +183 -0
  23. package/dist/proxy/act/history.d.ts.map +1 -0
  24. package/dist/proxy/act/index.d.ts +104 -0
  25. package/dist/proxy/act/index.d.ts.map +1 -0
  26. package/dist/proxy/chunking-encrypted.d.ts +14 -0
  27. package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
  28. package/dist/proxy/chunking.d.ts +15 -0
  29. package/dist/proxy/chunking.d.ts.map +1 -0
  30. package/dist/proxy/download-data.d.ts +16 -0
  31. package/dist/proxy/download-data.d.ts.map +1 -0
  32. package/dist/proxy/feed-manifest.d.ts +62 -0
  33. package/dist/proxy/feed-manifest.d.ts.map +1 -0
  34. package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
  35. package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
  36. package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
  37. package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
  38. package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
  39. package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
  40. package/dist/proxy/feeds/epochs/index.d.ts +35 -0
  41. package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
  42. package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
  43. package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
  44. package/dist/proxy/feeds/epochs/types.d.ts +109 -0
  45. package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
  46. package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
  47. package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
  48. package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
  49. package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
  50. package/dist/proxy/feeds/index.d.ts +5 -0
  51. package/dist/proxy/feeds/index.d.ts.map +1 -0
  52. package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
  53. package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
  54. package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
  55. package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
  56. package/dist/proxy/feeds/sequence/index.d.ts +23 -0
  57. package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
  58. package/dist/proxy/feeds/sequence/types.d.ts +80 -0
  59. package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
  60. package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
  61. package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
  62. package/dist/proxy/index.d.ts +6 -0
  63. package/dist/proxy/index.d.ts.map +1 -0
  64. package/dist/proxy/manifest-builder.d.ts +183 -0
  65. package/dist/proxy/manifest-builder.d.ts.map +1 -0
  66. package/dist/proxy/mantaray-encrypted.d.ts +27 -0
  67. package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
  68. package/dist/proxy/mantaray.d.ts +26 -0
  69. package/dist/proxy/mantaray.d.ts.map +1 -0
  70. package/dist/proxy/types.d.ts +29 -0
  71. package/dist/proxy/types.d.ts.map +1 -0
  72. package/dist/proxy/upload-data.d.ts +17 -0
  73. package/dist/proxy/upload-data.d.ts.map +1 -0
  74. package/dist/proxy/upload-encrypted-data.d.ts +103 -0
  75. package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
  76. package/dist/schemas.d.ts +240 -0
  77. package/dist/schemas.d.ts.map +1 -0
  78. package/dist/storage/debounced-uploader.d.ts +62 -0
  79. package/dist/storage/debounced-uploader.d.ts.map +1 -0
  80. package/dist/storage/utilization-store.d.ts +108 -0
  81. package/dist/storage/utilization-store.d.ts.map +1 -0
  82. package/dist/swarm-id-auth.d.ts +74 -0
  83. package/dist/swarm-id-auth.d.ts.map +1 -0
  84. package/dist/swarm-id-auth.js +2 -0
  85. package/dist/swarm-id-auth.js.map +1 -0
  86. package/dist/swarm-id-client.d.ts +878 -0
  87. package/dist/swarm-id-client.d.ts.map +1 -0
  88. package/dist/swarm-id-client.js +2 -0
  89. package/dist/swarm-id-client.js.map +1 -0
  90. package/dist/swarm-id-proxy.d.ts +236 -0
  91. package/dist/swarm-id-proxy.d.ts.map +1 -0
  92. package/dist/swarm-id-proxy.js +2 -0
  93. package/dist/swarm-id-proxy.js.map +1 -0
  94. package/dist/swarm-id.esm.js +2 -0
  95. package/dist/swarm-id.esm.js.map +1 -0
  96. package/dist/swarm-id.umd.js +2 -0
  97. package/dist/swarm-id.umd.js.map +1 -0
  98. package/dist/sync/index.d.ts +9 -0
  99. package/dist/sync/index.d.ts.map +1 -0
  100. package/dist/sync/key-derivation.d.ts +25 -0
  101. package/dist/sync/key-derivation.d.ts.map +1 -0
  102. package/dist/sync/restore-account.d.ts +28 -0
  103. package/dist/sync/restore-account.d.ts.map +1 -0
  104. package/dist/sync/serialization.d.ts +16 -0
  105. package/dist/sync/serialization.d.ts.map +1 -0
  106. package/dist/sync/store-interfaces.d.ts +53 -0
  107. package/dist/sync/store-interfaces.d.ts.map +1 -0
  108. package/dist/sync/sync-account.d.ts +44 -0
  109. package/dist/sync/sync-account.d.ts.map +1 -0
  110. package/dist/sync/types.d.ts +13 -0
  111. package/dist/sync/types.d.ts.map +1 -0
  112. package/dist/test-fixtures.d.ts +17 -0
  113. package/dist/test-fixtures.d.ts.map +1 -0
  114. package/dist/types-BD_VkNn0.js +2 -0
  115. package/dist/types-BD_VkNn0.js.map +1 -0
  116. package/dist/types-lJCaT-50.js +2 -0
  117. package/dist/types-lJCaT-50.js.map +1 -0
  118. package/dist/types.d.ts +2157 -0
  119. package/dist/types.d.ts.map +1 -0
  120. package/dist/utils/account-payload.d.ts +94 -0
  121. package/dist/utils/account-payload.d.ts.map +1 -0
  122. package/dist/utils/account-state-snapshot.d.ts +38 -0
  123. package/dist/utils/account-state-snapshot.d.ts.map +1 -0
  124. package/dist/utils/backup-encryption.d.ts +127 -0
  125. package/dist/utils/backup-encryption.d.ts.map +1 -0
  126. package/dist/utils/batch-utilization.d.ts +432 -0
  127. package/dist/utils/batch-utilization.d.ts.map +1 -0
  128. package/dist/utils/constants.d.ts +11 -0
  129. package/dist/utils/constants.d.ts.map +1 -0
  130. package/dist/utils/hex.d.ts +17 -0
  131. package/dist/utils/hex.d.ts.map +1 -0
  132. package/dist/utils/key-derivation.d.ts +92 -0
  133. package/dist/utils/key-derivation.d.ts.map +1 -0
  134. package/dist/utils/storage-managers.d.ts +65 -0
  135. package/dist/utils/storage-managers.d.ts.map +1 -0
  136. package/dist/utils/swarm-id-export.d.ts +24 -0
  137. package/dist/utils/swarm-id-export.d.ts.map +1 -0
  138. package/dist/utils/ttl.d.ts +49 -0
  139. package/dist/utils/ttl.d.ts.map +1 -0
  140. package/dist/utils/url.d.ts +41 -0
  141. package/dist/utils/url.d.ts.map +1 -0
  142. package/dist/utils/versioned-storage.d.ts +131 -0
  143. package/dist/utils/versioned-storage.d.ts.map +1 -0
  144. package/package.json +78 -0
  145. package/src/chunk/bmt.test.ts +217 -0
  146. package/src/chunk/bmt.ts +57 -0
  147. package/src/chunk/cac.test.ts +214 -0
  148. package/src/chunk/cac.ts +65 -0
  149. package/src/chunk/constants.ts +18 -0
  150. package/src/chunk/encrypted-cac.test.ts +385 -0
  151. package/src/chunk/encrypted-cac.ts +131 -0
  152. package/src/chunk/encryption.test.ts +352 -0
  153. package/src/chunk/encryption.ts +300 -0
  154. package/src/chunk/index.ts +47 -0
  155. package/src/index.ts +430 -0
  156. package/src/proxy/act/act.test.ts +278 -0
  157. package/src/proxy/act/act.ts +158 -0
  158. package/src/proxy/act/bee-compat.test.ts +948 -0
  159. package/src/proxy/act/crypto.test.ts +436 -0
  160. package/src/proxy/act/crypto.ts +376 -0
  161. package/src/proxy/act/grantee-list.test.ts +393 -0
  162. package/src/proxy/act/grantee-list.ts +239 -0
  163. package/src/proxy/act/history.test.ts +360 -0
  164. package/src/proxy/act/history.ts +413 -0
  165. package/src/proxy/act/index.test.ts +748 -0
  166. package/src/proxy/act/index.ts +853 -0
  167. package/src/proxy/chunking-encrypted.ts +95 -0
  168. package/src/proxy/chunking.ts +65 -0
  169. package/src/proxy/download-data.ts +448 -0
  170. package/src/proxy/feed-manifest.ts +174 -0
  171. package/src/proxy/feeds/epochs/async-finder.ts +372 -0
  172. package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
  173. package/src/proxy/feeds/epochs/epoch.ts +181 -0
  174. package/src/proxy/feeds/epochs/finder.ts +282 -0
  175. package/src/proxy/feeds/epochs/index.ts +73 -0
  176. package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
  177. package/src/proxy/feeds/epochs/test-utils.ts +274 -0
  178. package/src/proxy/feeds/epochs/types.ts +128 -0
  179. package/src/proxy/feeds/epochs/updater.ts +192 -0
  180. package/src/proxy/feeds/epochs/utils.ts +62 -0
  181. package/src/proxy/feeds/index.ts +5 -0
  182. package/src/proxy/feeds/sequence/async-finder.ts +31 -0
  183. package/src/proxy/feeds/sequence/finder.ts +73 -0
  184. package/src/proxy/feeds/sequence/index.ts +54 -0
  185. package/src/proxy/feeds/sequence/integration.test.ts +966 -0
  186. package/src/proxy/feeds/sequence/types.ts +103 -0
  187. package/src/proxy/feeds/sequence/updater.ts +71 -0
  188. package/src/proxy/index.ts +5 -0
  189. package/src/proxy/manifest-builder.test.ts +427 -0
  190. package/src/proxy/manifest-builder.ts +679 -0
  191. package/src/proxy/mantaray-encrypted.ts +78 -0
  192. package/src/proxy/mantaray.ts +104 -0
  193. package/src/proxy/types.ts +32 -0
  194. package/src/proxy/upload-data.ts +189 -0
  195. package/src/proxy/upload-encrypted-data.ts +658 -0
  196. package/src/schemas.ts +299 -0
  197. package/src/storage/debounced-uploader.ts +192 -0
  198. package/src/storage/utilization-store.ts +397 -0
  199. package/src/swarm-id-client.test.ts +99 -0
  200. package/src/swarm-id-client.ts +3095 -0
  201. package/src/swarm-id-proxy.ts +3891 -0
  202. package/src/sync/index.ts +28 -0
  203. package/src/sync/restore-account.ts +90 -0
  204. package/src/sync/serialization.ts +39 -0
  205. package/src/sync/store-interfaces.ts +62 -0
  206. package/src/sync/sync-account.test.ts +302 -0
  207. package/src/sync/sync-account.ts +396 -0
  208. package/src/sync/types.ts +11 -0
  209. package/src/test-fixtures.ts +109 -0
  210. package/src/types.ts +1651 -0
  211. package/src/utils/account-state-snapshot.test.ts +595 -0
  212. package/src/utils/account-state-snapshot.ts +94 -0
  213. package/src/utils/backup-encryption.test.ts +442 -0
  214. package/src/utils/backup-encryption.ts +352 -0
  215. package/src/utils/batch-utilization.ts +1309 -0
  216. package/src/utils/constants.ts +20 -0
  217. package/src/utils/hex.ts +27 -0
  218. package/src/utils/key-derivation.ts +197 -0
  219. package/src/utils/storage-managers.ts +365 -0
  220. package/src/utils/ttl.ts +129 -0
  221. package/src/utils/url.test.ts +136 -0
  222. package/src/utils/url.ts +71 -0
  223. package/src/utils/versioned-storage.ts +323 -0
@@ -0,0 +1,966 @@
1
+ /**
2
+ * Integration tests for sequential feeds
3
+ *
4
+ * Based on the Go implementation tests from bee/pkg/feeds/sequence
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
8
+ import { Binary } from "cafe-utility"
9
+ import type { Bee } from "@ethersphere/bee-js"
10
+ import { PrivateKey } from "@ethersphere/bee-js"
11
+ import { SyncSequentialFinder } from "./finder"
12
+ import { AsyncSequentialFinder } from "./async-finder"
13
+ import { BasicSequentialUpdater } from "./updater"
14
+ import {
15
+ MockBee,
16
+ MockChunkStore,
17
+ createTestSigner,
18
+ createTestTopic,
19
+ createTestReference,
20
+ createMockStamper,
21
+ mockFetch,
22
+ } from "../epochs/test-utils"
23
+
24
+ const SPAN_SIZE = 8
25
+
26
+ /**
27
+ * Create 64-byte reference (encrypted reference)
28
+ */
29
+ function createTestReference64(seed: number): Uint8Array {
30
+ const ref = new Uint8Array(64)
31
+ const view = new DataView(ref.buffer)
32
+ view.setBigUint64(0, BigInt(seed), false)
33
+ for (let i = 32; i < 64; i++) {
34
+ ref[i] = (seed + i) & 0xff
35
+ }
36
+ return ref
37
+ }
38
+
39
+ /**
40
+ * MockBee that counts download calls
41
+ */
42
+ class CountingMockBee extends MockBee {
43
+ public downloadCalls = 0
44
+ public uploadCalls = 0
45
+
46
+ override async downloadChunk(reference: string): Promise<Uint8Array> {
47
+ this.downloadCalls++
48
+ const lower = reference.toLowerCase()
49
+ if (!this.getStore().has(lower)) {
50
+ throw new Error("Request failed with status code 404")
51
+ }
52
+ return this.getStore().get(lower)
53
+ }
54
+
55
+ override async uploadChunk(
56
+ data: Uint8Array,
57
+ postageBatchId: string,
58
+ ): Promise<{ reference: string }> {
59
+ this.uploadCalls++
60
+ return super.uploadChunk(data, postageBatchId)
61
+ }
62
+ }
63
+
64
+ /**
65
+ * MockBee that simulates network failures
66
+ */
67
+ class FailingMockBee extends MockBee {
68
+ private failureCount = 0
69
+ private maxFailures: number
70
+
71
+ constructor(store?: MockChunkStore, maxFailures = 3) {
72
+ super(store)
73
+ this.maxFailures = maxFailures
74
+ }
75
+
76
+ override async downloadChunk(reference: string): Promise<Uint8Array> {
77
+ if (this.failureCount < this.maxFailures) {
78
+ this.failureCount++
79
+ throw new Error("Request failed with status code 500")
80
+ }
81
+ return super.downloadChunk(reference)
82
+ }
83
+
84
+ resetFailures(): void {
85
+ this.failureCount = 0
86
+ }
87
+ }
88
+
89
+ /**
90
+ * MockBee that simulates mixed error types
91
+ */
92
+ class MixedErrorMockBee extends CountingMockBee {
93
+ override async downloadChunk(reference: string): Promise<Uint8Array> {
94
+ this.downloadCalls++
95
+ const lower = reference.toLowerCase()
96
+ if (this.getStore().has(lower)) {
97
+ return this.getStore().get(lower)
98
+ }
99
+
100
+ const selector = lower.charCodeAt(0) % 3
101
+ if (selector === 0) {
102
+ throw new Error("Request failed with status code 404")
103
+ }
104
+ if (selector === 1) {
105
+ throw new Error("Request failed with status code 500")
106
+ }
107
+ throw new Error("timeout of 2000ms exceeded")
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Mock uploadSOC to store SOC data in the mock store.
113
+ */
114
+ vi.mock("../../upload-encrypted-data", async (importOriginal) => {
115
+ const mod =
116
+ await importOriginal<typeof import("../../upload-encrypted-data")>()
117
+ const { Binary } = await import("cafe-utility")
118
+
119
+ return {
120
+ ...mod,
121
+ uploadSOC: async (
122
+ bee: any,
123
+ _stamper: any,
124
+ signer: any,
125
+ identifier: any,
126
+ data: Uint8Array,
127
+ ) => {
128
+ // Validate data size (1-4096 bytes) - matching real uploadSOC
129
+ if (data.length < 1 || data.length > 4096) {
130
+ throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
131
+ }
132
+
133
+ // Create span (little-endian uint64 of data length)
134
+ const span = new Uint8Array(SPAN_SIZE)
135
+ const spanView = new DataView(span.buffer)
136
+ spanView.setBigUint64(0, BigInt(data.length), true)
137
+
138
+ // Sign: hash(identifier + hash(span + data))
139
+ const contentHash = Binary.keccak256(Binary.concatBytes(span, data))
140
+ const toSign = Binary.concatBytes(identifier.toUint8Array(), contentHash)
141
+ const signature = signer.sign(toSign)
142
+
143
+ // Build SOC data: identifier(32) + signature(65) + span(8) + payload
144
+ const socData = Binary.concatBytes(
145
+ identifier.toUint8Array(),
146
+ signature.toUint8Array(),
147
+ span,
148
+ data,
149
+ )
150
+
151
+ // Calculate SOC address: Keccak256(identifier + owner)
152
+ const owner = signer.publicKey().address()
153
+ const socAddress = Binary.keccak256(
154
+ Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),
155
+ )
156
+
157
+ // Store directly in mock store (bypassing fetch)
158
+ const store = bee.getStore()
159
+ const reference = Binary.uint8ArrayToHex(socAddress)
160
+ await store.put(reference, socData)
161
+
162
+ return {
163
+ socAddress,
164
+ tagUid: 0,
165
+ }
166
+ },
167
+ uploadEncryptedSOC: async (
168
+ bee: any,
169
+ _stamper: any,
170
+ signer: any,
171
+ identifier: any,
172
+ data: Uint8Array,
173
+ ) => {
174
+ // Validate data size (1-4096 bytes) - matching real uploadEncryptedSOC
175
+ if (data.length < 1 || data.length > 4096) {
176
+ throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
177
+ }
178
+
179
+ // Create span (little-endian uint64 of data length)
180
+ const span = new Uint8Array(SPAN_SIZE)
181
+ const spanView = new DataView(span.buffer)
182
+ spanView.setBigUint64(0, BigInt(data.length), true)
183
+
184
+ // Sign: hash(identifier + hash(span + data))
185
+ const contentHash = Binary.keccak256(Binary.concatBytes(span, data))
186
+ const toSign = Binary.concatBytes(identifier.toUint8Array(), contentHash)
187
+ const signature = signer.sign(toSign)
188
+
189
+ // Build SOC data: identifier(32) + signature(65) + span(8) + payload
190
+ const socData = Binary.concatBytes(
191
+ identifier.toUint8Array(),
192
+ signature.toUint8Array(),
193
+ span,
194
+ data,
195
+ )
196
+
197
+ // Calculate SOC address: Keccak256(identifier + owner)
198
+ const owner = signer.publicKey().address()
199
+ const socAddress = Binary.keccak256(
200
+ Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),
201
+ )
202
+
203
+ // Store directly in mock store (bypassing fetch/encryption)
204
+ const store = bee.getStore()
205
+ const reference = Binary.uint8ArrayToHex(socAddress)
206
+ await store.put(reference, socData)
207
+
208
+ return {
209
+ socAddress,
210
+ encryptionKey: new Uint8Array(32),
211
+ tagUid: 0,
212
+ }
213
+ },
214
+ }
215
+ })
216
+
217
+ describe("Sequential Feeds Integration", () => {
218
+ let store: MockChunkStore
219
+ let bee: MockBee
220
+ let signer: ReturnType<typeof createTestSigner>
221
+ let topic: ReturnType<typeof createTestTopic>
222
+ let stamper: ReturnType<typeof createMockStamper>
223
+
224
+ beforeEach(() => {
225
+ store = new MockChunkStore()
226
+ bee = new MockBee(store)
227
+ signer = createTestSigner()
228
+ topic = createTestTopic()
229
+ stamper = createMockStamper()
230
+ mockFetch(store, signer.publicKey().address())
231
+ })
232
+
233
+ afterEach(() => {
234
+ store.clear()
235
+ })
236
+
237
+ describe("Basic Operations", () => {
238
+ it("should return undefined when no updates exist", async () => {
239
+ const owner = signer.publicKey().address()
240
+ const beeClient = bee as unknown as Bee
241
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
242
+
243
+ const result = await finder.findAt(0n, 0n)
244
+ expect(result.current).toBeUndefined()
245
+ expect(result.next).toBe(0n)
246
+ })
247
+
248
+ it("should store and retrieve latest update index", async () => {
249
+ const beeClient = bee as unknown as Bee
250
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
251
+ const owner = updater.getOwner()
252
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
253
+
254
+ const updates = 5
255
+ for (let i = 0; i < updates; i++) {
256
+ await updater.update(createTestReference(i), stamper)
257
+ }
258
+
259
+ const result = await finder.findAt(0n, 0n)
260
+ expect(result.current).toBe(BigInt(updates - 1))
261
+ expect(result.next).toBe(BigInt(updates))
262
+ })
263
+
264
+ it("async finder should match sync finder", async () => {
265
+ const beeClient = bee as unknown as Bee
266
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
267
+ const owner = updater.getOwner()
268
+ const syncFinder = new SyncSequentialFinder(beeClient, topic, owner)
269
+ const asyncFinder = new AsyncSequentialFinder(beeClient, topic, owner)
270
+
271
+ const updates = 3
272
+ for (let i = 0; i < updates; i++) {
273
+ await updater.update(createTestReference(i), stamper)
274
+ }
275
+
276
+ const syncResult = await syncFinder.findAt(0n, 0n)
277
+ const asyncResult = await asyncFinder.findAt(0n, 0n)
278
+
279
+ expect(asyncResult.current).toBe(syncResult.current)
280
+ expect(asyncResult.next).toBe(syncResult.next)
281
+ })
282
+ })
283
+
284
+ describe("Multiple Updates", () => {
285
+ it("should write and read 10 sequential updates", async () => {
286
+ const beeClient = bee as unknown as Bee
287
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
288
+ const owner = updater.getOwner()
289
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
290
+
291
+ // Write indices 0-9
292
+ for (let i = 0; i < 10; i++) {
293
+ await updater.update(createTestReference(i), stamper)
294
+ }
295
+
296
+ // Verify finder returns correct result
297
+ const result = await finder.findAt(0n, 0n)
298
+ expect(result.current).toBe(9n)
299
+ expect(result.next).toBe(10n)
300
+ })
301
+
302
+ it(
303
+ "should write and read 50 sequential updates",
304
+ { timeout: 10000 },
305
+ async () => {
306
+ const beeClient = bee as unknown as Bee
307
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
308
+ const owner = updater.getOwner()
309
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
310
+
311
+ // Larger scale test
312
+ const count = 50
313
+ for (let i = 0; i < count; i++) {
314
+ await updater.update(createTestReference(i), stamper)
315
+ }
316
+
317
+ const result = await finder.findAt(0n, 0n)
318
+ expect(result.current).toBe(BigInt(count - 1))
319
+ expect(result.next).toBe(BigInt(count))
320
+ },
321
+ )
322
+
323
+ it("should maintain correct ordering across updates", async () => {
324
+ const beeClient = bee as unknown as Bee
325
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
326
+ const finder = new SyncSequentialFinder(
327
+ beeClient,
328
+ topic,
329
+ updater.getOwner(),
330
+ )
331
+
332
+ const references: Uint8Array[] = []
333
+ for (let i = 0; i < 5; i++) {
334
+ const ref = createTestReference(i * 100)
335
+ references.push(ref)
336
+ await updater.update(ref, stamper)
337
+ }
338
+
339
+ // Verify each update is stored at its expected index
340
+ const result = await finder.findAt(0n, 0n)
341
+ expect(result.current).toBe(4n)
342
+ expect(result.next).toBe(5n)
343
+ })
344
+
345
+ it("should handle payload content correctly for each index", async () => {
346
+ const beeClient = bee as unknown as Bee
347
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
348
+ const owner = updater.getOwner()
349
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
350
+
351
+ // Write different payloads at different indices
352
+ const payloads = [
353
+ createTestReference(111),
354
+ createTestReference(222),
355
+ createTestReference(333),
356
+ ]
357
+
358
+ for (const payload of payloads) {
359
+ await updater.update(payload, stamper)
360
+ }
361
+
362
+ // Verify content integrity
363
+ const result = await finder.findAt(0n, 0n)
364
+ expect(result.current).toBe(2n)
365
+ expect(result.next).toBe(3n)
366
+ })
367
+
368
+ it("should handle sequential updates with 64-byte references", async () => {
369
+ const beeClient = bee as unknown as Bee
370
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
371
+ const owner = updater.getOwner()
372
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
373
+
374
+ // Mix of 32-byte and 64-byte references
375
+ await updater.update(createTestReference64(1), stamper)
376
+ await updater.update(createTestReference(2), stamper)
377
+ await updater.update(createTestReference64(3), stamper)
378
+
379
+ const result = await finder.findAt(0n, 0n)
380
+ expect(result.current).toBe(2n)
381
+ expect(result.next).toBe(3n)
382
+ })
383
+ })
384
+
385
+ describe("Index Lookups", () => {
386
+ it("should find latest update when multiple exist", async () => {
387
+ const beeClient = bee as unknown as Bee
388
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
389
+ const owner = updater.getOwner()
390
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
391
+
392
+ // Write 5 updates
393
+ for (let i = 0; i < 5; i++) {
394
+ await updater.update(createTestReference(i), stamper)
395
+ }
396
+
397
+ // Finder should find the latest (index 4)
398
+ const result = await finder.findAt(0n, 0n)
399
+ expect(result.current).toBe(4n)
400
+ })
401
+
402
+ it("should return undefined for non-existent feed", async () => {
403
+ const beeClient = bee as unknown as Bee
404
+ // Different topic with no updates
405
+ const emptyTopic = createTestTopic("empty-topic")
406
+ const owner = signer.publicKey().address()
407
+ const finder = new SyncSequentialFinder(beeClient, emptyTopic, owner)
408
+
409
+ const result = await finder.findAt(0n, 0n)
410
+ expect(result.current).toBeUndefined()
411
+ expect(result.next).toBe(0n)
412
+ })
413
+
414
+ it("should handle index 0 correctly", async () => {
415
+ const beeClient = bee as unknown as Bee
416
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
417
+ const owner = updater.getOwner()
418
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
419
+
420
+ // Single update at index 0
421
+ await updater.update(createTestReference(42), stamper)
422
+
423
+ const result = await finder.findAt(0n, 0n)
424
+ expect(result.current).toBe(0n)
425
+ expect(result.next).toBe(1n)
426
+ })
427
+
428
+ it("should handle large index counts", { timeout: 15000 }, async () => {
429
+ const beeClient = bee as unknown as Bee
430
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
431
+ const owner = updater.getOwner()
432
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
433
+
434
+ // Write 100 updates to test scalability
435
+ const count = 100
436
+ for (let i = 0; i < count; i++) {
437
+ await updater.update(createTestReference(i), stamper)
438
+ }
439
+
440
+ const result = await finder.findAt(0n, 0n)
441
+ expect(result.current).toBe(BigInt(count - 1))
442
+ expect(result.next).toBe(BigInt(count))
443
+ })
444
+
445
+ it("should correctly report next index for empty feed", async () => {
446
+ const beeClient = bee as unknown as Bee
447
+ const owner = signer.publicKey().address()
448
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
449
+
450
+ const result = await finder.findAt(0n, 0n)
451
+ expect(result.next).toBe(0n)
452
+ })
453
+ })
454
+
455
+ describe("Timestamp Handling", () => {
456
+ it("should ignore at parameter (sequential feeds are index-based)", async () => {
457
+ const beeClient = bee as unknown as Bee
458
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
459
+ const owner = updater.getOwner()
460
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
461
+
462
+ await updater.update(createTestReference(1), stamper)
463
+ await updater.update(createTestReference(2), stamper)
464
+
465
+ // at parameter should be ignored for sequential feeds
466
+ const result1 = await finder.findAt(0n, 0n)
467
+ const result2 = await finder.findAt(1000n, 0n)
468
+ const result3 = await finder.findAt(999999n, 0n)
469
+
470
+ expect(result1.current).toBe(1n)
471
+ expect(result2.current).toBe(1n)
472
+ expect(result3.current).toBe(1n)
473
+ })
474
+
475
+ it("should ignore after parameter (sequential feeds scan from 0)", async () => {
476
+ const beeClient = bee as unknown as Bee
477
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
478
+ const owner = updater.getOwner()
479
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
480
+
481
+ await updater.update(createTestReference(1), stamper)
482
+ await updater.update(createTestReference(2), stamper)
483
+ await updater.update(createTestReference(3), stamper)
484
+
485
+ // after parameter should be ignored for sequential feeds
486
+ const result1 = await finder.findAt(0n, 0n)
487
+ const result2 = await finder.findAt(0n, 100n)
488
+
489
+ expect(result1.current).toBe(2n)
490
+ expect(result2.current).toBe(2n)
491
+ })
492
+
493
+ it("should work with async finder using same parameters", async () => {
494
+ const beeClient = bee as unknown as Bee
495
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
496
+ const owner = updater.getOwner()
497
+ const asyncFinder = new AsyncSequentialFinder(beeClient, topic, owner)
498
+
499
+ await updater.update(createTestReference(1), stamper)
500
+
501
+ const result1 = await asyncFinder.findAt(0n, 0n)
502
+ const result2 = await asyncFinder.findAt(999n, 999n)
503
+
504
+ expect(result1.current).toBe(0n)
505
+ expect(result2.current).toBe(0n)
506
+ })
507
+
508
+ it("should handle queries on empty feed consistently", async () => {
509
+ const beeClient = bee as unknown as Bee
510
+ const owner = signer.publicKey().address()
511
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
512
+
513
+ const result1 = await finder.findAt(0n, 0n)
514
+ const result2 = await finder.findAt(100n, 50n)
515
+
516
+ expect(result1.current).toBeUndefined()
517
+ expect(result2.current).toBeUndefined()
518
+ expect(result1.next).toBe(0n)
519
+ expect(result2.next).toBe(0n)
520
+ })
521
+ })
522
+
523
+ describe("State Management", () => {
524
+ it("should track next index correctly", async () => {
525
+ const beeClient = bee as unknown as Bee
526
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
527
+
528
+ expect(updater.getState().nextIndex).toBe(0n)
529
+
530
+ await updater.update(createTestReference(1), stamper)
531
+ expect(updater.getState().nextIndex).toBe(1n)
532
+
533
+ await updater.update(createTestReference(2), stamper)
534
+ expect(updater.getState().nextIndex).toBe(2n)
535
+
536
+ await updater.update(createTestReference(3), stamper)
537
+ expect(updater.getState().nextIndex).toBe(3n)
538
+ })
539
+
540
+ it("should resume from saved state", async () => {
541
+ const beeClient = bee as unknown as Bee
542
+ const updater1 = new BasicSequentialUpdater(beeClient, topic, signer)
543
+
544
+ // Write some updates
545
+ await updater1.update(createTestReference(1), stamper)
546
+ await updater1.update(createTestReference(2), stamper)
547
+ const state = updater1.getState()
548
+
549
+ // Create new updater and restore state
550
+ const updater2 = new BasicSequentialUpdater(beeClient, topic, signer)
551
+ updater2.setState(state)
552
+
553
+ expect(updater2.getState().nextIndex).toBe(2n)
554
+
555
+ // Continue writing
556
+ await updater2.update(createTestReference(3), stamper)
557
+ expect(updater2.getState().nextIndex).toBe(3n)
558
+
559
+ // Verify all updates are findable
560
+ const finder = new SyncSequentialFinder(
561
+ beeClient,
562
+ topic,
563
+ updater2.getOwner(),
564
+ )
565
+ const result = await finder.findAt(0n, 0n)
566
+ expect(result.current).toBe(2n)
567
+ expect(result.next).toBe(3n)
568
+ })
569
+
570
+ it("should reset state correctly", async () => {
571
+ const beeClient = bee as unknown as Bee
572
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
573
+
574
+ // Write updates
575
+ await updater.update(createTestReference(1), stamper)
576
+ await updater.update(createTestReference(2), stamper)
577
+ expect(updater.getState().nextIndex).toBe(2n)
578
+
579
+ // Reset
580
+ updater.reset()
581
+ expect(updater.getState().nextIndex).toBe(0n)
582
+
583
+ // New writes should start from 0 (will overwrite previous)
584
+ await updater.update(createTestReference(100), stamper)
585
+ expect(updater.getState().nextIndex).toBe(1n)
586
+ })
587
+ })
588
+
589
+ describe("Error Handling", () => {
590
+ it("should handle network errors gracefully", async () => {
591
+ const failingStore = new MockChunkStore()
592
+ const failingBee = new FailingMockBee(failingStore, 0) // Always fail
593
+ const beeClient = failingBee as unknown as Bee
594
+ const owner = signer.publicKey().address()
595
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
596
+
597
+ // Should return undefined, not throw
598
+ const result = await finder.findAt(0n, 0n)
599
+ expect(result.current).toBeUndefined()
600
+ expect(result.next).toBe(0n)
601
+ })
602
+
603
+ it("should handle 404 for missing chunks", async () => {
604
+ const countingStore = new MockChunkStore()
605
+ const countingBee = new CountingMockBee(countingStore)
606
+ const beeClient = countingBee as unknown as Bee
607
+ const owner = signer.publicKey().address()
608
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
609
+
610
+ // Finder should return undefined, not crash
611
+ const result = await finder.findAt(0n, 0n)
612
+ expect(result.current).toBeUndefined()
613
+ expect(countingBee.downloadCalls).toBe(1) // Only tried index 0
614
+ })
615
+
616
+ it("should handle mixed error types", async () => {
617
+ const mixedStore = new MockChunkStore()
618
+ const mixedBee = new MixedErrorMockBee(mixedStore)
619
+ const beeClient = mixedBee as unknown as Bee
620
+ const owner = signer.publicKey().address()
621
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
622
+
623
+ // Should handle gracefully regardless of error type
624
+ const result = await finder.findAt(0n, 0n)
625
+ expect(result.current).toBeUndefined()
626
+ })
627
+
628
+ it("should continue finding after partial success", async () => {
629
+ const beeClient = bee as unknown as Bee
630
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
631
+ const owner = updater.getOwner()
632
+
633
+ // Create 3 updates
634
+ await updater.update(createTestReference(1), stamper)
635
+ await updater.update(createTestReference(2), stamper)
636
+ await updater.update(createTestReference(3), stamper)
637
+
638
+ // Now use counting bee to verify behavior
639
+ const countingBee = new CountingMockBee(store)
640
+ const finder = new SyncSequentialFinder(
641
+ countingBee as unknown as Bee,
642
+ topic,
643
+ owner,
644
+ )
645
+
646
+ const result = await finder.findAt(0n, 0n)
647
+ expect(result.current).toBe(2n)
648
+ expect(result.next).toBe(3n)
649
+ // Should have probed indices 0, 1, 2, 3 (where 3 returns 404)
650
+ expect(countingBee.downloadCalls).toBe(4)
651
+ })
652
+
653
+ it("should handle empty payload gracefully", async () => {
654
+ const beeClient = bee as unknown as Bee
655
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
656
+
657
+ // Empty payloads should throw (data length 0 is invalid)
658
+ const emptyPayload = new Uint8Array(0)
659
+ await expect(updater.update(emptyPayload, stamper)).rejects.toThrow()
660
+ })
661
+ })
662
+
663
+ describe("Owner/Topic Isolation", () => {
664
+ it("should isolate updates by owner", async () => {
665
+ const beeClient = bee as unknown as Bee
666
+
667
+ // Two different signers (owners)
668
+ const signerA = createTestSigner()
669
+ const signerB = new PrivateKey(
670
+ "9a4ce1ef8d14b7864ea3f1ecfcb39f937ce4a45f47f4d7d02f6b76f1f3ab2c11",
671
+ )
672
+
673
+ const updaterA = new BasicSequentialUpdater(beeClient, topic, signerA)
674
+ const updaterB = new BasicSequentialUpdater(beeClient, topic, signerB)
675
+
676
+ // Write to both feeds
677
+ await updaterA.update(createTestReference(100), stamper)
678
+ await updaterA.update(createTestReference(101), stamper)
679
+
680
+ await updaterB.update(createTestReference(200), stamper)
681
+
682
+ // Each owner should see only their own updates
683
+ const finderA = new SyncSequentialFinder(
684
+ beeClient,
685
+ topic,
686
+ updaterA.getOwner(),
687
+ )
688
+ const finderB = new SyncSequentialFinder(
689
+ beeClient,
690
+ topic,
691
+ updaterB.getOwner(),
692
+ )
693
+
694
+ const resultA = await finderA.findAt(0n, 0n)
695
+ const resultB = await finderB.findAt(0n, 0n)
696
+
697
+ expect(resultA.current).toBe(1n) // Owner A has 2 updates (index 0, 1)
698
+ expect(resultB.current).toBe(0n) // Owner B has 1 update (index 0)
699
+ })
700
+
701
+ it("should isolate updates by topic", async () => {
702
+ const beeClient = bee as unknown as Bee
703
+
704
+ const topicA = createTestTopic("topic-a")
705
+ const topicB = createTestTopic("topic-b")
706
+
707
+ const updaterA = new BasicSequentialUpdater(beeClient, topicA, signer)
708
+ const updaterB = new BasicSequentialUpdater(beeClient, topicB, signer)
709
+
710
+ // Write different amounts to each topic
711
+ await updaterA.update(createTestReference(1), stamper)
712
+ await updaterA.update(createTestReference(2), stamper)
713
+ await updaterA.update(createTestReference(3), stamper)
714
+
715
+ await updaterB.update(createTestReference(10), stamper)
716
+
717
+ const owner = signer.publicKey().address()
718
+ const finderA = new SyncSequentialFinder(beeClient, topicA, owner)
719
+ const finderB = new SyncSequentialFinder(beeClient, topicB, owner)
720
+
721
+ const resultA = await finderA.findAt(0n, 0n)
722
+ const resultB = await finderB.findAt(0n, 0n)
723
+
724
+ expect(resultA.current).toBe(2n) // Topic A has 3 updates
725
+ expect(resultB.current).toBe(0n) // Topic B has 1 update
726
+ })
727
+
728
+ it("should handle cross-owner lookup correctly", async () => {
729
+ const beeClient = bee as unknown as Bee
730
+
731
+ const signerA = createTestSigner()
732
+ const signerB = new PrivateKey(
733
+ "7f6c8f5de489c56ba40b494a26d0c6dd0c05fc4f0d37fe2f217af6e9ac7b1a01",
734
+ )
735
+
736
+ const updaterA = new BasicSequentialUpdater(beeClient, topic, signerA)
737
+
738
+ // Owner A writes updates
739
+ await updaterA.update(createTestReference(1), stamper)
740
+ await updaterA.update(createTestReference(2), stamper)
741
+
742
+ // Try to find using Owner B's address - should find nothing
743
+ const finderB = new SyncSequentialFinder(
744
+ beeClient,
745
+ topic,
746
+ signerB.publicKey().address(),
747
+ )
748
+ const result = await finderB.findAt(0n, 0n)
749
+
750
+ expect(result.current).toBeUndefined()
751
+ expect(result.next).toBe(0n)
752
+ })
753
+ })
754
+
755
+ describe("Concurrent Operations", () => {
756
+ it("should handle concurrent reads safely", async () => {
757
+ const beeClient = bee as unknown as Bee
758
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
759
+ const owner = updater.getOwner()
760
+
761
+ // Write some updates
762
+ for (let i = 0; i < 10; i++) {
763
+ await updater.update(createTestReference(i), stamper)
764
+ }
765
+
766
+ // Create multiple finders and read concurrently
767
+ const finders = [
768
+ new SyncSequentialFinder(beeClient, topic, owner),
769
+ new SyncSequentialFinder(beeClient, topic, owner),
770
+ new AsyncSequentialFinder(beeClient, topic, owner),
771
+ new AsyncSequentialFinder(beeClient, topic, owner),
772
+ ]
773
+
774
+ const results = await Promise.all(
775
+ finders.map((finder) => finder.findAt(0n, 0n)),
776
+ )
777
+
778
+ // All should return the same result
779
+ for (const result of results) {
780
+ expect(result.current).toBe(9n)
781
+ expect(result.next).toBe(10n)
782
+ }
783
+ })
784
+
785
+ it("should handle rapid sequential updates", async () => {
786
+ const beeClient = bee as unknown as Bee
787
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
788
+ const owner = updater.getOwner()
789
+
790
+ // Rapid sequential updates (must be sequential to avoid race conditions)
791
+ // Sequential feeds require sequential writes since nextIndex is incremented after each update
792
+ for (let i = 0; i < 20; i++) {
793
+ await updater.update(createTestReference(i), stamper)
794
+ }
795
+
796
+ // Verify all updates succeeded
797
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
798
+ const result = await finder.findAt(0n, 0n)
799
+
800
+ expect(result.current).toBe(19n)
801
+ expect(result.next).toBe(20n)
802
+ expect(updater.getState().nextIndex).toBe(20n)
803
+ })
804
+ })
805
+
806
+ describe("Performance", () => {
807
+ it(
808
+ "should complete linear scan within reasonable bounds",
809
+ { timeout: 30000 },
810
+ async () => {
811
+ const countingStore = new MockChunkStore()
812
+ const countingBee = new CountingMockBee(countingStore)
813
+ const beeClient = countingBee as unknown as Bee
814
+
815
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
816
+
817
+ // Write 100 updates
818
+ const updateCount = 100
819
+ for (let i = 0; i < updateCount; i++) {
820
+ await updater.update(createTestReference(i), stamper)
821
+ }
822
+
823
+ // Reset counter before find
824
+ countingBee.downloadCalls = 0
825
+
826
+ const finder = new SyncSequentialFinder(
827
+ beeClient,
828
+ topic,
829
+ updater.getOwner(),
830
+ )
831
+ const result = await finder.findAt(0n, 0n)
832
+
833
+ expect(result.current).toBe(BigInt(updateCount - 1))
834
+ // Linear scan should probe exactly updateCount + 1 times (0 to 100 inclusive, where 100 is 404)
835
+ expect(countingBee.downloadCalls).toBe(updateCount + 1)
836
+ },
837
+ )
838
+
839
+ it(
840
+ "sync and async finders should match for large datasets",
841
+ { timeout: 10000 },
842
+ async () => {
843
+ const beeClient = bee as unknown as Bee
844
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
845
+ const owner = updater.getOwner()
846
+
847
+ // Write 50 updates
848
+ for (let i = 0; i < 50; i++) {
849
+ await updater.update(createTestReference(i), stamper)
850
+ }
851
+
852
+ const syncFinder = new SyncSequentialFinder(beeClient, topic, owner)
853
+ const asyncFinder = new AsyncSequentialFinder(beeClient, topic, owner)
854
+
855
+ const syncResult = await syncFinder.findAt(0n, 0n)
856
+ const asyncResult = await asyncFinder.findAt(0n, 0n)
857
+
858
+ expect(asyncResult.current).toBe(syncResult.current)
859
+ expect(asyncResult.next).toBe(syncResult.next)
860
+ expect(syncResult.current).toBe(49n)
861
+ },
862
+ )
863
+ })
864
+
865
+ describe("Edge Cases", () => {
866
+ it("should handle single update correctly", async () => {
867
+ const beeClient = bee as unknown as Bee
868
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
869
+ const owner = updater.getOwner()
870
+
871
+ await updater.update(createTestReference(42), stamper)
872
+
873
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
874
+ const result = await finder.findAt(0n, 0n)
875
+
876
+ expect(result.current).toBe(0n)
877
+ expect(result.next).toBe(1n)
878
+ })
879
+
880
+ it("should handle maximum practical index", async () => {
881
+ const beeClient = bee as unknown as Bee
882
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
883
+
884
+ // Start from a high index
885
+ updater.setState({ nextIndex: 1000000n })
886
+
887
+ await updater.update(createTestReference(1), stamper)
888
+ expect(updater.getState().nextIndex).toBe(1000001n)
889
+
890
+ await updater.update(createTestReference(2), stamper)
891
+ expect(updater.getState().nextIndex).toBe(1000002n)
892
+ })
893
+
894
+ it("should preserve reference integrity through update cycle", async () => {
895
+ const beeClient = bee as unknown as Bee
896
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
897
+
898
+ const ref32 = createTestReference(123)
899
+ const ref64 = createTestReference64(456)
900
+
901
+ const socAddress1 = await updater.update(ref32, stamper)
902
+ const socAddress2 = await updater.update(ref64, stamper)
903
+
904
+ // SOC addresses should be valid 32-byte addresses
905
+ expect(socAddress1).toHaveLength(32)
906
+ expect(socAddress2).toHaveLength(32)
907
+ expect(socAddress1).not.toEqual(socAddress2)
908
+ })
909
+
910
+ it("should handle getOwner correctly", async () => {
911
+ const beeClient = bee as unknown as Bee
912
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
913
+
914
+ const owner = updater.getOwner()
915
+ const expectedOwner = signer.publicKey().address()
916
+
917
+ expect(owner.toHex()).toBe(expectedOwner.toHex())
918
+ })
919
+ })
920
+
921
+ describe("Finder Consistency", () => {
922
+ it("should return consistent results across multiple queries", async () => {
923
+ const beeClient = bee as unknown as Bee
924
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
925
+ const owner = updater.getOwner()
926
+
927
+ await updater.update(createTestReference(1), stamper)
928
+ await updater.update(createTestReference(2), stamper)
929
+ await updater.update(createTestReference(3), stamper)
930
+
931
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
932
+
933
+ // Query multiple times
934
+ const results = await Promise.all([
935
+ finder.findAt(0n, 0n),
936
+ finder.findAt(0n, 0n),
937
+ finder.findAt(0n, 0n),
938
+ ])
939
+
940
+ for (const result of results) {
941
+ expect(result.current).toBe(2n)
942
+ expect(result.next).toBe(3n)
943
+ }
944
+ })
945
+
946
+ it("should handle interleaved reads and writes", async () => {
947
+ const beeClient = bee as unknown as Bee
948
+ const updater = new BasicSequentialUpdater(beeClient, topic, signer)
949
+ const owner = updater.getOwner()
950
+ const finder = new SyncSequentialFinder(beeClient, topic, owner)
951
+
952
+ // Write, read, write, read pattern
953
+ await updater.update(createTestReference(1), stamper)
954
+ const result1 = await finder.findAt(0n, 0n)
955
+ expect(result1.current).toBe(0n)
956
+
957
+ await updater.update(createTestReference(2), stamper)
958
+ const result2 = await finder.findAt(0n, 0n)
959
+ expect(result2.current).toBe(1n)
960
+
961
+ await updater.update(createTestReference(3), stamper)
962
+ const result3 = await finder.findAt(0n, 0n)
963
+ expect(result3.current).toBe(2n)
964
+ })
965
+ })
966
+ })