@econ-v1/domain 6.7.0 → 6.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/dist/errors/index.d.ts +96 -25
  2. package/dist/errors/index.d.ts.map +1 -1
  3. package/dist/events/index.d.ts +127 -20
  4. package/dist/events/index.d.ts.map +1 -1
  5. package/dist/index.d.ts +250 -47
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/models/agent.d.ts +25 -4
  8. package/dist/models/agent.d.ts.map +1 -1
  9. package/dist/models/app-data.d.ts +84 -0
  10. package/dist/models/app-data.d.ts.map +1 -0
  11. package/dist/models/conversation.d.ts +12 -2
  12. package/dist/models/conversation.d.ts.map +1 -1
  13. package/dist/models/delegation.d.ts +12 -3
  14. package/dist/models/delegation.d.ts.map +1 -1
  15. package/dist/models/device-boot-state.d.ts +12 -0
  16. package/dist/models/device-boot-state.d.ts.map +1 -0
  17. package/dist/models/device-identity.d.ts +11 -3
  18. package/dist/models/device-identity.d.ts.map +1 -1
  19. package/dist/models/graph.d.ts +26 -3
  20. package/dist/models/graph.d.ts.map +1 -1
  21. package/dist/models/index.d.ts +82 -14
  22. package/dist/models/index.d.ts.map +1 -1
  23. package/dist/models/json.d.ts +5 -5
  24. package/dist/models/json.d.ts.map +1 -1
  25. package/dist/models/l402.d.ts +4 -5
  26. package/dist/models/l402.d.ts.map +1 -1
  27. package/dist/models/live-query-definition.d.ts +15 -16
  28. package/dist/models/live-query-definition.d.ts.map +1 -1
  29. package/dist/models/node-app-memory.d.ts +107 -0
  30. package/dist/models/node-app-memory.d.ts.map +1 -0
  31. package/dist/models/outbox-intent.d.ts +3 -3
  32. package/dist/models/outbox-intent.d.ts.map +1 -1
  33. package/dist/models/outbox.d.ts +67 -15
  34. package/dist/models/outbox.d.ts.map +1 -1
  35. package/dist/models/owner-views.d.ts +28 -28
  36. package/dist/models/owner-views.d.ts.map +1 -1
  37. package/dist/models/payment.d.ts +28 -9
  38. package/dist/models/payment.d.ts.map +1 -1
  39. package/dist/models/query-diff.d.ts +2 -2
  40. package/dist/models/query-diff.d.ts.map +1 -1
  41. package/dist/models/release-manifest.d.ts +32 -47
  42. package/dist/models/release-manifest.d.ts.map +1 -1
  43. package/dist/models/repository-write.d.ts +3 -3
  44. package/dist/models/repository-write.d.ts.map +1 -1
  45. package/dist/models/revisioned-commands.d.ts +66 -13
  46. package/dist/models/revisioned-commands.d.ts.map +1 -1
  47. package/dist/models/revisioned.d.ts +50 -10
  48. package/dist/models/revisioned.d.ts.map +1 -1
  49. package/dist/models/settings.d.ts +2 -2
  50. package/dist/models/settings.d.ts.map +1 -1
  51. package/dist/models/sync.d.ts +18 -18
  52. package/dist/models/sync.d.ts.map +1 -1
  53. package/dist/models/wallet.d.ts +16 -16
  54. package/dist/models/wallet.d.ts.map +1 -1
  55. package/dist/queries/certificate-trust.d.ts +13 -0
  56. package/dist/queries/certificate-trust.d.ts.map +1 -0
  57. package/dist/queries/conversations.d.ts +38 -7
  58. package/dist/queries/conversations.d.ts.map +1 -1
  59. package/dist/queries/discovery.d.ts +10 -10
  60. package/dist/queries/discovery.d.ts.map +1 -1
  61. package/dist/queries/notifications.d.ts +24 -4
  62. package/dist/queries/notifications.d.ts.map +1 -1
  63. package/dist/queries/onboarding-flow.d.ts +25 -0
  64. package/dist/queries/onboarding-flow.d.ts.map +1 -0
  65. package/dist/queries/onboarding-lcd-screen.d.ts +56 -0
  66. package/dist/queries/onboarding-lcd-screen.d.ts.map +1 -0
  67. package/dist/queries/onboarding-status.d.ts +10 -10
  68. package/dist/queries/onboarding-status.d.ts.map +1 -1
  69. package/dist/queries/revisioned.d.ts +47 -13
  70. package/dist/queries/revisioned.d.ts.map +1 -1
  71. package/dist/queries/wallet.d.ts +40 -10
  72. package/dist/queries/wallet.d.ts.map +1 -1
  73. package/dist/services/abort-signal.d.ts +54 -0
  74. package/dist/services/abort-signal.d.ts.map +1 -0
  75. package/dist/services/certificate-trust-service.d.ts +42 -0
  76. package/dist/services/certificate-trust-service.d.ts.map +1 -0
  77. package/dist/services/conversation-service.d.ts +103 -15
  78. package/dist/services/conversation-service.d.ts.map +1 -1
  79. package/dist/services/conversation-sync.d.ts +9 -5
  80. package/dist/services/conversation-sync.d.ts.map +1 -1
  81. package/dist/services/device-boot-state-service.d.ts +17 -0
  82. package/dist/services/device-boot-state-service.d.ts.map +1 -0
  83. package/dist/services/device-cleanup-service.d.ts +47 -0
  84. package/dist/services/device-cleanup-service.d.ts.map +1 -0
  85. package/dist/services/discovery-probe-service.d.ts +37 -46
  86. package/dist/services/discovery-probe-service.d.ts.map +1 -1
  87. package/dist/services/gossip-service.d.ts +28 -13
  88. package/dist/services/gossip-service.d.ts.map +1 -1
  89. package/dist/services/invite-redemption-service.d.ts +43 -0
  90. package/dist/services/invite-redemption-service.d.ts.map +1 -0
  91. package/dist/services/lan-candidate-refresh-service.d.ts +22 -7
  92. package/dist/services/lan-candidate-refresh-service.d.ts.map +1 -1
  93. package/dist/services/lcd-decision-service.d.ts +58 -0
  94. package/dist/services/lcd-decision-service.d.ts.map +1 -0
  95. package/dist/services/onboarding-checkpoint-store.d.ts +54 -0
  96. package/dist/services/onboarding-checkpoint-store.d.ts.map +1 -0
  97. package/dist/services/onboarding-flow-service.d.ts +180 -0
  98. package/dist/services/onboarding-flow-service.d.ts.map +1 -0
  99. package/dist/services/onboarding-status-service.d.ts +21 -38
  100. package/dist/services/onboarding-status-service.d.ts.map +1 -1
  101. package/dist/services/outbox-service.d.ts +105 -29
  102. package/dist/services/outbox-service.d.ts.map +1 -1
  103. package/dist/services/owner-claim-service.d.ts +187 -0
  104. package/dist/services/owner-claim-service.d.ts.map +1 -0
  105. package/dist/services/owner-view-refresh-service.d.ts +40 -0
  106. package/dist/services/owner-view-refresh-service.d.ts.map +1 -0
  107. package/dist/services/owner-view-service.d.ts +49 -4
  108. package/dist/services/owner-view-service.d.ts.map +1 -1
  109. package/dist/services/peer-directory-service.d.ts +53 -17
  110. package/dist/services/peer-directory-service.d.ts.map +1 -1
  111. package/dist/services/release-manifest-verifier.d.ts +18 -20
  112. package/dist/services/release-manifest-verifier.d.ts.map +1 -1
  113. package/dist/services/revisioned-domain-service.d.ts +50 -17
  114. package/dist/services/revisioned-domain-service.d.ts.map +1 -1
  115. package/dist/services/runtime-globals.d.ts +39 -0
  116. package/dist/services/runtime-globals.d.ts.map +1 -0
  117. package/dist/services/session-service.d.ts +23 -3
  118. package/dist/services/session-service.d.ts.map +1 -1
  119. package/dist/services/sync/authoritative-mirror.d.ts +88 -15
  120. package/dist/services/sync/authoritative-mirror.d.ts.map +1 -1
  121. package/dist/services/sync/index.d.ts +17 -5
  122. package/dist/services/sync/index.d.ts.map +1 -1
  123. package/dist/services/sync/log-sync.d.ts +42 -8
  124. package/dist/services/sync/log-sync.d.ts.map +1 -1
  125. package/dist/services/sync/revision-merge.d.ts +80 -13
  126. package/dist/services/sync/revision-merge.d.ts.map +1 -1
  127. package/dist/services/sync/strategy.d.ts +11 -1
  128. package/dist/services/sync/strategy.d.ts.map +1 -1
  129. package/dist/services/transport-manager.d.ts +84 -29
  130. package/dist/services/transport-manager.d.ts.map +1 -1
  131. package/dist/services/wallet-mirror-service.d.ts +81 -18
  132. package/dist/services/wallet-mirror-service.d.ts.map +1 -1
  133. package/dist/services/wifi-provisioning-service.d.ts +146 -0
  134. package/dist/services/wifi-provisioning-service.d.ts.map +1 -0
  135. package/package.json +18 -12
  136. package/src/errors/index.js +126 -0
  137. package/src/events/index.js +124 -0
  138. package/src/index.js +566 -0
  139. package/src/models/agent.js +43 -0
  140. package/src/models/app-data.js +122 -0
  141. package/src/models/conversation.js +36 -0
  142. package/{dist → src}/models/delegation.js +18 -1
  143. package/src/models/device-boot-state.js +3 -0
  144. package/{dist → src}/models/device-identity.js +18 -2
  145. package/{dist → src}/models/graph.js +30 -3
  146. package/src/models/index.js +175 -0
  147. package/src/models/json.js +18 -0
  148. package/src/models/l402.js +9 -0
  149. package/src/models/live-query-definition.js +30 -0
  150. package/src/models/node-app-memory.js +130 -0
  151. package/src/models/outbox-intent.js +12 -0
  152. package/{dist → src}/models/outbox.js +102 -3
  153. package/src/models/owner-views.js +59 -0
  154. package/src/models/payment.js +24 -0
  155. package/src/models/query-diff.js +7 -0
  156. package/src/models/release-manifest.js +160 -0
  157. package/src/models/repository-write.js +10 -0
  158. package/src/models/revisioned-commands.js +92 -0
  159. package/src/models/revisioned.js +41 -0
  160. package/src/models/settings.js +16 -0
  161. package/src/models/sync.js +44 -0
  162. package/src/models/wallet.js +40 -0
  163. package/src/queries/certificate-trust.js +31 -0
  164. package/{dist → src}/queries/conversations.js +54 -3
  165. package/src/queries/discovery.js +34 -0
  166. package/{dist → src}/queries/notifications.js +25 -2
  167. package/src/queries/onboarding-flow.js +48 -0
  168. package/src/queries/onboarding-lcd-screen.js +100 -0
  169. package/src/queries/onboarding-status.js +34 -0
  170. package/src/queries/revisioned.js +128 -0
  171. package/{dist → src}/queries/wallet.js +35 -1
  172. package/src/services/abort-signal.js +69 -0
  173. package/src/services/certificate-trust-service.js +119 -0
  174. package/{dist → src}/services/conversation-service.js +108 -1
  175. package/src/services/conversation-sync.js +28 -0
  176. package/src/services/device-boot-state-service.js +45 -0
  177. package/src/services/device-cleanup-service.js +112 -0
  178. package/src/services/discovery-probe-service.js +196 -0
  179. package/{dist → src}/services/gossip-service.js +54 -4
  180. package/src/services/invite-redemption-service.js +64 -0
  181. package/src/services/lan-candidate-refresh-service.js +164 -0
  182. package/src/services/lcd-decision-service.js +93 -0
  183. package/src/services/onboarding-checkpoint-store.js +155 -0
  184. package/src/services/onboarding-flow-service.js +1096 -0
  185. package/src/services/onboarding-status-service.js +64 -0
  186. package/{dist → src}/services/outbox-service.js +132 -1
  187. package/src/services/owner-claim-service.js +347 -0
  188. package/src/services/owner-view-refresh-service.js +208 -0
  189. package/src/services/owner-view-service.js +87 -0
  190. package/{dist → src}/services/peer-directory-service.js +72 -3
  191. package/src/services/release-manifest-verifier.js +108 -0
  192. package/{dist → src}/services/revisioned-domain-service.js +189 -9
  193. package/src/services/runtime-globals.js +174 -0
  194. package/src/services/session-service.js +50 -0
  195. package/{dist → src}/services/sync/authoritative-mirror.js +90 -1
  196. package/src/services/sync/index.js +67 -0
  197. package/src/services/sync/log-sync.js +60 -0
  198. package/src/services/sync/revision-merge.js +119 -0
  199. package/src/services/sync/strategy.js +44 -0
  200. package/src/services/transport-manager.js +478 -0
  201. package/{dist → src}/services/wallet-mirror-service.js +226 -6
  202. package/src/services/wifi-provisioning-service.js +278 -0
  203. package/dist/errors/index.js +0 -45
  204. package/dist/errors/index.js.map +0 -1
  205. package/dist/events/index.js +0 -17
  206. package/dist/events/index.js.map +0 -1
  207. package/dist/index.js +0 -31
  208. package/dist/index.js.map +0 -1
  209. package/dist/models/agent.js +0 -17
  210. package/dist/models/agent.js.map +0 -1
  211. package/dist/models/conversation.js +0 -2
  212. package/dist/models/conversation.js.map +0 -1
  213. package/dist/models/delegation.js.map +0 -1
  214. package/dist/models/device-identity.js.map +0 -1
  215. package/dist/models/graph.js.map +0 -1
  216. package/dist/models/index.js +0 -8
  217. package/dist/models/index.js.map +0 -1
  218. package/dist/models/json.js +0 -2
  219. package/dist/models/json.js.map +0 -1
  220. package/dist/models/l402.js +0 -2
  221. package/dist/models/l402.js.map +0 -1
  222. package/dist/models/live-query-definition.js +0 -2
  223. package/dist/models/live-query-definition.js.map +0 -1
  224. package/dist/models/outbox-intent.js +0 -2
  225. package/dist/models/outbox-intent.js.map +0 -1
  226. package/dist/models/outbox.js.map +0 -1
  227. package/dist/models/owner-views.js +0 -7
  228. package/dist/models/owner-views.js.map +0 -1
  229. package/dist/models/payment.js +0 -3
  230. package/dist/models/payment.js.map +0 -1
  231. package/dist/models/query-diff.js +0 -2
  232. package/dist/models/query-diff.js.map +0 -1
  233. package/dist/models/release-manifest.js +0 -147
  234. package/dist/models/release-manifest.js.map +0 -1
  235. package/dist/models/repository-write.js +0 -2
  236. package/dist/models/repository-write.js.map +0 -1
  237. package/dist/models/revisioned-commands.js +0 -35
  238. package/dist/models/revisioned-commands.js.map +0 -1
  239. package/dist/models/revisioned.js +0 -2
  240. package/dist/models/revisioned.js.map +0 -1
  241. package/dist/models/settings.js +0 -2
  242. package/dist/models/settings.js.map +0 -1
  243. package/dist/models/sync.js +0 -2
  244. package/dist/models/sync.js.map +0 -1
  245. package/dist/models/wallet.js +0 -2
  246. package/dist/models/wallet.js.map +0 -1
  247. package/dist/queries/conversations.js.map +0 -1
  248. package/dist/queries/discovery.js +0 -27
  249. package/dist/queries/discovery.js.map +0 -1
  250. package/dist/queries/notifications.js.map +0 -1
  251. package/dist/queries/onboarding-status.js +0 -27
  252. package/dist/queries/onboarding-status.js.map +0 -1
  253. package/dist/queries/revisioned.js +0 -58
  254. package/dist/queries/revisioned.js.map +0 -1
  255. package/dist/queries/wallet.js.map +0 -1
  256. package/dist/services/conversation-service.js.map +0 -1
  257. package/dist/services/conversation-sync.js +0 -9
  258. package/dist/services/conversation-sync.js.map +0 -1
  259. package/dist/services/discovery-probe-service.js +0 -129
  260. package/dist/services/discovery-probe-service.js.map +0 -1
  261. package/dist/services/gossip-service.js.map +0 -1
  262. package/dist/services/lan-candidate-refresh-service.js +0 -79
  263. package/dist/services/lan-candidate-refresh-service.js.map +0 -1
  264. package/dist/services/onboarding-status-service.js +0 -32
  265. package/dist/services/onboarding-status-service.js.map +0 -1
  266. package/dist/services/outbox-service.js.map +0 -1
  267. package/dist/services/owner-view-service.js +0 -21
  268. package/dist/services/owner-view-service.js.map +0 -1
  269. package/dist/services/peer-directory-service.js.map +0 -1
  270. package/dist/services/release-manifest-verifier.js +0 -84
  271. package/dist/services/release-manifest-verifier.js.map +0 -1
  272. package/dist/services/revisioned-domain-service.js.map +0 -1
  273. package/dist/services/session-service.js +0 -20
  274. package/dist/services/session-service.js.map +0 -1
  275. package/dist/services/sync/authoritative-mirror.js.map +0 -1
  276. package/dist/services/sync/index.js +0 -4
  277. package/dist/services/sync/index.js.map +0 -1
  278. package/dist/services/sync/log-sync.js +0 -20
  279. package/dist/services/sync/log-sync.js.map +0 -1
  280. package/dist/services/sync/revision-merge.js +0 -46
  281. package/dist/services/sync/revision-merge.js.map +0 -1
  282. package/dist/services/sync/strategy.js +0 -2
  283. package/dist/services/sync/strategy.js.map +0 -1
  284. package/dist/services/transport-manager.js +0 -234
  285. package/dist/services/transport-manager.js.map +0 -1
  286. package/dist/services/wallet-mirror-service.js.map +0 -1
@@ -1,19 +1,95 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {import("@econ-v1/ports").HomeNodeClient} HomeNodeClient
5
+ */
6
+ /**
7
+ * @typedef {import("@econ-v1/ports").OutboxRecord} OutboxRecord
8
+ */
9
+ /**
10
+ * @typedef {import("@econ-v1/ports").OutboxRepository} OutboxRepository
11
+ */
12
+ /**
13
+ * @template TPayload
14
+ * @typedef {import("../models/sync.js").AuthoritativeCommand<TPayload>} AuthoritativeCommand
15
+ */
16
+ /**
17
+ * @template TPayload
18
+ * @typedef {import("../models/sync.js").SyncDelta<TPayload>} SyncDelta
19
+ */
20
+ /**
21
+ * @typedef {import("../models/sync.js").SyncApplyResult} SyncApplyResult
22
+ */
23
+ /**
24
+ * @typedef {import("../models/wallet.js").CreateInvoiceInput} CreateInvoiceInput
25
+ */
26
+ /**
27
+ * @typedef {import("../models/wallet.js").PaymentHistoryView} PaymentHistoryView
28
+ */
29
+ /**
30
+ * @typedef {import("../models/wallet.js").PayInvoiceInput} PayInvoiceInput
31
+ */
32
+ /**
33
+ * @typedef {import("../models/wallet.js").WalletAuthoritativeSnapshot} WalletAuthoritativeSnapshot
34
+ */
35
+ /**
36
+ * @typedef {import("../models/wallet.js").WalletBalanceView} WalletBalanceView
37
+ */
38
+ /**
39
+ * @typedef {import("../models/wallet.js").WalletCommand} WalletCommand
40
+ */
41
+ /**
42
+ * @typedef {import("../models/wallet.js").WalletMirrorRepository} WalletMirrorRepository
43
+ */
44
+ /**
45
+ * @typedef {import("../models/wallet.js").WalletSnapshotPayload} WalletSnapshotPayload
46
+ */
47
+ /**
48
+ * @typedef {import("../models/payment.js").WalletCommandResult} WalletCommandResult
49
+ */
50
+ /**
51
+ * @typedef {import("../models/l402.js").L402HistoryView} L402HistoryView
52
+ */
53
+ /**
54
+ * @typedef {{ readonly id?: () => string; readonly now?: () => string; readonly staleAfterMs: number; }} WalletMirrorServiceOptions
55
+ */
1
56
  import { AuthoritativeMirror, AuthoritativeRevalidationRequiredError, } from "./sync/index.js";
57
+ /**
58
+ * @extends {Error}
59
+ */
2
60
  export class OfflineWalletCommandError extends Error {
3
61
  constructor() {
4
62
  super("Wallet commands require an online home node");
5
63
  this.name = "OfflineWalletCommandError";
6
64
  }
7
65
  }
66
+ /**
67
+ * @extends {Error}
68
+ */
8
69
  export class WalletFreshStateRequiredError extends Error {
9
70
  constructor() {
10
71
  super("Wallet state could not be freshly revalidated; confirm again when connected");
11
72
  this.name = "WalletFreshStateRequiredError";
12
73
  }
13
74
  }
75
+ /**
76
+ * @extends {Error}
77
+ */
14
78
  export class InsufficientWalletBalanceError extends Error {
79
+ /**
80
+ * @readonly
81
+ * @type {number}
82
+ */
15
83
  amountMsat;
84
+ /**
85
+ * @readonly
86
+ * @type {number}
87
+ */
16
88
  spendableMsat;
89
+ /**
90
+ * @param {number} amountMsat
91
+ * @param {number} spendableMsat
92
+ */
17
93
  constructor(amountMsat, spendableMsat) {
18
94
  super("The authoritative wallet balance is insufficient for this payment");
19
95
  this.name = "InsufficientWalletBalanceError";
@@ -21,12 +97,18 @@ export class InsufficientWalletBalanceError extends Error {
21
97
  this.spendableMsat = spendableMsat;
22
98
  }
23
99
  }
100
+ /**
101
+ * @extends {Error}
102
+ */
24
103
  export class ExpiredInvoiceError extends Error {
25
104
  constructor() {
26
105
  super("The invoice has expired");
27
106
  this.name = "ExpiredInvoiceError";
28
107
  }
29
108
  }
109
+ /**
110
+ * @extends {Error}
111
+ */
30
112
  export class WalletCommandRejectedError extends Error {
31
113
  constructor() {
32
114
  super("The authoritative wallet command was rejected");
@@ -34,12 +116,40 @@ export class WalletCommandRejectedError extends Error {
34
116
  }
35
117
  }
36
118
  export class WalletMirrorService {
119
+ /**
120
+ * @readonly
121
+ * @type {() => string}
122
+ */
37
123
  #id;
124
+ /**
125
+ * @readonly
126
+ * @type {WalletMirrorRepository}
127
+ */
38
128
  #mirror;
129
+ /**
130
+ * @readonly
131
+ * @type {() => string}
132
+ */
39
133
  #now;
134
+ /**
135
+ * @readonly
136
+ * @type {OutboxRepository}
137
+ */
40
138
  #outbox;
139
+ /**
140
+ * @readonly
141
+ * @type {AuthoritativeMirror<WalletSnapshotPayload, WalletCommand, WalletCommandResult>}
142
+ */
41
143
  #strategy;
144
+ /**
145
+ * @readonly
146
+ * @type {HomeNodeClient}
147
+ */
42
148
  #homeNode;
149
+ /**
150
+ * @param {{ readonly homeNode: HomeNodeClient; readonly mirror: WalletMirrorRepository; readonly outbox: OutboxRepository; }} dependencies
151
+ * @param {WalletMirrorServiceOptions} options
152
+ */
43
153
  constructor(dependencies, options) {
44
154
  if (!Number.isInteger(options.staleAfterMs) || options.staleAfterMs <= 0) {
45
155
  throw new RangeError("Wallet mirror staleAfterMs must be a positive integer");
@@ -55,9 +165,16 @@ export class WalletMirrorService {
55
165
  staleAfterMs: options.staleAfterMs,
56
166
  });
57
167
  }
168
+ /**
169
+ * @param {SyncDelta<WalletSnapshotPayload>} delta
170
+ * @returns {Promise<SyncApplyResult>}
171
+ */
58
172
  async applyDelta(delta) {
59
173
  return await this.#strategy.applyDelta(delta, { domain: "wallet", now: this.#now() });
60
174
  }
175
+ /**
176
+ * @returns {Promise<WalletBalanceView>}
177
+ */
61
178
  async readBalance() {
62
179
  const snapshot = await this.#mirror.readSnapshot();
63
180
  if (snapshot === undefined)
@@ -70,6 +187,9 @@ export class WalletMirrorService {
70
187
  staleAt: snapshot.staleAt,
71
188
  };
72
189
  }
190
+ /**
191
+ * @returns {Promise<PaymentHistoryView>}
192
+ */
73
193
  async listPayments() {
74
194
  const snapshot = await this.#mirror.readSnapshot();
75
195
  if (snapshot === undefined)
@@ -81,6 +201,9 @@ export class WalletMirrorService {
81
201
  staleAt: snapshot.staleAt,
82
202
  };
83
203
  }
204
+ /**
205
+ * @returns {Promise<L402HistoryView>}
206
+ */
84
207
  async listL402Receipts() {
85
208
  const snapshot = await this.#mirror.readSnapshot();
86
209
  if (snapshot === undefined)
@@ -92,25 +215,33 @@ export class WalletMirrorService {
92
215
  staleAt: snapshot.staleAt,
93
216
  };
94
217
  }
218
+ /**
219
+ * @param {CreateInvoiceInput} input
220
+ * @returns {Promise<Extract<WalletCommandResult, { readonly kind: "invoice-created" }>>}
221
+ */
95
222
  async createInvoice(input) {
96
223
  requirePositiveMsat(input.amountMsat, "invoice amount");
97
224
  requireText(input.idempotencyKey, "invoice idempotency key");
98
- return await this.#runCommand({
225
+ return /** @type {Extract<WalletCommandResult, { readonly kind: "invoice-created" }>} */ (await this.#runCommand({
99
226
  amountMsat: input.amountMsat,
100
227
  ...(input.description === undefined ? {} : { description: input.description }),
101
228
  kind: "create-invoice",
102
229
  }, input.idempotencyKey, (snapshot) => {
103
230
  if (snapshot.value.balanceMsat < 0)
104
231
  throw new WalletFreshStateRequiredError();
105
- });
232
+ }));
106
233
  }
234
+ /**
235
+ * @param {PayInvoiceInput} input
236
+ * @returns {Promise<Extract<WalletCommandResult, { readonly kind: "payment-submitted" }>>}
237
+ */
107
238
  async payInvoice(input) {
108
239
  requirePositiveMsat(input.amountMsat, "payment amount");
109
240
  requireText(input.idempotencyKey, "payment idempotency key");
110
241
  requireText(input.invoiceId, "invoice id");
111
242
  if (input.expiresAt !== undefined && isExpired(input.expiresAt, this.#now()))
112
243
  throw new ExpiredInvoiceError();
113
- return await this.#runCommand({
244
+ return /** @type {Extract<WalletCommandResult, { readonly kind: "payment-submitted" }>} */ (await this.#runCommand({
114
245
  amountMsat: input.amountMsat,
115
246
  ...(input.expiresAt === undefined ? {} : { expiresAt: input.expiresAt }),
116
247
  invoiceId: input.invoiceId,
@@ -119,8 +250,14 @@ export class WalletMirrorService {
119
250
  if (input.amountMsat > snapshot.value.spendableMsat) {
120
251
  throw new InsufficientWalletBalanceError(input.amountMsat, snapshot.value.spendableMsat);
121
252
  }
122
- });
253
+ }));
123
254
  }
255
+ /**
256
+ * @param {WalletCommand} command
257
+ * @param {string} idempotencyKey
258
+ * @param {(snapshot: WalletAuthoritativeSnapshot) => void} validate
259
+ * @returns {Promise<WalletCommandResult>}
260
+ */
124
261
  async #runCommand(command, idempotencyKey, validate) {
125
262
  const cached = await this.#mirror.readCommandResult(idempotencyKey);
126
263
  if (cached !== undefined)
@@ -161,6 +298,9 @@ export class WalletMirrorService {
161
298
  throw error instanceof WalletFreshStateRequiredError ? error : new WalletCommandRejectedError();
162
299
  }
163
300
  }
301
+ /**
302
+ * @returns {Promise<WalletAuthoritativeSnapshot>}
303
+ */
164
304
  async #ensureFresh() {
165
305
  const current = await this.#mirror.readSnapshot();
166
306
  if (current !== undefined && this.#isFresh(current))
@@ -181,6 +321,10 @@ export class WalletMirrorService {
181
321
  throw new WalletFreshStateRequiredError();
182
322
  return refreshed;
183
323
  }
324
+ /**
325
+ * @param {AuthoritativeCommand<WalletCommand>} command
326
+ * @returns {Promise<WalletCommandResult>}
327
+ */
184
328
  async #execute(command) {
185
329
  this.#assertOnline();
186
330
  try {
@@ -198,6 +342,11 @@ export class WalletMirrorService {
198
342
  throw new WalletCommandRejectedError();
199
343
  }
200
344
  }
345
+ /**
346
+ * @param {WalletCommand} command
347
+ * @param {string} idempotencyKey
348
+ * @returns {OutboxRecord}
349
+ */
201
350
  #outboxRecord(command, idempotencyKey) {
202
351
  const createdAt = this.#now();
203
352
  return {
@@ -210,13 +359,27 @@ export class WalletMirrorService {
210
359
  updatedAt: createdAt,
211
360
  };
212
361
  }
362
+ /**
363
+ * @returns {void}
364
+ */
213
365
  #assertOnline() {
214
366
  if (!this.#isOnline())
215
367
  throw new OfflineWalletCommandError();
216
368
  }
369
+ /**
370
+ * @returns {boolean}
371
+ */
217
372
  #isOnline() { return this.#homeNode.state.status === "connected"; }
373
+ /**
374
+ * @param {WalletAuthoritativeSnapshot} snapshot
375
+ * @returns {boolean}
376
+ */
218
377
  #isFresh(snapshot) { return Date.parse(this.#now()) < Date.parse(snapshot.staleAt); }
219
378
  }
379
+ /**
380
+ * @param {unknown} value
381
+ * @returns {WalletSnapshotPayload}
382
+ */
220
383
  function parseSnapshotPayload(value) {
221
384
  const record = asRecord(value, "wallet snapshot");
222
385
  return {
@@ -226,6 +389,11 @@ function parseSnapshotPayload(value) {
226
389
  spendableMsat: positiveOrZero(record.spendableMsat, "wallet spendable balance"),
227
390
  };
228
391
  }
392
+ /**
393
+ * @param {unknown} value
394
+ * @param {number} amountMsat
395
+ * @returns {Extract<WalletCommandResult, { readonly kind: "invoice-created" }>}
396
+ */
229
397
  function parseInvoiceResult(value, amountMsat) {
230
398
  const record = asRecord(value, "invoice result");
231
399
  const invoiceId = text(record.invoiceId, "invoice id");
@@ -239,6 +407,11 @@ function parseInvoiceResult(value, amountMsat) {
239
407
  status: "created",
240
408
  };
241
409
  }
410
+ /**
411
+ * @param {unknown} value
412
+ * @param {number} amountMsat
413
+ * @returns {Extract<WalletCommandResult, { readonly kind: "payment-submitted" }>}
414
+ */
242
415
  function parsePaymentResult(value, amountMsat) {
243
416
  const record = asRecord(value, "payment result");
244
417
  const status = record.status ?? "pending";
@@ -252,6 +425,10 @@ function parsePaymentResult(value, amountMsat) {
252
425
  status,
253
426
  };
254
427
  }
428
+ /**
429
+ * @param {unknown} value
430
+ * @returns {WalletSnapshotPayload["payments"]}
431
+ */
255
432
  function parsePayments(value) {
256
433
  if (value === undefined)
257
434
  return [];
@@ -277,6 +454,10 @@ function parsePayments(value) {
277
454
  };
278
455
  });
279
456
  }
457
+ /**
458
+ * @param {unknown} value
459
+ * @returns {WalletSnapshotPayload["l402"]}
460
+ */
280
461
  function parseL402(value) {
281
462
  if (value === undefined)
282
463
  return [];
@@ -293,42 +474,81 @@ function parseL402(value) {
293
474
  };
294
475
  });
295
476
  }
477
+ /**
478
+ * @param {unknown} value
479
+ * @param {string} _label
480
+ * @returns {Record<string, unknown>}
481
+ */
296
482
  function asRecord(value, _label) {
297
483
  if (value === null || typeof value !== "object" || Array.isArray(value))
298
484
  throw new WalletFreshStateRequiredError();
299
- return value;
485
+ return /** @type {Record<string, unknown>} */ (value);
300
486
  }
487
+ /**
488
+ * @param {unknown} value
489
+ * @param {string} _label
490
+ * @returns {string}
491
+ */
301
492
  function text(value, _label) {
302
493
  if (typeof value !== "string" || value.trim() === "")
303
494
  throw new WalletCommandRejectedError();
304
495
  return value;
305
496
  }
497
+ /**
498
+ * @param {unknown} value
499
+ * @param {string} label
500
+ * @returns {string}
501
+ */
306
502
  function timestamp(value, label) {
307
503
  const parsed = text(value, label);
308
504
  if (!Number.isFinite(Date.parse(parsed)))
309
505
  throw new WalletFreshStateRequiredError();
310
506
  return parsed;
311
507
  }
508
+ /**
509
+ * @param {unknown} value
510
+ * @param {string} _label
511
+ * @returns {number}
512
+ */
312
513
  function positiveOrZero(value, _label) {
313
514
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
314
515
  throw new WalletFreshStateRequiredError();
315
516
  return value;
316
517
  }
518
+ /**
519
+ * @param {unknown} value
520
+ * @param {number} fallback
521
+ * @returns {number}
522
+ */
317
523
  function numberOrDefault(value, fallback) {
318
524
  return value === undefined ? fallback : positiveOrZero(value, "amount");
319
525
  }
526
+ /**
527
+ * @param {number} value
528
+ * @param {string} label
529
+ * @returns {void}
530
+ */
320
531
  function requirePositiveMsat(value, label) {
321
532
  if (!Number.isSafeInteger(value) || value <= 0)
322
533
  throw new TypeError(`${label} must be a positive integer`);
323
534
  }
535
+ /**
536
+ * @param {string} value
537
+ * @param {string} label
538
+ * @returns {void}
539
+ */
324
540
  function requireText(value, label) {
325
541
  if (value.trim() === "")
326
542
  throw new TypeError(`${label} must be a non-empty string`);
327
543
  }
544
+ /**
545
+ * @param {string} expiresAt
546
+ * @param {string} now
547
+ * @returns {boolean}
548
+ */
328
549
  function isExpired(expiresAt, now) {
329
550
  const expiry = Date.parse(expiresAt);
330
551
  if (!Number.isFinite(expiry))
331
552
  throw new TypeError("invoice expiry must be an ISO timestamp");
332
553
  return expiry <= Date.parse(now);
333
554
  }
334
- //# sourceMappingURL=wallet-mirror-service.js.map
@@ -0,0 +1,278 @@
1
+ // @ts-check
2
+ /** @typedef {import('@econ-v1/ports').DelayPort} DelayPort */
3
+ /** @typedef {import('@econ-v1/ports').HomeNodeClient} HomeNodeClient */
4
+ import { DomainError } from "../errors/index.js";
5
+ const HANDOFF_POLL_INTERVAL_MS = 3000;
6
+ const HANDOFF_TRANSPORT_LOST_POLLS = 2;
7
+ const RECONNECT_POLL_INTERVAL_MS = 4000;
8
+ export class WifiProvisioningService {
9
+ /**
10
+ * @param {() => boolean} [shouldContinue]
11
+ * @returns {Promise<WifiReconnectOutcome>}
12
+ */
13
+ async awaitReconnect(shouldContinue = () => true) {
14
+ for (;;) {
15
+ if (!shouldContinue()) return "superseded";
16
+ const outcome = await this.status();
17
+ if (outcome.kind === "connected") return "connected";
18
+ await this.#delay.wait(RECONNECT_POLL_INTERVAL_MS);
19
+ }
20
+ }
21
+ /**
22
+ * @readonly
23
+ */
24
+ #delay;
25
+ /**
26
+ * @readonly
27
+ */
28
+ #node;
29
+ /**
30
+ * @readonly
31
+ */
32
+ #now;
33
+ /**
34
+ * @param {WifiProvisioningServiceOptions} options
35
+ */
36
+ constructor(options) {
37
+ this.#delay = options.delay;
38
+ this.#node = options.node;
39
+ this.#now = options.now;
40
+ }
41
+ /**
42
+ * Whether the node is ALREADY joined to a home network. Asked once at flow entry so a person
43
+ * who configured Wi-Fi on the node's own captive portal is never marched through the
44
+ * credential steps a second time — resubmitting working credentials is not idempotent on the
45
+ * node side (the join tears down and re-adds the connection) and a handshake flake in that
46
+ * window strands the node back in setup AP mode. The wire values are node-server's
47
+ * `http_status` PascalCase strings; only an explicit `"Connected"` counts.
48
+ * @returns {Promise<WifiStatusOutcome>}
49
+ */
50
+ async status() {
51
+ try {
52
+ const wire = await this.#node.invoke({
53
+ operation: "GET /api/v2/wifi/status",
54
+ payload: {},
55
+ });
56
+ if (wire.connection_status !== "Connected")
57
+ return { kind: "not-connected" };
58
+ return {
59
+ kind: "connected",
60
+ ssid: typeof wire.current_ssid === "string" ? wire.current_ssid : "",
61
+ };
62
+ } catch {
63
+ return { kind: "unreachable" };
64
+ }
65
+ }
66
+ /** Lists what the node's radio can currently see. A failure is a first-class outcome, not a
67
+ * throw: step 4 stays on screen and offers "Scan again".
68
+ * @returns {Promise<WifiScanOutcome>}
69
+ */
70
+ async scan() {
71
+ try {
72
+ const wire = await this.#node.invoke({
73
+ operation: "GET /api/v2/wifi/scan",
74
+ payload: {},
75
+ });
76
+ const rows = Array.isArray(wire.networks) ? wire.networks : [];
77
+ /** @type {WifiProvisioningNetwork[]} */
78
+ const networks = [];
79
+ for (const row of rows) {
80
+ const ssid = typeof row.ssid === "string" ? row.ssid : "";
81
+ if (ssid.length === 0) continue;
82
+ networks.push({
83
+ security: normaliseSecurity(row.security),
84
+ signal: clampSignal(row.signal_quality_percent),
85
+ ssid,
86
+ });
87
+ }
88
+ return { kind: "scanned", networks };
89
+ } catch {
90
+ return { kind: "scan-failed" };
91
+ }
92
+ }
93
+ /**
94
+ * Hands the credentials to the node exactly once. `secret === undefined` means an open
95
+ * network, in which case the `password` key is omitted rather than sent as an empty string —
96
+ * the node validates length 8-63 whenever the field is present.
97
+ * @param {string} ssid
98
+ * @param {string | undefined} secret
99
+ * @returns {Promise<WifiConfigureOutcome>}
100
+ */
101
+ async configure(ssid, secret) {
102
+ /** @type {{ auto_connect: boolean, hidden: boolean, password?: string, priority: number, ssid: string }} */
103
+ /** @type {Record<string, unknown>} */
104
+ const payload = {
105
+ auto_connect: true,
106
+ hidden: false,
107
+ priority: 100,
108
+ ssid,
109
+ };
110
+ if (secret !== undefined) payload.password = secret;
111
+ try {
112
+ const wire = await this.#node.invoke({
113
+ operation: "POST /api/v2/wifi/configure",
114
+ payload,
115
+ });
116
+ const operationId =
117
+ typeof wire.operation_id === "string" ? wire.operation_id : "";
118
+ // No handle means nothing to watch. Reporting "accepted" here would strand step 6 forever.
119
+ if (operationId.length === 0) return { kind: "unreachable" };
120
+ return { kind: "accepted", operationId };
121
+ } catch (error) {
122
+ // The node validates ssid/password shape before it ever touches the radio, and
123
+ // `PrePairingHomeNodeClient` maps that envelope onto `invalid_request`. Everything else —
124
+ // transport, 5xx, unknown envelope — is "we could not tell", never a credential verdict.
125
+ if (error instanceof DomainError && error.code === "invalid_request") {
126
+ return { kind: "credentials-rejected" };
127
+ }
128
+ return { kind: "unreachable" };
129
+ }
130
+ }
131
+ /**
132
+ * Watches one configure operation across the network handoff. See the class comment: a
133
+ * thrown poll is EXPECTED and means "keep waiting".
134
+ *
135
+ * `deadline` is an absolute epoch-millisecond value from the same clock as `now`, supplied by
136
+ * the caller so a resumed flow can keep the ORIGINAL deadline rather than restarting it.
137
+ * @param {string} operationId
138
+ * @param {number} deadline
139
+ * @param {() => void} [onTransportLost]
140
+ * @returns {Promise<WifiHandoffOutcome>}
141
+ */
142
+ async awaitHandoff(operationId, deadline, onTransportLost) {
143
+ const operation = `GET /api/v2/wifi/operations/${encodeURIComponent(operationId)}`;
144
+ let consecutiveFailures = 0;
145
+ let reportedTransportLost = false;
146
+ for (;;) {
147
+ try {
148
+ const wire = await this.#node.invoke({ operation, payload: {} });
149
+ consecutiveFailures = 0;
150
+ const status = typeof wire.status === "string" ? wire.status : "";
151
+ if (status === "Connected") return "connected";
152
+ if (status === "Failed") return "credentials-rejected";
153
+ } catch {
154
+ consecutiveFailures += 1;
155
+ if (consecutiveFailures >= HANDOFF_TRANSPORT_LOST_POLLS && !reportedTransportLost) {
156
+ reportedTransportLost = true;
157
+ try {
158
+ onTransportLost?.();
159
+ } catch {
160
+ // The caller's problem, never this loop's.
161
+ }
162
+ }
163
+ }
164
+ if (this.#now() >= deadline) return "unreachable";
165
+ await this.#delay.wait(HANDOFF_POLL_INTERVAL_MS);
166
+ }
167
+ }
168
+ }
169
+ /** The node reports whatever its supplicant said. Anything not recognisably open/WEP/WPA3 is
170
+ * treated as WPA2 — the conservative choice, because it demands a password rather than
171
+ * silently offering to join without one.
172
+ * @param {unknown} value
173
+ * @returns {WifiProvisioningSecurity}
174
+ */
175
+ function normaliseSecurity(value) {
176
+ if (typeof value !== "string") return "wpa2";
177
+ const lowered = value.toLowerCase();
178
+ if (lowered.includes("wpa3")) return "wpa3";
179
+ if (lowered.includes("wep")) return "wep";
180
+ if (lowered === "open" || lowered === "none" || lowered === "") return "open";
181
+ return "wpa2";
182
+ }
183
+ /**
184
+ * @param {unknown} value
185
+ * @returns {number}
186
+ */
187
+ function clampSignal(value) {
188
+ if (typeof value !== "number" || Number.isNaN(value)) return 0;
189
+ if (value < 0) return 0;
190
+ if (value > 100) return 100;
191
+ return Math.round(value);
192
+ }
193
+ /**
194
+ * The node's Wi-Fi provisioning surface, expressed as three operations with CLOSED outcome
195
+ * unions (design §10). Nothing here throws at its caller and nothing here returns node text:
196
+ * `OnboardingFlowService` turns these outcomes into `OnboardingReasonCode`s, and the UI turns
197
+ * those into copy. A raw `DomainError.message` reaching a subscriber would be both a leak and
198
+ * an unreviewable string in the product.
199
+ *
200
+ * THE CENTRAL DIFFICULTY is `awaitHandoff()`. Between `configure()` and the node reporting
201
+ * `Connected`, the node LEAVES its own access point and joins the home network — the browser
202
+ * that issued the request is on that access point, so the poll *is expected to fail*, possibly
203
+ * for the entire operation. A thrown request here therefore means "keep waiting", not "it
204
+ * broke"; only three things end the wait:
205
+ *
206
+ * - the node answering `Connected` -> "connected" (it came back, on the LAN)
207
+ * - the node answering `Failed` -> "credentials-rejected" (it came back to say no)
208
+ * - the deadline passing -> "unreachable" (step 7's manual way back)
209
+ *
210
+ * `Failed` is the ONLY signal that distinguishes a wrong password from a normal handoff, which
211
+ * is why it must not be collapsed into the generic failure branch: collapsing it would send a
212
+ * person who mistyped their password to "walk back to your node" instead of to the keyboard.
213
+ *
214
+ * The status strings are the node's, verified against
215
+ * `core/adapters-network/src/http/server/wifi.rs::http_operation_status`, which maps its
216
+ * internal states onto exactly `"Connected"` / `"Failed"` / `"Connecting"` / `"Pending"`.
217
+ * (The design doc's "Completed" is wrong; the wire value is "Connected".)
218
+ *
219
+ * SECURITY: `configure()` takes the secret as an argument and forwards it in a single request.
220
+ * It is never assigned to a field, never returned, never logged, and never included in an
221
+ * outcome — `WifiConfigureOutcome` has no field it could occupy.
222
+ * @typedef {"open" | "wep" | "wpa2" | "wpa3"} WifiProvisioningSecurity
223
+ */
224
+ /**
225
+ * @typedef {| { readonly kind: "scanned"; readonly networks: readonly WifiProvisioningNetwork[] }
226
+ * | { readonly kind: "scan-failed" }} WifiScanOutcome
227
+ */
228
+ /**
229
+ * @typedef {| { readonly kind: "accepted"; readonly operationId: string }
230
+ * | { readonly kind: "credentials-rejected" }
231
+ * | { readonly kind: "unreachable" }} WifiConfigureOutcome
232
+ */
233
+ /** @typedef {"connected" | "credentials-rejected" | "unreachable"} WifiHandoffOutcome */
234
+ /** @typedef {"connected" | "superseded"} WifiReconnectOutcome */
235
+ /** What the node's radio is doing RIGHT NOW, asked once at flow entry. `connected` carries the
236
+ * joined SSID so the flow can show which network needs no credentials. `unreachable` collapses
237
+ * every failure — a flow that cannot ask simply walks the ordinary Wi-Fi steps.
238
+ * @typedef {| { readonly kind: "connected"; readonly ssid: string }
239
+ * | { readonly kind: "not-connected" }
240
+ * | { readonly kind: "unreachable" }} WifiStatusOutcome
241
+ */
242
+ /**
243
+ * @typedef {object} WifiProvisioningNetwork
244
+ * @property {WifiProvisioningSecurity} security
245
+ * @property {number} signal 0-100 signal quality. dBm never crosses this boundary.
246
+ * @property {string} ssid
247
+ */
248
+ /**
249
+ * @typedef {object} WifiProvisioningServiceOptions
250
+ * @property {Pick<DelayPort, "wait">} delay
251
+ * @property {Pick<HomeNodeClient, "invoke">} node
252
+ * @property {() => number} now Epoch milliseconds. Injected so the deadline is testable and domain owns no clock.
253
+ */
254
+ /**
255
+ * @typedef {object} WifiScanRowWire
256
+ * @property {unknown} [security]
257
+ * @property {unknown} [signal_quality_percent]
258
+ * @property {unknown} [ssid]
259
+ */
260
+ /**
261
+ * @typedef {object} WifiScanWire
262
+ * @property {readonly WifiScanRowWire[]} [networks]
263
+ */
264
+ /**
265
+ * @typedef {object} WifiConfigureWire
266
+ * @property {unknown} [operation_id]
267
+ * @property {unknown} [success]
268
+ */
269
+ /**
270
+ * @typedef {object} WifiOperationStatusWire
271
+ * @property {unknown} [status]
272
+ */
273
+ /**
274
+ * @typedef {object} WifiStatusWire
275
+ * @property {unknown} [connection_status]
276
+ * @property {unknown} [current_ssid]
277
+ */
278
+ // @ts-check