@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,360 @@
1
+ /**
2
+ * Unit tests for ACT History Management
3
+ */
4
+
5
+ import { describe, it, expect, vi } from "vitest"
6
+ import {
7
+ calculateReversedTimestamp,
8
+ calculateOriginalTimestamp,
9
+ createHistoryManifest,
10
+ addHistoryEntry,
11
+ getLatestEntry,
12
+ getEntryAtTimestamp,
13
+ getAllEntries,
14
+ serializeHistoryTree,
15
+ saveHistoryTreeRecursively,
16
+ deserializeHistory,
17
+ loadHistoryEntries,
18
+ getCurrentTimestamp,
19
+ } from "./history"
20
+ import { uint8ArrayToHex, hexToUint8Array } from "../../utils/hex"
21
+
22
+ // Helper to create a random 32-byte hex string
23
+ function randomHexRef(): string {
24
+ const bytes = new Uint8Array(32)
25
+ crypto.getRandomValues(bytes)
26
+ return uint8ArrayToHex(bytes)
27
+ }
28
+
29
+ describe("Timestamp calculations", () => {
30
+ it("calculateReversedTimestamp should reverse timestamp correctly", () => {
31
+ const timestamp = 1700000000
32
+ const reversed = calculateReversedTimestamp(timestamp)
33
+
34
+ // MaxInt64 = 9223372036854775807
35
+ // 9223372036854775807 - 1700000000 = 9223372035154775807
36
+ expect(reversed).toBe("9223372035154775807")
37
+ })
38
+
39
+ it("calculateOriginalTimestamp should restore original timestamp", () => {
40
+ const original = 1700000000
41
+ const reversed = calculateReversedTimestamp(original)
42
+ const restored = calculateOriginalTimestamp(reversed)
43
+
44
+ expect(restored).toBe(original)
45
+ })
46
+
47
+ it("reversed timestamps should sort correctly (latest first)", () => {
48
+ const timestamps = [1000, 2000, 3000, 4000, 5000]
49
+ const reversed = timestamps.map(calculateReversedTimestamp)
50
+
51
+ // Sort ascending
52
+ reversed.sort((a, b) => a.localeCompare(b))
53
+
54
+ // After sorting, convert back to original timestamps
55
+ const sortedOriginals = reversed.map(calculateOriginalTimestamp)
56
+
57
+ // Should be sorted descending (latest first)
58
+ expect(sortedOriginals).toEqual([5000, 4000, 3000, 2000, 1000])
59
+ })
60
+
61
+ it("should handle edge cases for timestamps", () => {
62
+ // Very small timestamp
63
+ const small = calculateReversedTimestamp(0)
64
+ expect(calculateOriginalTimestamp(small)).toBe(0)
65
+
66
+ // Current time range timestamp
67
+ const now = Math.floor(Date.now() / 1000)
68
+ const nowReversed = calculateReversedTimestamp(now)
69
+ expect(calculateOriginalTimestamp(nowReversed)).toBe(now)
70
+ })
71
+ })
72
+
73
+ describe("History manifest operations", () => {
74
+ it("createHistoryManifest should create empty MantarayNode", () => {
75
+ const manifest = createHistoryManifest()
76
+ expect(manifest).toBeDefined()
77
+ expect(manifest.forks.size).toBe(0)
78
+ })
79
+
80
+ it("addHistoryEntry should add entry with correct path and reference", () => {
81
+ const manifest = createHistoryManifest()
82
+ const timestamp = 1700000000
83
+ const actReference = randomHexRef()
84
+
85
+ addHistoryEntry(manifest, timestamp, actReference)
86
+
87
+ // Should have one fork
88
+ expect(manifest.forks.size).toBeGreaterThan(0)
89
+
90
+ // Get latest entry and verify
91
+ const latest = getLatestEntry(manifest)
92
+ expect(latest).toBeDefined()
93
+ expect(latest?.timestamp).toBe(timestamp)
94
+ expect(latest?.metadata.actReference).toBe(actReference)
95
+ })
96
+
97
+ it("addHistoryEntry should store encryptedGranteeListRef in metadata", () => {
98
+ const manifest = createHistoryManifest()
99
+ const timestamp = 1700000000
100
+ const actReference = randomHexRef()
101
+ const granteeListRef = randomHexRef()
102
+
103
+ addHistoryEntry(manifest, timestamp, actReference, granteeListRef)
104
+
105
+ const latest = getLatestEntry(manifest)
106
+ expect(latest).toBeDefined()
107
+ expect(latest?.metadata.encryptedGranteeListRef).toBe(granteeListRef)
108
+ })
109
+
110
+ it("getLatestEntry should return most recent entry", () => {
111
+ const manifest = createHistoryManifest()
112
+
113
+ // Add entries in order
114
+ const ref1 = randomHexRef()
115
+ const ref2 = randomHexRef()
116
+ const ref3 = randomHexRef()
117
+
118
+ addHistoryEntry(manifest, 1000, ref1)
119
+ addHistoryEntry(manifest, 2000, ref2)
120
+ addHistoryEntry(manifest, 3000, ref3)
121
+
122
+ const latest = getLatestEntry(manifest)
123
+ expect(latest).toBeDefined()
124
+ expect(latest?.timestamp).toBe(3000)
125
+ expect(latest?.metadata.actReference).toBe(ref3)
126
+ })
127
+
128
+ it("getLatestEntry should return undefined for empty manifest", () => {
129
+ const manifest = createHistoryManifest()
130
+ const latest = getLatestEntry(manifest)
131
+ expect(latest).toBeUndefined()
132
+ })
133
+
134
+ it("getEntryAtTimestamp should find correct entry", () => {
135
+ const manifest = createHistoryManifest()
136
+
137
+ const ref1 = randomHexRef()
138
+ const ref2 = randomHexRef()
139
+ const ref3 = randomHexRef()
140
+
141
+ addHistoryEntry(manifest, 1000, ref1)
142
+ addHistoryEntry(manifest, 2000, ref2)
143
+ addHistoryEntry(manifest, 3000, ref3)
144
+
145
+ // Query exactly at timestamp 2000
146
+ const entry = getEntryAtTimestamp(manifest, 2000)
147
+ expect(entry).toBeDefined()
148
+ expect(entry?.timestamp).toBe(2000)
149
+ expect(entry?.metadata.actReference).toBe(ref2)
150
+ })
151
+
152
+ it("getEntryAtTimestamp should find entry at or before timestamp", () => {
153
+ const manifest = createHistoryManifest()
154
+
155
+ const ref1 = randomHexRef()
156
+ const ref2 = randomHexRef()
157
+
158
+ addHistoryEntry(manifest, 1000, ref1)
159
+ addHistoryEntry(manifest, 3000, ref2)
160
+
161
+ // Query at timestamp 2000 (between 1000 and 3000)
162
+ // Should return entry at 1000 (most recent at or before 2000)
163
+ const entry = getEntryAtTimestamp(manifest, 2000)
164
+ expect(entry).toBeDefined()
165
+ expect(entry?.timestamp).toBe(1000)
166
+ })
167
+
168
+ it("getAllEntries should return entries sorted newest first", () => {
169
+ const manifest = createHistoryManifest()
170
+
171
+ const ref1 = randomHexRef()
172
+ const ref2 = randomHexRef()
173
+ const ref3 = randomHexRef()
174
+
175
+ // Add in arbitrary order
176
+ addHistoryEntry(manifest, 2000, ref2)
177
+ addHistoryEntry(manifest, 1000, ref1)
178
+ addHistoryEntry(manifest, 3000, ref3)
179
+
180
+ const entries = getAllEntries(manifest)
181
+
182
+ expect(entries.length).toBe(3)
183
+ // Should be sorted newest first
184
+ expect(entries[0].timestamp).toBe(3000)
185
+ expect(entries[1].timestamp).toBe(2000)
186
+ expect(entries[2].timestamp).toBe(1000)
187
+ })
188
+
189
+ it("getAllEntries should return empty array for empty manifest", () => {
190
+ const manifest = createHistoryManifest()
191
+ const entries = getAllEntries(manifest)
192
+ expect(entries).toEqual([])
193
+ })
194
+ })
195
+
196
+ describe("History serialization", () => {
197
+ it("serializeHistoryTree should produce correct blobs", async () => {
198
+ const manifest = createHistoryManifest()
199
+ addHistoryEntry(manifest, 1000, randomHexRef())
200
+
201
+ const result = await serializeHistoryTree(manifest)
202
+
203
+ expect(result.blobs.size).toBeGreaterThan(0)
204
+ expect(result.rootReference).toBeDefined()
205
+ expect(result.rootReference.length).toBe(64) // 32 bytes hex
206
+ })
207
+
208
+ it("saveHistoryTreeRecursively should upload bottom-up", async () => {
209
+ const manifest = createHistoryManifest()
210
+ addHistoryEntry(manifest, 1000, randomHexRef())
211
+ addHistoryEntry(manifest, 2000, randomHexRef())
212
+
213
+ const uploads: { data: Uint8Array; isRoot: boolean }[] = []
214
+
215
+ const uploadFn = vi.fn(async (data: Uint8Array, isRoot: boolean) => {
216
+ uploads.push({ data, isRoot })
217
+ // Return a mock reference based on content
218
+ const hash = new Uint8Array(32)
219
+ for (let i = 0; i < Math.min(data.length, 32); i++) {
220
+ hash[i] = data[i]
221
+ }
222
+ return { reference: uint8ArrayToHex(hash), tagUid: 123 }
223
+ })
224
+
225
+ const result = await saveHistoryTreeRecursively(manifest, uploadFn)
226
+
227
+ // Should have uploaded at least one node
228
+ expect(uploadFn).toHaveBeenCalled()
229
+ expect(result.rootReference).toBeDefined()
230
+ expect(result.tagUid).toBe(123)
231
+
232
+ // Last upload should be the root
233
+ expect(uploads[uploads.length - 1].isRoot).toBe(true)
234
+ })
235
+
236
+ it("deserializeHistory should parse MantarayNode format", async () => {
237
+ const manifest = createHistoryManifest()
238
+ const actRef = randomHexRef()
239
+ addHistoryEntry(manifest, 1000, actRef)
240
+
241
+ // Serialize
242
+ const serialized = await serializeHistoryTree(manifest)
243
+
244
+ // Deserialize root
245
+ const selfAddress = hexToUint8Array(serialized.rootReference)
246
+ const rootData = serialized.blobs.get(serialized.rootReference)
247
+ expect(rootData).toBeDefined()
248
+
249
+ const deserialized = deserializeHistory(rootData!, selfAddress)
250
+ expect(deserialized).toBeDefined()
251
+ expect(deserialized.forks.size).toBeGreaterThan(0)
252
+ })
253
+
254
+ it("loadHistoryEntries should load child nodes recursively", async () => {
255
+ const manifest = createHistoryManifest()
256
+ const actRef = randomHexRef()
257
+ const granteeListRef = randomHexRef()
258
+ addHistoryEntry(manifest, 1000, actRef, granteeListRef)
259
+
260
+ // Serialize
261
+ const serialized = await serializeHistoryTree(manifest)
262
+
263
+ // Deserialize root
264
+ const selfAddress = hexToUint8Array(serialized.rootReference)
265
+ const rootData = serialized.blobs.get(serialized.rootReference)!
266
+ const deserialized = deserializeHistory(rootData, selfAddress)
267
+
268
+ // Load children
269
+ await loadHistoryEntries(deserialized, async (ref) => {
270
+ const data = serialized.blobs.get(ref)
271
+ if (!data) {
272
+ throw new Error(`Data not found for reference: ${ref}`)
273
+ }
274
+ return data
275
+ })
276
+
277
+ // Now should be able to get entries
278
+ const entry = getLatestEntry(deserialized)
279
+ expect(entry).toBeDefined()
280
+ expect(entry?.timestamp).toBe(1000)
281
+ expect(entry?.metadata.actReference).toBe(actRef)
282
+ expect(entry?.metadata.encryptedGranteeListRef).toBe(granteeListRef)
283
+ })
284
+ })
285
+
286
+ describe("getCurrentTimestamp", () => {
287
+ it("should return current Unix timestamp in seconds", () => {
288
+ const before = Math.floor(Date.now() / 1000)
289
+ const timestamp = getCurrentTimestamp()
290
+ const after = Math.floor(Date.now() / 1000)
291
+
292
+ expect(timestamp).toBeGreaterThanOrEqual(before)
293
+ expect(timestamp).toBeLessThanOrEqual(after)
294
+ })
295
+ })
296
+
297
+ describe("History roundtrip", () => {
298
+ it("should preserve all data through serialize/deserialize/load cycle", async () => {
299
+ const manifest = createHistoryManifest()
300
+
301
+ // Add multiple entries with metadata
302
+ const entries = [
303
+ {
304
+ timestamp: 1000,
305
+ actRef: randomHexRef(),
306
+ granteeListRef: randomHexRef(),
307
+ },
308
+ {
309
+ timestamp: 2000,
310
+ actRef: randomHexRef(),
311
+ granteeListRef: randomHexRef(),
312
+ },
313
+ {
314
+ timestamp: 3000,
315
+ actRef: randomHexRef(),
316
+ granteeListRef: randomHexRef(),
317
+ },
318
+ ]
319
+
320
+ for (const e of entries) {
321
+ addHistoryEntry(manifest, e.timestamp, e.actRef, e.granteeListRef)
322
+ }
323
+
324
+ // Serialize
325
+ const serialized = await serializeHistoryTree(manifest)
326
+
327
+ // Deserialize
328
+ const selfAddress = hexToUint8Array(serialized.rootReference)
329
+ const rootData = serialized.blobs.get(serialized.rootReference)!
330
+ const deserialized = deserializeHistory(rootData, selfAddress)
331
+
332
+ // Load children
333
+ await loadHistoryEntries(deserialized, async (ref) => {
334
+ const data = serialized.blobs.get(ref)
335
+ if (!data) {
336
+ throw new Error(`Data not found for reference: ${ref}`)
337
+ }
338
+ return data
339
+ })
340
+
341
+ // Verify all entries are present
342
+ const allEntries = getAllEntries(deserialized)
343
+ expect(allEntries.length).toBe(3)
344
+
345
+ // Verify entries are sorted newest first
346
+ expect(allEntries[0].timestamp).toBe(3000)
347
+ expect(allEntries[1].timestamp).toBe(2000)
348
+ expect(allEntries[2].timestamp).toBe(1000)
349
+
350
+ // Verify metadata is preserved
351
+ for (const original of entries) {
352
+ const found = allEntries.find((e) => e.timestamp === original.timestamp)
353
+ expect(found).toBeDefined()
354
+ expect(found?.metadata.actReference).toBe(original.actRef)
355
+ expect(found?.metadata.encryptedGranteeListRef).toBe(
356
+ original.granteeListRef,
357
+ )
358
+ }
359
+ })
360
+ })