@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
package/src/schemas.ts ADDED
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Entity Schemas with Zod
3
+ *
4
+ * Defines Zod schemas for storage entities with transforms to convert
5
+ * serialized primitives to bee-js runtime types. Types are derived using
6
+ * z.infer to guarantee schema/type consistency.
7
+ */
8
+
9
+ import { z } from "zod"
10
+ import {
11
+ EthAddress,
12
+ BatchId as BeeBatchId,
13
+ Bytes,
14
+ PrivateKey as BeePrivateKey,
15
+ } from "@ethersphere/bee-js"
16
+
17
+ // ============================================================================
18
+ // Network Settings Constants
19
+ // ============================================================================
20
+
21
+ export const DEFAULT_BEE_NODE_URL = "https://api.gateway.ethswarm.org/"
22
+ export const DEFAULT_GNOSIS_RPC_URL = "https://xdai.fairdatasociety.org/"
23
+
24
+ // ============================================================================
25
+ // Base Validation Schemas (hex strings, no transforms)
26
+ // ============================================================================
27
+
28
+ const hexString = (length: number) =>
29
+ z.string().regex(new RegExp(`^[0-9a-fA-F]{${length}}$`), {
30
+ message: `Must be a ${length}-character hex string`,
31
+ })
32
+
33
+ // Support both regular (32-byte = 64 hex chars) and encrypted (64-byte = 128 hex chars) references
34
+ export const ReferenceSchema = z
35
+ .string()
36
+ .regex(/^[0-9a-fA-F]{64}$|^[0-9a-fA-F]{128}$/, {
37
+ message:
38
+ "Reference must be 64 hex chars (32 bytes) or 128 hex chars (64 bytes for encrypted)",
39
+ })
40
+ export const BatchIdSchema = hexString(64) // 32 bytes
41
+ export const AddressSchema = hexString(40) // 20 bytes
42
+ export const PrivateKeySchema = hexString(64) // 32 bytes
43
+ export const EncryptionKeySchema = hexString(64) // 32 bytes symmetric key
44
+ export const IdentifierSchema = hexString(64) // 32 bytes
45
+ export const SignatureSchema = hexString(130) // 65 bytes
46
+ export const TimestampSchema = z.preprocess(
47
+ (val) => (typeof val === "bigint" ? val.toString() : val),
48
+ z.union([z.number(), z.string()]),
49
+ )
50
+ export const FeedIndexSchema = z.preprocess(
51
+ (val) => (typeof val === "bigint" ? val.toString() : val),
52
+ z.union([z.number(), z.string()]),
53
+ )
54
+
55
+ export type Reference = z.infer<typeof ReferenceSchema>
56
+ export type BatchId = z.infer<typeof BatchIdSchema>
57
+ export type Address = z.infer<typeof AddressSchema>
58
+ export type PrivateKey = z.infer<typeof PrivateKeySchema>
59
+ export type Identifier = z.infer<typeof IdentifierSchema>
60
+ export type Signature = z.infer<typeof SignatureSchema>
61
+ export type Timestamp = z.infer<typeof TimestampSchema>
62
+ export type FeedIndex = z.infer<typeof FeedIndexSchema>
63
+
64
+ // ============================================================================
65
+ // Primitive → bee-js Type Transforms (internal, for entity schemas)
66
+ // ============================================================================
67
+
68
+ /**
69
+ * Schema for EthAddress - validates 40-char hex string, transforms to EthAddress
70
+ */
71
+ const StoredEthAddress = z
72
+ .string()
73
+ .length(40)
74
+ .transform((s) => new EthAddress(s))
75
+
76
+ /**
77
+ * Schema for BatchId - validates 64-char hex string, transforms to BatchId
78
+ */
79
+ const StoredBatchId = z
80
+ .string()
81
+ .length(64)
82
+ .transform((s) => new BeeBatchId(s))
83
+
84
+ /**
85
+ * Schema for PrivateKey - validates 64-char hex string, transforms to PrivateKey
86
+ */
87
+ const StoredPrivateKey = z
88
+ .string()
89
+ .length(64)
90
+ .transform((s) => new BeePrivateKey(s))
91
+
92
+ /**
93
+ * Schema for Bytes - validates number array, transforms to Bytes
94
+ */
95
+ const StoredBytes = z
96
+ .array(z.number())
97
+ .transform((arr) => new Bytes(new Uint8Array(arr)))
98
+
99
+ // ============================================================================
100
+ // Account Schemas
101
+ // ============================================================================
102
+
103
+ /**
104
+ * Passkey Account Schema V1
105
+ */
106
+ export const PasskeyAccountSchemaV1 = z.object({
107
+ id: StoredEthAddress,
108
+ name: z.string(),
109
+ createdAt: z.number(),
110
+ type: z.literal("passkey"),
111
+ credentialId: z.string(),
112
+ swarmEncryptionKey: z.string().length(64), // NEW: derived encryption key for Swarm data (64-char hex)
113
+ defaultPostageStampBatchID: StoredBatchId.optional(), // NEW: account default stamp
114
+ })
115
+
116
+ /**
117
+ * Ethereum Account Schema V1
118
+ */
119
+ export const EthereumAccountSchemaV1 = z.object({
120
+ id: StoredEthAddress,
121
+ name: z.string(),
122
+ createdAt: z.number(),
123
+ type: z.literal("ethereum"),
124
+ ethereumAddress: StoredEthAddress,
125
+ encryptedMasterKey: StoredBytes,
126
+ encryptionSalt: StoredBytes,
127
+ encryptedSecretSeed: StoredBytes, // Encrypted secret seed for later retrieval
128
+ swarmEncryptionKey: z.string().length(64), // NEW: derived encryption key for Swarm data (64-char hex)
129
+ defaultPostageStampBatchID: StoredBatchId.optional(), // NEW: account default stamp
130
+ })
131
+
132
+ /**
133
+ * Agent Account Schema V1
134
+ * For automated testing and programmatic use with BIP39 seed phrases
135
+ * Seed phrase is NOT stored - must be re-entered on each authentication (like passkey)
136
+ */
137
+ export const AgentAccountSchemaV1 = z.object({
138
+ id: StoredEthAddress,
139
+ name: z.string(),
140
+ createdAt: z.number(),
141
+ type: z.literal("agent"),
142
+ swarmEncryptionKey: z.string().length(64), // derived encryption key for Swarm data (64-char hex)
143
+ defaultPostageStampBatchID: StoredBatchId.optional(),
144
+ })
145
+
146
+ /**
147
+ * Account Schema V1 (discriminated union)
148
+ */
149
+ export const AccountSchemaV1 = z.discriminatedUnion("type", [
150
+ PasskeyAccountSchemaV1,
151
+ EthereumAccountSchemaV1,
152
+ AgentAccountSchemaV1,
153
+ ])
154
+
155
+ // ============================================================================
156
+ // Identity Schema
157
+ // ============================================================================
158
+
159
+ /**
160
+ * Identity Schema V1
161
+ */
162
+ export const IdentitySchemaV1 = z.object({
163
+ id: AddressSchema,
164
+ accountId: StoredEthAddress,
165
+ name: z.string(),
166
+ defaultPostageStampBatchID: StoredBatchId.optional(),
167
+ createdAt: z.number(),
168
+ settings: z
169
+ .object({
170
+ appSessionDuration: z.number().optional(),
171
+ })
172
+ .optional(),
173
+ })
174
+
175
+ // ============================================================================
176
+ // Connected App Schema
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Connected App Schema V1 (no transforms needed - all primitives)
181
+ */
182
+ export const ConnectedAppSchemaV1 = z.object({
183
+ appUrl: z.string(),
184
+ appName: z.string(),
185
+ lastConnectedAt: z.number(),
186
+ identityId: z.string(),
187
+ appIcon: z.string().optional(),
188
+ appDescription: z.string().optional(),
189
+ connectedUntil: z.number().optional(),
190
+ appSecret: z.string().optional(),
191
+ })
192
+
193
+ // ============================================================================
194
+ // Postage Stamp Schema
195
+ // ============================================================================
196
+
197
+ /**
198
+ * Postage Stamp Schema V1
199
+ */
200
+ export const PostageStampSchemaV1 = z.object({
201
+ accountId: z.string().length(40), // CHANGED: was identityId
202
+ batchID: StoredBatchId,
203
+ signerKey: StoredPrivateKey,
204
+ utilization: z.number(),
205
+ usable: z.boolean(),
206
+ depth: z.number(),
207
+ amount: z.string().transform((val) => BigInt(val)),
208
+ bucketDepth: z.number(),
209
+ blockNumber: z.number(),
210
+ immutableFlag: z.boolean(),
211
+ exists: z.boolean(),
212
+ batchTTL: z.number().optional(),
213
+ createdAt: z.number(),
214
+ })
215
+
216
+ // ============================================================================
217
+ // Sync State Snapshot Schemas
218
+ // ============================================================================
219
+
220
+ /**
221
+ * Account Metadata Schema V1
222
+ */
223
+ export const AccountMetadataSchemaV1 = z.object({
224
+ accountName: z.string(),
225
+ defaultPostageStampBatchID: z.string().length(64).optional(), // BatchId hex string
226
+ createdAt: z.number(),
227
+ lastModified: z.number(),
228
+ })
229
+
230
+ const ACCOUNT_STATE_SNAPSHOT_VERSION = 1
231
+
232
+ /**
233
+ * Unified account state snapshot schema used by both file export and Swarm sync.
234
+ * Contains minimal metadata instead of the full Account object.
235
+ */
236
+ export const AccountStateSnapshotSchemaV1 = z.object({
237
+ version: z.literal(ACCOUNT_STATE_SNAPSHOT_VERSION),
238
+ timestamp: z.number(),
239
+ accountId: z.string().length(40),
240
+ metadata: AccountMetadataSchemaV1,
241
+ identities: z.array(IdentitySchemaV1),
242
+ connectedApps: z.array(ConnectedAppSchemaV1),
243
+ postageStamps: z.array(PostageStampSchemaV1),
244
+ })
245
+
246
+ // ============================================================================
247
+ // Derived Types (guaranteed to match current schema version)
248
+ // ============================================================================
249
+
250
+ export type PasskeyAccount = z.infer<typeof PasskeyAccountSchemaV1>
251
+ export type EthereumAccount = z.infer<typeof EthereumAccountSchemaV1>
252
+ export type AgentAccount = z.infer<typeof AgentAccountSchemaV1>
253
+ export type Account = z.infer<typeof AccountSchemaV1>
254
+ export type Identity = z.infer<typeof IdentitySchemaV1>
255
+ export type ConnectedApp = z.infer<typeof ConnectedAppSchemaV1>
256
+ export type PostageStamp = z.infer<typeof PostageStampSchemaV1>
257
+ export type AccountMetadata = z.infer<typeof AccountMetadataSchemaV1>
258
+ export type AccountStateSnapshot = z.infer<typeof AccountStateSnapshotSchemaV1>
259
+
260
+ // ============================================================================
261
+ // Network Settings Schema
262
+ // ============================================================================
263
+
264
+ /**
265
+ * Network Settings Schema V1
266
+ * Stores user-configurable network endpoints (Bee node and Gnosis RPC)
267
+ */
268
+ export const NetworkSettingsSchemaV1 = z.object({
269
+ beeNodeUrl: z.string().url(),
270
+ gnosisRpcUrl: z.string().url(),
271
+ })
272
+
273
+ export type NetworkSettings = z.infer<typeof NetworkSettingsSchemaV1>
274
+
275
+ // ============================================================================
276
+ // BroadcastChannel Message Schemas
277
+ // ============================================================================
278
+
279
+ /**
280
+ * Bucket update entry for cross-tab synchronization
281
+ */
282
+ export const BucketUpdateSchema = z.object({
283
+ index: z.number().int().min(0).max(65535),
284
+ value: z.number().int().min(0),
285
+ })
286
+
287
+ /**
288
+ * Utilization update message sent via BroadcastChannel
289
+ */
290
+ export const UtilizationUpdateMessageSchema = z.object({
291
+ type: z.literal("utilization-updated"),
292
+ batchId: z.string().length(64),
293
+ buckets: z.array(BucketUpdateSchema),
294
+ })
295
+
296
+ export type BucketUpdate = z.infer<typeof BucketUpdateSchema>
297
+ export type UtilizationUpdateMessage = z.infer<
298
+ typeof UtilizationUpdateMessageSchema
299
+ >
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Debounced Utilization Uploader
3
+ *
4
+ * Batches multiple utilization updates together to minimize upload frequency.
5
+ * Uses a per-batch debounce mechanism with configurable delay.
6
+ */
7
+
8
+ import type { DirtyChunkTracker } from "../utils/batch-utilization"
9
+
10
+ /**
11
+ * Default debounce delay in milliseconds
12
+ */
13
+ const DEFAULT_DEBOUNCE_DELAY_MS = 1000
14
+
15
+ /**
16
+ * Pending upload task for a batch
17
+ */
18
+ interface PendingUpload {
19
+ /** Dirty chunk tracker accumulating changes */
20
+ tracker: DirtyChunkTracker
21
+
22
+ /** Timer ID for debounce */
23
+ timerId: ReturnType<typeof setTimeout> | undefined
24
+
25
+ /** Upload function to execute */
26
+ uploadFn: () => Promise<void>
27
+
28
+ /** Promise resolvers for awaiting completion */
29
+ promise: { resolve: () => void; reject: (error: Error) => void }
30
+ }
31
+
32
+ /**
33
+ * Debounced uploader for batch utilization data
34
+ *
35
+ * Batches multiple updates within a time window and uploads only once.
36
+ * Each batch has its own independent debounce timer.
37
+ */
38
+ export class DebouncedUtilizationUploader {
39
+ private pendingUploads = new Map<string, PendingUpload>()
40
+ private defaultDelay: number
41
+
42
+ /**
43
+ * Create a new debounced uploader
44
+ * @param delay - Debounce delay in milliseconds (default: 1000ms = 1s)
45
+ */
46
+ constructor(delay = DEFAULT_DEBOUNCE_DELAY_MS) {
47
+ this.defaultDelay = delay
48
+ }
49
+
50
+ /**
51
+ * Schedule an upload for a batch (debounced)
52
+ *
53
+ * If multiple updates occur within the debounce window, they are merged
54
+ * and only one upload is performed.
55
+ *
56
+ * @param batchId - Batch ID (hex string)
57
+ * @param tracker - Dirty chunk tracker with current changes
58
+ * @param uploadFn - Function to execute upload
59
+ * @param delay - Optional custom delay for this upload
60
+ * @returns Promise that resolves when upload completes
61
+ */
62
+ scheduleUpload(
63
+ batchId: string,
64
+ tracker: DirtyChunkTracker,
65
+ uploadFn: () => Promise<void>,
66
+ delay?: number,
67
+ ): Promise<void> {
68
+ const actualDelay = delay ?? this.defaultDelay
69
+
70
+ // Cancel existing timer if present
71
+ const existing = this.pendingUploads.get(batchId)
72
+ if (existing?.timerId) {
73
+ clearTimeout(existing.timerId)
74
+ // Reject old promise
75
+ existing.promise.reject(new Error("Upload cancelled by new request"))
76
+ }
77
+
78
+ // Merge with existing tracker or create new
79
+ const mergedTracker = existing?.tracker ?? tracker
80
+
81
+ // If we're merging with an existing tracker, copy dirty chunks
82
+ if (existing && existing.tracker !== tracker) {
83
+ for (const chunkIndex of tracker.getDirtyChunks()) {
84
+ mergedTracker.markDirty(chunkIndex * 1024) // Mark any bucket in the chunk
85
+ }
86
+ }
87
+
88
+ // Create new promise
89
+ return new Promise<void>((resolve, reject) => {
90
+ // Schedule new upload
91
+ const timerId = setTimeout(async () => {
92
+ try {
93
+ await uploadFn()
94
+ resolve()
95
+ } catch (error) {
96
+ console.error(
97
+ `[DebouncedUploader] Upload failed for batch ${batchId}:`,
98
+ error,
99
+ )
100
+ reject(error instanceof Error ? error : new Error(String(error)))
101
+ } finally {
102
+ // Remove from pending
103
+ this.pendingUploads.delete(batchId)
104
+ }
105
+ }, actualDelay)
106
+
107
+ // Store pending upload
108
+ this.pendingUploads.set(batchId, {
109
+ tracker: mergedTracker,
110
+ timerId,
111
+ uploadFn,
112
+ promise: { resolve, reject },
113
+ })
114
+ })
115
+ }
116
+
117
+ /**
118
+ * Flush pending upload for a batch immediately (cancel debounce)
119
+ * @param batchId - Batch ID to flush
120
+ */
121
+ async flush(batchId: string): Promise<void> {
122
+ const pending = this.pendingUploads.get(batchId)
123
+ if (!pending) {
124
+ return
125
+ }
126
+
127
+ // Cancel timer
128
+ if (pending.timerId) {
129
+ clearTimeout(pending.timerId)
130
+ }
131
+
132
+ // Execute immediately
133
+ try {
134
+ await pending.uploadFn()
135
+ } finally {
136
+ this.pendingUploads.delete(batchId)
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Flush all pending uploads immediately
142
+ */
143
+ async flushAll(): Promise<void> {
144
+ const batchIds = Array.from(this.pendingUploads.keys())
145
+
146
+ await Promise.all(batchIds.map((batchId) => this.flush(batchId)))
147
+ }
148
+
149
+ /**
150
+ * Cancel pending upload for a batch (discard changes)
151
+ * @param batchId - Batch ID to cancel
152
+ */
153
+ cancel(batchId: string): void {
154
+ const pending = this.pendingUploads.get(batchId)
155
+ if (!pending) {
156
+ return
157
+ }
158
+
159
+ if (pending.timerId) {
160
+ clearTimeout(pending.timerId)
161
+ }
162
+
163
+ this.pendingUploads.delete(batchId)
164
+ }
165
+
166
+ /**
167
+ * Cancel all pending uploads (discard all changes)
168
+ */
169
+ cancelAll(): void {
170
+ for (const pending of this.pendingUploads.values()) {
171
+ if (pending.timerId) {
172
+ clearTimeout(pending.timerId)
173
+ }
174
+ }
175
+
176
+ this.pendingUploads.clear()
177
+ }
178
+
179
+ /**
180
+ * Get count of pending uploads
181
+ */
182
+ getPendingCount(): number {
183
+ return this.pendingUploads.size
184
+ }
185
+
186
+ /**
187
+ * Check if a batch has a pending upload
188
+ */
189
+ hasPending(batchId: string): boolean {
190
+ return this.pendingUploads.has(batchId)
191
+ }
192
+ }