@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,18 +1,108 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {import("@econ-v1/ports").OutboxRecord} OutboxRecord
5
+ */
6
+ /**
7
+ * @typedef {import("@econ-v1/ports").OutboxRepository} OutboxRepository
8
+ */
9
+ /**
10
+ * @typedef {import("@econ-v1/ports").RepositoryJsonValue} RepositoryJsonValue
11
+ */
12
+ /**
13
+ * @template {JsonValue} TValue
14
+ * @typedef {import("../models/revisioned.js").RevisionCommand<TValue>} RevisionCommand
15
+ */
16
+ /**
17
+ * @template TValue
18
+ * @typedef {import("../models/revisioned.js").RevisionCommandResult<TValue>} RevisionCommandResult
19
+ */
20
+ /**
21
+ * @template TValue
22
+ * @typedef {import("../models/revisioned.js").RevisionConflict<TValue>} RevisionConflict
23
+ */
24
+ /**
25
+ * @template {JsonValue} TValue
26
+ * @typedef {import("../models/revisioned.js").RevisionConflictRecord<TValue>} RevisionConflictRecord
27
+ */
28
+ /**
29
+ * @template {JsonValue} TValue
30
+ * @typedef {import("../models/revisioned.js").RevisionConflictPayload<TValue>} RevisionConflictPayload
31
+ */
32
+ /**
33
+ * @template TValue
34
+ * @typedef {import("../models/revisioned.js").RevisionEntity<TValue>} RevisionEntity
35
+ */
36
+ /**
37
+ * @typedef {import("../models/revisioned.js").RevisionResolution} RevisionResolution
38
+ */
39
+ /**
40
+ * @typedef {import("../models/revisioned.js").RevisionedDomain} RevisionedDomain
41
+ */
42
+ /**
43
+ * @typedef {import("../models/json.js").JsonValue} JsonValue
44
+ */
45
+ /**
46
+ * @template {JsonValue} TValue
47
+ * @typedef {{ apply(entity: RevisionEntity<TValue>): Promise<void>; command(input: RevisionCommand<TValue>): Promise<RevisionCommandResult<TValue>>; read(id: string): Promise<RevisionEntity<TValue> | undefined>; }} RevisionedDomainRepository
48
+ */
49
+ /**
50
+ * @template {JsonValue} TValue
51
+ * @typedef {{ readonly domain: RevisionedDomain; readonly now?: () => string; readonly outbox: OutboxRepository; readonly repository: RevisionedDomainRepository<TValue>; }} RevisionedDomainServiceOptions
52
+ */
53
+ /**
54
+ * @template {JsonValue} TValue
55
+ * @typedef {| { readonly entity: RevisionEntity<TValue>; readonly status: "accepted" } | { readonly conflict: RevisionConflictRecord<TValue>; readonly status: "conflict" }} RevisionedWriteResult
56
+ */
57
+ /**
58
+ * @template {JsonValue} TValue
59
+ * @typedef {| { readonly entity: RevisionEntity<TValue>; readonly status: "accepted" } | { readonly entity: RevisionEntity<TValue>; readonly status: "discarded" } | { readonly conflict: RevisionConflictRecord<TValue>; readonly status: "conflict" }} RevisionResolutionResult
60
+ */
61
+ /**
62
+ * @template {JsonValue} TValue
63
+ * @typedef {{ readonly baseRev?: number; readonly id: string; readonly idempotencyKey: string; readonly value: TValue; }} RevisionedWriteInput
64
+ */
1
65
  import { DomainError } from "../errors/index.js";
2
66
  import { createOutboxIntent } from "../models/outbox.js";
3
67
  import { REVISIONED_DOMAINS, } from "../models/revisioned.js";
4
68
  import { RevisionMerge } from "./sync/revision-merge.js";
69
+ /**
70
+ * @template {JsonValue} TValue
71
+ */
5
72
  export class RevisionedDomainService {
73
+ /**
74
+ * @readonly
75
+ * @type {RevisionedDomain}
76
+ */
6
77
  #domain;
78
+ /**
79
+ * @readonly
80
+ * @type {() => string}
81
+ */
7
82
  #now;
83
+ /**
84
+ * @readonly
85
+ * @type {OutboxRepository}
86
+ */
8
87
  #outbox;
88
+ /**
89
+ * @readonly
90
+ * @type {RevisionedDomainRepository<TValue>}
91
+ */
9
92
  #repository;
93
+ /**
94
+ * @param {RevisionedDomainServiceOptions<TValue>} options
95
+ */
10
96
  constructor(options) {
11
97
  this.#domain = options.domain;
12
98
  this.#now = options.now ?? (() => new Date().toISOString());
13
99
  this.#outbox = options.outbox;
14
100
  this.#repository = options.repository;
15
101
  }
102
+ /**
103
+ * @param {RevisionedWriteInput<TValue>} input
104
+ * @returns {Promise<RevisionedWriteResult<TValue>>}
105
+ */
16
106
  async update(input) {
17
107
  validateWriteInput(this.#domain, input);
18
108
  const merge = this.#merge(input.idempotencyKey);
@@ -28,17 +118,30 @@ export class RevisionedDomainService {
28
118
  throw new Error("Revision conflict was not persisted");
29
119
  return { conflict, status: "conflict" };
30
120
  }
121
+ /**
122
+ * @returns {Promise<readonly RevisionConflictRecord<TValue>[]>}
123
+ */
31
124
  async listConflicts() {
32
125
  const records = await this.#outbox.list({ domain: this.#outboxDomain(), state: "conflict" });
33
- return records.map((record) => parseRevisionConflictRecord(record.payload, this.#domain));
126
+ return records.map((record) =>
127
+ /** @type {RevisionConflictRecord<TValue>} */ (
128
+ parseRevisionConflictRecord(record.payload, this.#domain)
129
+ ));
34
130
  }
131
+ /**
132
+ * @param {string} conflictIdValue
133
+ * @param {RevisionResolution} resolution
134
+ * @returns {Promise<RevisionResolutionResult<TValue>>}
135
+ */
35
136
  async resolveConflict(conflictIdValue, resolution) {
36
137
  if (conflictIdValue.trim() === "")
37
138
  throw invalid("conflict id");
38
139
  const stored = await this.#outbox.read(conflictIdValue);
39
140
  if (stored === undefined)
40
141
  throw new DomainError({ code: "not_found", message: "Revision conflict was not found" });
41
- const conflict = parseRevisionConflictRecord(stored.payload, this.#domain);
142
+ const conflict = /** @type {RevisionConflictRecord<TValue>} */ (
143
+ parseRevisionConflictRecord(stored.payload, this.#domain)
144
+ );
42
145
  if (resolution === "theirs") {
43
146
  await this.#repository.apply(conflict.theirs);
44
147
  await this.#outbox.delete(stored.id);
@@ -66,6 +169,10 @@ export class RevisionedDomainService {
66
169
  await this.#outbox.delete(stored.id);
67
170
  return { conflict: replacement, status: "conflict" };
68
171
  }
172
+ /**
173
+ * @param {string} idempotencyKey
174
+ * @returns {RevisionMerge<TValue>}
175
+ */
69
176
  #merge(idempotencyKey) {
70
177
  return new RevisionMerge({
71
178
  parkConflict: async (conflict) => {
@@ -75,11 +182,18 @@ export class RevisionedDomainService {
75
182
  write: async (entity) => await this.#repository.apply(entity),
76
183
  });
77
184
  }
185
+ /**
186
+ * @param {RevisionConflict<TValue> & { readonly idempotencyKey: string }} conflict
187
+ * @returns {Promise<RevisionConflictRecord<TValue>>}
188
+ */
78
189
  async #persistConflict(conflict) {
79
190
  const id = conflictId(this.#domain, conflict.id, conflict.idempotencyKey);
80
191
  const existing = await this.#outbox.read(id);
81
192
  if (existing !== undefined)
82
- return parseRevisionConflictRecord(existing.payload, this.#domain);
193
+ return /** @type {RevisionConflictRecord<TValue>} */ (
194
+ parseRevisionConflictRecord(existing.payload, this.#domain)
195
+ );
196
+ /** @type {RevisionConflictPayload<TValue>} */
83
197
  const record = {
84
198
  ...conflict,
85
199
  conflictId: id,
@@ -87,6 +201,7 @@ export class RevisionedDomainService {
87
201
  domain: this.#domain,
88
202
  kind: "revision-conflict.v1",
89
203
  };
204
+ /** @type {OutboxRecord} */
90
205
  const entry = {
91
206
  ...createOutboxIntent({
92
207
  authority: "eventual",
@@ -94,7 +209,7 @@ export class RevisionedDomainService {
94
209
  domain: this.#outboxDomain(),
95
210
  id,
96
211
  idempotencyKey: `conflict:${id}`,
97
- payload: record,
212
+ payload: /** @type {RepositoryJsonValue} */ (record),
98
213
  state: "conflict",
99
214
  updatedAt: record.createdAt,
100
215
  }),
@@ -103,14 +218,31 @@ export class RevisionedDomainService {
103
218
  await this.#outbox.create(entry);
104
219
  return record;
105
220
  }
221
+ /**
222
+ * @param {string} id
223
+ * @returns {Promise<RevisionConflictRecord<TValue> | undefined>}
224
+ */
106
225
  async #readConflict(id) {
107
226
  const record = await this.#outbox.read(id);
108
- return record === undefined ? undefined : parseRevisionConflictRecord(record.payload, this.#domain);
227
+ return record === undefined
228
+ ? undefined
229
+ : /** @type {RevisionConflictRecord<TValue>} */ (
230
+ parseRevisionConflictRecord(record.payload, this.#domain)
231
+ );
109
232
  }
233
+ /**
234
+ * @returns {string}
235
+ */
110
236
  #outboxDomain() {
111
237
  return `revision:${this.#domain}`;
112
238
  }
113
239
  }
240
+ /**
241
+ * @template {JsonValue} TValue
242
+ * @param {unknown} value
243
+ * @param {RevisionedDomain} [expectedDomain]
244
+ * @returns {RevisionConflictRecord<TValue>}
245
+ */
114
246
  export function parseRevisionConflictRecord(value, expectedDomain) {
115
247
  if (!isRecord(value) || value.kind !== "revision-conflict.v1")
116
248
  throw invalid("revision conflict payload");
@@ -131,10 +263,16 @@ export function parseRevisionConflictRecord(value, expectedDomain) {
131
263
  domain,
132
264
  id: value.id,
133
265
  idempotencyKey: value.idempotencyKey,
134
- mine: value.mine,
135
- theirs: value.theirs,
266
+ mine: /** @type {TValue} */ (value.mine),
267
+ theirs: /** @type {RevisionEntity<TValue>} */ (value.theirs),
136
268
  };
137
269
  }
270
+ /**
271
+ * @template {JsonValue} TValue
272
+ * @param {RevisionedDomain} domain
273
+ * @param {RevisionedWriteInput<TValue>} input
274
+ * @returns {void}
275
+ */
138
276
  function validateWriteInput(domain, input) {
139
277
  if (!nonEmptyString(input.id) || !nonEmptyString(input.idempotencyKey))
140
278
  throw invalid("revision write identity");
@@ -150,6 +288,10 @@ function validateWriteInput(domain, input) {
150
288
  if (domain === "graph")
151
289
  validateGraph(input.value);
152
290
  }
291
+ /**
292
+ * @param {JsonValue} value
293
+ * @returns {void}
294
+ */
153
295
  function validateGraph(value) {
154
296
  if (!isRecord(value) || !Array.isArray(value.nodes) || !Array.isArray(value.edges))
155
297
  throw invalid("graph shape");
@@ -165,12 +307,25 @@ function validateGraph(value) {
165
307
  throw invalid("graph edge");
166
308
  }
167
309
  }
310
+ /**
311
+ * @param {unknown} value
312
+ * @returns {value is RevisionEntity<JsonValue>}
313
+ */
168
314
  function isRevisionEntity(value) {
169
315
  return isRecord(value) && nonEmptyString(value.id) && isNonNegativeInteger(value.rev) && isJsonValue(value.value);
170
316
  }
317
+ /**
318
+ * @param {unknown} value
319
+ * @returns {value is RevisionedDomain}
320
+ */
171
321
  function isRevisionedDomain(value) {
172
- return REVISIONED_DOMAINS.includes(value);
322
+ return typeof value === "string" &&
323
+ REVISIONED_DOMAINS.includes(/** @type {RevisionedDomain} */ (value));
173
324
  }
325
+ /**
326
+ * @param {unknown} value
327
+ * @returns {value is JsonValue}
328
+ */
174
329
  function isJsonValue(value) {
175
330
  if (value === null || typeof value === "string" || typeof value === "boolean")
176
331
  return true;
@@ -182,24 +337,49 @@ function isJsonValue(value) {
182
337
  return false;
183
338
  return Object.values(value).every(isJsonValue);
184
339
  }
340
+ /**
341
+ * @param {unknown} value
342
+ * @returns {value is number}
343
+ */
185
344
  function isNonNegativeInteger(value) {
186
345
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
187
346
  }
347
+ /**
348
+ * @param {unknown} value
349
+ * @returns {value is Record<string, unknown>}
350
+ */
188
351
  function isRecord(value) {
189
352
  return value !== null && typeof value === "object" && !Array.isArray(value);
190
353
  }
354
+ /**
355
+ * @param {unknown} value
356
+ * @returns {value is string}
357
+ */
191
358
  function nonEmptyString(value) {
192
359
  return typeof value === "string" && value.trim() !== "";
193
360
  }
361
+ /**
362
+ * @param {unknown} value
363
+ * @returns {string}
364
+ */
194
365
  function requiredTimestamp(value) {
195
366
  if (!nonEmptyString(value) || !Number.isFinite(Date.parse(value)))
196
367
  throw invalid("revision conflict timestamp");
197
368
  return value;
198
369
  }
370
+ /**
371
+ * @param {string} field
372
+ * @returns {DomainError}
373
+ */
199
374
  function invalid(field) {
200
375
  return new DomainError({ code: "validation_failed", message: `Invalid ${field}` });
201
376
  }
377
+ /**
378
+ * @param {RevisionedDomain} domain
379
+ * @param {string} id
380
+ * @param {string} idempotencyKey
381
+ * @returns {string}
382
+ */
202
383
  function conflictId(domain, id, idempotencyKey) {
203
384
  return `${domain}:${id}:${idempotencyKey}`;
204
385
  }
205
- //# sourceMappingURL=revisioned-domain-service.js.map
@@ -0,0 +1,174 @@
1
+ // @ts-check
2
+
3
+ /** @typedef {import("./abort-signal.js").AbortSignalPort} AbortSignalPort */
4
+
5
+ /** @typedef {{ abort(): void, readonly signal: AbortSignalPort }} RuntimeAbortController */
6
+ /** @typedef {{ readonly hostname: string, readonly origin: string, readonly password: string, readonly protocol: string, readonly username: string }} RuntimeUrl */
7
+ /** @typedef {new () => unknown} NoArgumentConstructor */
8
+ /** @typedef {new (input: string) => unknown} StringArgumentConstructor */
9
+
10
+ /**
11
+ * @returns {RuntimeAbortController}
12
+ */
13
+ export function createRuntimeAbortController() {
14
+ const Constructor = runtimeGlobal("AbortController");
15
+ if (!isNoArgumentConstructor(Constructor)) {
16
+ throw new Error("AbortController is unavailable");
17
+ }
18
+ const controller = new Constructor();
19
+ if (!isRuntimeAbortController(controller)) {
20
+ throw new TypeError("AbortController has an invalid runtime shape");
21
+ }
22
+ return controller;
23
+ }
24
+
25
+ /**
26
+ * @param {string} value
27
+ * @returns {RuntimeUrl}
28
+ */
29
+ export function parseRuntimeUrl(value) {
30
+ const Constructor = runtimeGlobal("URL");
31
+ if (!isStringArgumentConstructor(Constructor)) {
32
+ throw new Error("URL is unavailable");
33
+ }
34
+ const url = new Constructor(value);
35
+ if (!isRuntimeUrl(url)) {
36
+ throw new TypeError("URL has an invalid runtime shape");
37
+ }
38
+ return url;
39
+ }
40
+
41
+ /**
42
+ * @param {string} value
43
+ * @returns {Uint8Array}
44
+ */
45
+ export function encodeUtf8(value) {
46
+ const Constructor = runtimeGlobal("TextEncoder");
47
+ if (!isNoArgumentConstructor(Constructor)) {
48
+ throw new Error("TextEncoder is unavailable");
49
+ }
50
+ const encoder = new Constructor();
51
+ if (!isTextEncoder(encoder)) {
52
+ throw new TypeError("TextEncoder has an invalid runtime shape");
53
+ }
54
+ return encoder.encode(value);
55
+ }
56
+
57
+ /**
58
+ * @param {Uint8Array} value
59
+ * @returns {string}
60
+ */
61
+ export function decodeUtf8(value) {
62
+ const Constructor = runtimeGlobal("TextDecoder");
63
+ if (!isNoArgumentConstructor(Constructor)) {
64
+ throw new Error("TextDecoder is unavailable");
65
+ }
66
+ const decoder = new Constructor();
67
+ if (!isTextDecoder(decoder)) {
68
+ throw new TypeError("TextDecoder has an invalid runtime shape");
69
+ }
70
+ return decoder.decode(value);
71
+ }
72
+
73
+ /**
74
+ * Confine the reflection library's unsound return value here and require a
75
+ * runtime guard before every use.
76
+ *
77
+ * @param {string} name
78
+ * @returns {unknown}
79
+ */
80
+ function runtimeGlobal(name) {
81
+ return /** @type {unknown} */ (Reflect.get(globalThis, name));
82
+ }
83
+
84
+ /**
85
+ * @param {unknown} value
86
+ * @returns {value is NoArgumentConstructor}
87
+ */
88
+ function isNoArgumentConstructor(value) {
89
+ return isConstructable(value);
90
+ }
91
+
92
+ /**
93
+ * @param {unknown} value
94
+ * @returns {value is StringArgumentConstructor}
95
+ */
96
+ function isStringArgumentConstructor(value) {
97
+ return isConstructable(value);
98
+ }
99
+
100
+ /**
101
+ * Test the ECMAScript [[Construct]] capability directly. Bound constructors
102
+ * deliberately have no own `prototype` property but remain constructable.
103
+ *
104
+ * @param {unknown} value
105
+ * @returns {boolean}
106
+ */
107
+ function isConstructable(value) {
108
+ if (typeof value !== "function") return false;
109
+ try {
110
+ Reflect.construct(Object, [], value);
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * @param {unknown} value
119
+ * @returns {value is RuntimeAbortController}
120
+ */
121
+ function isRuntimeAbortController(value) {
122
+ return isRecord(value) && typeof value.abort === "function" && isAbortSignal(value.signal);
123
+ }
124
+
125
+ /**
126
+ * @param {unknown} value
127
+ * @returns {value is AbortSignalPort}
128
+ */
129
+ function isAbortSignal(value) {
130
+ return isRecord(value) &&
131
+ typeof value.aborted === "boolean" &&
132
+ typeof value.addEventListener === "function" &&
133
+ typeof value.dispatchEvent === "function" &&
134
+ typeof value.removeEventListener === "function" &&
135
+ typeof value.throwIfAborted === "function" &&
136
+ "reason" in value;
137
+ }
138
+
139
+ /**
140
+ * @param {unknown} value
141
+ * @returns {value is RuntimeUrl}
142
+ */
143
+ function isRuntimeUrl(value) {
144
+ return isRecord(value) &&
145
+ typeof value.hostname === "string" &&
146
+ typeof value.origin === "string" &&
147
+ typeof value.password === "string" &&
148
+ typeof value.protocol === "string" &&
149
+ typeof value.username === "string";
150
+ }
151
+
152
+ /**
153
+ * @param {unknown} value
154
+ * @returns {value is { encode(input?: string): Uint8Array }}
155
+ */
156
+ function isTextEncoder(value) {
157
+ return isRecord(value) && typeof value.encode === "function";
158
+ }
159
+
160
+ /**
161
+ * @param {unknown} value
162
+ * @returns {value is { decode(input?: Uint8Array): string }}
163
+ */
164
+ function isTextDecoder(value) {
165
+ return isRecord(value) && typeof value.decode === "function";
166
+ }
167
+
168
+ /**
169
+ * @param {unknown} value
170
+ * @returns {value is Record<string, unknown>}
171
+ */
172
+ function isRecord(value) {
173
+ return typeof value === "object" && value !== null;
174
+ }
@@ -0,0 +1,50 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {import("@econ-v1/ports").DeviceDelegation} DeviceDelegation
5
+ */
6
+ /**
7
+ * @typedef {import("@econ-v1/ports").DeviceIdentity} DeviceIdentity
8
+ */
9
+ /**
10
+ * @typedef {import("@econ-v1/ports").IdentityDiagnostics} IdentityDiagnostics
11
+ */
12
+ /**
13
+ * @typedef {import("@econ-v1/ports").IdentityPort} IdentityPort
14
+ */
15
+ import { normalizeDelegation } from "../models/delegation.js";
16
+ export class SessionService {
17
+ /** @readonly @type {IdentityPort} */
18
+ identity;
19
+ /**
20
+ * @param {IdentityPort} identity
21
+ */
22
+ constructor(identity) {
23
+ this.identity = identity;
24
+ }
25
+ /**
26
+ * @returns {Promise<DeviceIdentity>}
27
+ */
28
+ async initialize() {
29
+ return await this.identity.generate();
30
+ }
31
+ /**
32
+ * @param {DeviceDelegation} delegation
33
+ * @returns {Promise<void>}
34
+ */
35
+ async acceptDelegation(delegation) {
36
+ await this.identity.storeDelegation(normalizeDelegation(delegation));
37
+ }
38
+ /**
39
+ * @returns {Promise<void>}
40
+ */
41
+ async revoke() {
42
+ await this.identity.clear();
43
+ }
44
+ /**
45
+ * @returns {Promise<IdentityDiagnostics>}
46
+ */
47
+ async diagnostics() {
48
+ return await this.identity.diagnostics();
49
+ }
50
+ }
@@ -1,19 +1,87 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @template TPayload
5
+ * @typedef {import("../../models/sync.js").AuthoritativeCommand<TPayload>} AuthoritativeCommand
6
+ */
7
+ /**
8
+ * @template TValue
9
+ * @typedef {import("../../models/sync.js").AuthoritativeSnapshot<TValue>} AuthoritativeSnapshot
10
+ */
11
+ /**
12
+ * @typedef {import("../../models/sync.js").SyncApplyResult} SyncApplyResult
13
+ */
14
+ /**
15
+ * @template TPayload
16
+ * @typedef {import("../../models/sync.js").SyncDelta<TPayload>} SyncDelta
17
+ */
18
+ /**
19
+ * @typedef {import("../../models/sync.js").SyncDeltaContext} SyncDeltaContext
20
+ */
21
+ /**
22
+ * @template TPayload
23
+ * @typedef {import("../../models/sync.js").SyncStrategy<TPayload>} SyncStrategy
24
+ */
25
+ /**
26
+ * @template TValue
27
+ * @template TCommand
28
+ * @template TResult
29
+ * @typedef {{ readonly apply: (snapshot: AuthoritativeSnapshot<TValue>, delta: SyncDelta<TValue>) => Promise<void>; readonly execute: (command: AuthoritativeCommand<TCommand>) => Promise<TResult>; readonly staleAfterMs: number; }} AuthoritativeMirrorOptions
30
+ */
31
+ /**
32
+ * @typedef {{ readonly now: string; readonly revalidate?: () => Promise<boolean>; }} AuthoritativeCommandOptions
33
+ */
34
+ /**
35
+ * @extends {Error}
36
+ */
1
37
  export class AuthoritativeRevalidationRequiredError extends Error {
38
+ /**
39
+ * @readonly
40
+ * @type {string}
41
+ */
2
42
  idempotencyKey;
43
+ /**
44
+ * @param {string} idempotencyKey
45
+ */
3
46
  constructor(idempotencyKey) {
4
47
  super(`Authoritative command ${idempotencyKey} requires fresh state revalidation`);
5
48
  this.name = "AuthoritativeRevalidationRequiredError";
6
49
  this.idempotencyKey = idempotencyKey;
7
50
  }
8
51
  }
52
+ /**
53
+ * @template TValue
54
+ * @template TCommand
55
+ * @template TResult
56
+ * @implements {SyncStrategy<TValue>}
57
+ */
9
58
  export class AuthoritativeMirror {
59
+ /** @readonly @type {"authoritative-mirror"} */
10
60
  kind = "authoritative-mirror";
61
+ /**
62
+ * @readonly
63
+ * @type {AuthoritativeMirrorOptions<TValue, TCommand, TResult>["apply"]}
64
+ */
11
65
  #apply;
66
+ /**
67
+ * @readonly
68
+ * @type {AuthoritativeMirrorOptions<TValue, TCommand, TResult>["execute"]}
69
+ */
12
70
  #execute;
71
+ /**
72
+ * @readonly
73
+ * @type {number}
74
+ */
13
75
  #staleAfterMs;
14
76
  #applied = new Set();
15
77
  #commands = new Map();
78
+ /**
79
+ * @type {AuthoritativeSnapshot<TValue> | undefined}
80
+ */
16
81
  #snapshot;
82
+ /**
83
+ * @param {AuthoritativeMirrorOptions<TValue, TCommand, TResult>} options
84
+ */
17
85
  constructor(options) {
18
86
  if (!Number.isInteger(options.staleAfterMs) || options.staleAfterMs <= 0) {
19
87
  throw new RangeError("Authoritative mirror staleAfterMs must be a positive integer");
@@ -22,6 +90,11 @@ export class AuthoritativeMirror {
22
90
  this.#execute = options.execute;
23
91
  this.#staleAfterMs = options.staleAfterMs;
24
92
  }
93
+ /**
94
+ * @param {SyncDelta<TValue>} delta
95
+ * @param {SyncDeltaContext} context
96
+ * @returns {Promise<SyncApplyResult>}
97
+ */
25
98
  async applyDelta(delta, context) {
26
99
  if (this.#applied.has(delta.id))
27
100
  return { applied: false };
@@ -36,12 +109,24 @@ export class AuthoritativeMirror {
36
109
  this.#applied.add(delta.id);
37
110
  return { applied: true };
38
111
  }
112
+ /**
113
+ * @returns {AuthoritativeSnapshot<TValue> | undefined}
114
+ */
39
115
  snapshot() {
40
116
  return this.#snapshot;
41
117
  }
118
+ /**
119
+ * @param {string} now
120
+ * @returns {boolean}
121
+ */
42
122
  isStale(now) {
43
123
  return this.#snapshot === undefined || parseDate(now, "current time") >= Date.parse(this.#snapshot.staleAt);
44
124
  }
125
+ /**
126
+ * @param {AuthoritativeCommand<TCommand>} command
127
+ * @param {AuthoritativeCommandOptions} options
128
+ * @returns {Promise<TResult>}
129
+ */
45
130
  async runCommand(command, options) {
46
131
  const existing = this.#commands.get(command.idempotencyKey);
47
132
  if (existing !== undefined)
@@ -62,10 +147,14 @@ export class AuthoritativeMirror {
62
147
  }
63
148
  }
64
149
  }
150
+ /**
151
+ * @param {string} value
152
+ * @param {string} label
153
+ * @returns {number}
154
+ */
65
155
  function parseDate(value, label) {
66
156
  const parsed = Date.parse(value);
67
157
  if (!Number.isFinite(parsed))
68
158
  throw new RangeError(`Invalid ${label}`);
69
159
  return parsed;
70
160
  }
71
- //# sourceMappingURL=authoritative-mirror.js.map