@learncard/network-brain-service 3.18.0 → 3.19.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 (478) hide show
  1. package/.env.example +38 -2
  2. package/CHANGELOG.md +118 -0
  3. package/README.md +42 -0
  4. package/compose.inbox.yml +7 -0
  5. package/dist/accesslayer/auth-grant/read.d.ts +1 -0
  6. package/dist/accesslayer/auth-grant/read.d.ts.map +1 -1
  7. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  8. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  9. package/dist/accesslayer/credential-activity/create.d.ts.map +1 -1
  10. package/dist/accesslayer/credential-refresh/email-delivery.d.ts +18 -0
  11. package/dist/accesslayer/credential-refresh/email-delivery.d.ts.map +1 -0
  12. package/dist/accesslayer/credential-refresh/index.d.ts +1 -0
  13. package/dist/accesslayer/credential-refresh/index.d.ts.map +1 -1
  14. package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
  15. package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
  16. package/dist/accesslayer/inbox-batch/store.d.ts +33 -0
  17. package/dist/accesslayer/inbox-batch/store.d.ts.map +1 -0
  18. package/dist/accesslayer/inbox-credential/create.d.ts +5 -0
  19. package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
  20. package/dist/accesslayer/inbox-credential/refresh.d.ts +71 -0
  21. package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -0
  22. package/dist/accesslayer/inbox-credential/update.d.ts +1 -1
  23. package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
  24. package/dist/accesslayer/presentation/read.d.ts.map +1 -1
  25. package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
  26. package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
  27. package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
  28. package/dist/accesslayer/share-link/abandon.d.ts +3 -0
  29. package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
  30. package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
  31. package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
  32. package/dist/accesslayer/share-link/errors.d.ts +8 -0
  33. package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
  34. package/dist/accesslayer/share-link/finalize.d.ts +3 -0
  35. package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
  36. package/dist/accesslayer/share-link/helpers.d.ts +56 -0
  37. package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
  38. package/dist/accesslayer/share-link/index.d.ts +12 -0
  39. package/dist/accesslayer/share-link/index.d.ts.map +1 -0
  40. package/dist/accesslayer/share-link/list.d.ts +5 -0
  41. package/dist/accesslayer/share-link/list.d.ts.map +1 -0
  42. package/dist/accesslayer/share-link/read.d.ts +5 -0
  43. package/dist/accesslayer/share-link/read.d.ts.map +1 -0
  44. package/dist/accesslayer/share-link/receipt.d.ts +11 -0
  45. package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
  46. package/dist/accesslayer/share-link/recovery.d.ts +6 -0
  47. package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
  48. package/dist/accesslayer/share-link/reserve.d.ts +4 -0
  49. package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
  50. package/dist/accesslayer/share-link/revoke.d.ts +3 -0
  51. package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
  52. package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
  53. package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
  54. package/dist/accesslayer/share-link/transaction.d.ts +15 -0
  55. package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
  56. package/dist/accesslayer/share-link/types.d.ts +370 -0
  57. package/dist/accesslayer/share-link/types.d.ts.map +1 -0
  58. package/dist/app.d.ts +4 -0
  59. package/dist/app.d.ts.map +1 -1
  60. package/dist/cache/atomic-counter.test-helpers.d.ts +3 -0
  61. package/dist/cache/atomic-counter.test-helpers.d.ts.map +1 -0
  62. package/dist/cache/atomic-counter.test.d.ts +2 -0
  63. package/dist/cache/atomic-counter.test.d.ts.map +1 -0
  64. package/dist/cache/index.d.ts +1 -1
  65. package/dist/cache/index.d.ts.map +1 -1
  66. package/dist/config/environment.d.ts +55 -0
  67. package/dist/config/environment.d.ts.map +1 -1
  68. package/dist/constants/auth-grant.d.ts +2 -0
  69. package/dist/constants/auth-grant.d.ts.map +1 -1
  70. package/dist/credential-refresh.d.ts.map +1 -1
  71. package/dist/helpers/activity.helpers.d.ts +4 -0
  72. package/dist/helpers/activity.helpers.d.ts.map +1 -1
  73. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  74. package/dist/helpers/contact-method.helpers.d.ts.map +1 -1
  75. package/dist/helpers/contact-method.helpers.test.d.ts +2 -0
  76. package/dist/helpers/contact-method.helpers.test.d.ts.map +1 -0
  77. package/dist/helpers/credential-refresh-email-content.helpers.d.ts +44 -0
  78. package/dist/helpers/credential-refresh-email-content.helpers.d.ts.map +1 -0
  79. package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts +2 -0
  80. package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts.map +1 -0
  81. package/dist/helpers/credential-refresh-email.helpers.d.ts +17 -0
  82. package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -0
  83. package/dist/helpers/credential-refresh-proof.helpers.d.ts +4 -0
  84. package/dist/helpers/credential-refresh-proof.helpers.d.ts.map +1 -0
  85. package/dist/helpers/credential-refresh-proof.helpers.test.d.ts +2 -0
  86. package/dist/helpers/credential-refresh-proof.helpers.test.d.ts.map +1 -0
  87. package/dist/helpers/credential-refresh.helpers.d.ts +45 -2
  88. package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
  89. package/dist/helpers/credential.helpers.d.ts +2 -1
  90. package/dist/helpers/credential.helpers.d.ts.map +1 -1
  91. package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
  92. package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
  93. package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
  94. package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
  95. package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
  96. package/dist/helpers/inbox-batch-http.helpers.d.ts +15 -0
  97. package/dist/helpers/inbox-batch-http.helpers.d.ts.map +1 -0
  98. package/dist/helpers/inbox-batch-http.helpers.test.d.ts +2 -0
  99. package/dist/helpers/inbox-batch-http.helpers.test.d.ts.map +1 -0
  100. package/dist/helpers/inbox-batch-status.helpers.d.ts +3 -0
  101. package/dist/helpers/inbox-batch-status.helpers.d.ts.map +1 -0
  102. package/dist/helpers/inbox-batch.helpers.d.ts +10 -0
  103. package/dist/helpers/inbox-batch.helpers.d.ts.map +1 -0
  104. package/dist/helpers/inbox-batch.helpers.test.d.ts +2 -0
  105. package/dist/helpers/inbox-batch.helpers.test.d.ts.map +1 -0
  106. package/dist/helpers/inbox-issuance-error.helpers.d.ts +13 -0
  107. package/dist/helpers/inbox-issuance-error.helpers.d.ts.map +1 -0
  108. package/dist/helpers/inbox-queue.helpers.d.ts +11 -0
  109. package/dist/helpers/inbox-queue.helpers.d.ts.map +1 -0
  110. package/dist/helpers/inbox-queue.helpers.test.d.ts +2 -0
  111. package/dist/helpers/inbox-queue.helpers.test.d.ts.map +1 -0
  112. package/dist/helpers/inbox-refresh.helpers.d.ts +186 -0
  113. package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -0
  114. package/dist/helpers/inbox.helpers.d.ts +9 -1
  115. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  116. package/dist/helpers/notificationMessages.d.ts +1 -1
  117. package/dist/helpers/notificationMessages.d.ts.map +1 -1
  118. package/dist/helpers/notifications.helpers.d.ts.map +1 -1
  119. package/dist/helpers/presentation-metadata.d.ts +4 -0
  120. package/dist/helpers/presentation-metadata.d.ts.map +1 -0
  121. package/dist/helpers/presentation.helpers.d.ts +1 -1
  122. package/dist/helpers/presentation.helpers.d.ts.map +1 -1
  123. package/dist/helpers/profile.helpers.d.ts +1 -0
  124. package/dist/helpers/profile.helpers.d.ts.map +1 -1
  125. package/dist/helpers/rateLimit.helpers.d.ts.map +1 -1
  126. package/dist/helpers/refresh-send-intent.helpers.d.ts +51 -0
  127. package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -0
  128. package/dist/helpers/share-content-client/adapters.d.ts +10 -0
  129. package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
  130. package/dist/helpers/share-content-client/canonical.d.ts +7 -0
  131. package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
  132. package/dist/helpers/share-content-client/client.d.ts +5 -0
  133. package/dist/helpers/share-content-client/client.d.ts.map +1 -0
  134. package/dist/helpers/share-content-client/config.d.ts +11 -0
  135. package/dist/helpers/share-content-client/config.d.ts.map +1 -0
  136. package/dist/helpers/share-content-client/index.d.ts +6 -0
  137. package/dist/helpers/share-content-client/index.d.ts.map +1 -0
  138. package/dist/helpers/share-content-client/responses.d.ts +16 -0
  139. package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
  140. package/dist/helpers/share-content-client/types.d.ts +145 -0
  141. package/dist/helpers/share-content-client/types.d.ts.map +1 -0
  142. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
  143. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
  144. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
  145. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
  146. package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
  147. package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
  148. package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
  149. package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
  150. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
  151. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
  152. package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
  153. package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
  154. package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
  155. package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
  156. package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
  157. package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
  158. package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
  159. package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
  160. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
  161. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
  162. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
  163. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
  164. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
  165. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
  166. package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
  167. package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
  168. package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
  169. package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
  170. package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
  171. package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
  172. package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
  173. package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
  174. package/dist/helpers/share-link-maintenance/runner.d.ts +19 -0
  175. package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
  176. package/dist/helpers/share-link-maintenance/runtime.d.ts +36 -0
  177. package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
  178. package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
  179. package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
  180. package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
  181. package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
  182. package/dist/helpers/share-link-maintenance/types.d.ts +53 -0
  183. package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
  184. package/dist/helpers/share-link-owner/config.d.ts +16 -0
  185. package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
  186. package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
  187. package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
  188. package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
  189. package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
  190. package/dist/helpers/share-link-owner-projection.d.ts +4 -0
  191. package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
  192. package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
  193. package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
  194. package/dist/helpers/share-link-passcode.d.ts +6 -0
  195. package/dist/helpers/share-link-passcode.d.ts.map +1 -0
  196. package/dist/helpers/share-link-policy/index.d.ts +4 -0
  197. package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
  198. package/dist/helpers/share-link-policy/production.d.ts +13 -0
  199. package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
  200. package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
  201. package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
  202. package/dist/helpers/share-link-policy/types.d.ts +16 -0
  203. package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
  204. package/dist/helpers/share-link-public/config.d.ts +13 -0
  205. package/dist/helpers/share-link-public/config.d.ts.map +1 -0
  206. package/dist/helpers/share-link-view-notification.d.ts +2 -0
  207. package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
  208. package/dist/helpers/shim.d.ts +1 -1
  209. package/dist/helpers/shim.d.ts.map +1 -1
  210. package/dist/inbox-queue-local.d.ts +2 -0
  211. package/dist/inbox-queue-local.d.ts.map +1 -0
  212. package/dist/index.js +2 -2
  213. package/dist/models/AuthGrant.d.ts.map +1 -1
  214. package/dist/models/CredentialActivity.d.ts +1 -0
  215. package/dist/models/CredentialActivity.d.ts.map +1 -1
  216. package/dist/models/CredentialRefresh.d.ts +5 -2
  217. package/dist/models/CredentialRefresh.d.ts.map +1 -1
  218. package/dist/models/InboxCredential.d.ts +1 -0
  219. package/dist/models/InboxCredential.d.ts.map +1 -1
  220. package/dist/models/Presentation.d.ts +2 -0
  221. package/dist/models/Presentation.d.ts.map +1 -1
  222. package/dist/models/Profile.d.ts +2 -0
  223. package/dist/models/Profile.d.ts.map +1 -1
  224. package/dist/models/ShareLink.d.ts +44 -0
  225. package/dist/models/ShareLink.d.ts.map +1 -0
  226. package/dist/models/ShareViewReceipt.d.ts +22 -0
  227. package/dist/models/ShareViewReceipt.d.ts.map +1 -0
  228. package/dist/models/credential-refresh-constraints.d.ts.map +1 -1
  229. package/dist/models/index.d.ts +1 -0
  230. package/dist/models/index.d.ts.map +1 -1
  231. package/dist/models/share-link-constraints.d.ts +3 -0
  232. package/dist/models/share-link-constraints.d.ts.map +1 -0
  233. package/dist/openapi.d.ts.map +1 -1
  234. package/dist/public-share-link-http.d.ts +4 -0
  235. package/dist/public-share-link-http.d.ts.map +1 -0
  236. package/dist/routes/act-as.test.d.ts +2 -0
  237. package/dist/routes/act-as.test.d.ts.map +1 -0
  238. package/dist/routes/activity.d.ts +3 -0
  239. package/dist/routes/activity.d.ts.map +1 -1
  240. package/dist/routes/app-store.d.ts.map +1 -1
  241. package/dist/routes/auth-grants.d.ts +10 -0
  242. package/dist/routes/auth-grants.d.ts.map +1 -1
  243. package/dist/routes/boosts.d.ts +494 -0
  244. package/dist/routes/boosts.d.ts.map +1 -1
  245. package/dist/routes/contracts.d.ts +2 -2
  246. package/dist/routes/contracts.d.ts.map +1 -1
  247. package/dist/routes/credential-refreshes.d.ts.map +1 -1
  248. package/dist/routes/credentials.d.ts +1 -1
  249. package/dist/routes/credentials.d.ts.map +1 -1
  250. package/dist/routes/inbox.d.ts +953 -11
  251. package/dist/routes/inbox.d.ts.map +1 -1
  252. package/dist/routes/index.d.ts +154 -85
  253. package/dist/routes/index.d.ts.map +1 -1
  254. package/dist/routes/presentations.d.ts +1 -0
  255. package/dist/routes/presentations.d.ts.map +1 -1
  256. package/dist/routes/profile-manager.d.ts.map +1 -1
  257. package/dist/routes/profiles.d.ts.map +1 -1
  258. package/dist/routes/public-share-links.d.ts +230 -0
  259. package/dist/routes/public-share-links.d.ts.map +1 -0
  260. package/dist/routes/share-links.d.ts +844 -0
  261. package/dist/routes/share-links.d.ts.map +1 -0
  262. package/dist/routes/workflows.d.ts.map +1 -1
  263. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  264. package/dist/services/delivery/adapters/postmark.adapter.test.d.ts +2 -0
  265. package/dist/services/delivery/adapters/postmark.adapter.test.d.ts.map +1 -0
  266. package/dist/services/registry/registry.factory.d.ts.map +1 -1
  267. package/dist/types/activity.d.ts +4 -0
  268. package/dist/types/activity.d.ts.map +1 -1
  269. package/dist/types/consentflowcontract.d.ts +1 -1
  270. package/dist/types/credential-refresh.d.ts +10 -1
  271. package/dist/types/credential-refresh.d.ts.map +1 -1
  272. package/dist/types/inbox-batch.d.ts +38 -0
  273. package/dist/types/inbox-batch.d.ts.map +1 -0
  274. package/inbox-e2e-setup.ts +53 -1
  275. package/inbox-elasticmq.conf +10 -0
  276. package/lambda.ts +64 -16
  277. package/package.json +24 -16
  278. package/scripts/seed-demo-persona.ts +425 -0
  279. package/scripts/setup-inbox-queues.ts +60 -0
  280. package/serverless-local.yml +2 -0
  281. package/serverless.yml +111 -0
  282. package/shareLinkMaintenanceLambda.ts +49 -0
  283. package/src/accesslayer/auth-grant/read.ts +2 -1
  284. package/src/accesslayer/consentflowcontract/relationships/create.ts +9 -16
  285. package/src/accesslayer/consentflowcontract/relationships/update.ts +15 -28
  286. package/src/accesslayer/credential-activity/create.ts +1 -0
  287. package/src/accesslayer/credential-refresh/email-delivery.ts +116 -0
  288. package/src/accesslayer/credential-refresh/index.ts +1 -0
  289. package/src/accesslayer/credential-refresh/read.ts +8 -2
  290. package/src/accesslayer/credential-refresh/update.ts +4 -0
  291. package/src/accesslayer/inbox-batch/store.ts +469 -0
  292. package/src/accesslayer/inbox-credential/create.ts +51 -19
  293. package/src/accesslayer/inbox-credential/refresh.ts +221 -0
  294. package/src/accesslayer/inbox-credential/update.ts +1 -1
  295. package/src/accesslayer/presentation/read.ts +57 -25
  296. package/src/accesslayer/presentation/relationships/create.ts +54 -11
  297. package/src/accesslayer/presentation/relationships/read.ts +21 -6
  298. package/src/accesslayer/share-link/abandon.ts +100 -0
  299. package/src/accesslayer/share-link/cleanup.ts +271 -0
  300. package/src/accesslayer/share-link/errors.ts +32 -0
  301. package/src/accesslayer/share-link/finalize.ts +265 -0
  302. package/src/accesslayer/share-link/helpers.ts +419 -0
  303. package/src/accesslayer/share-link/index.ts +11 -0
  304. package/src/accesslayer/share-link/list.ts +162 -0
  305. package/src/accesslayer/share-link/read.ts +107 -0
  306. package/src/accesslayer/share-link/receipt.ts +548 -0
  307. package/src/accesslayer/share-link/recovery.ts +545 -0
  308. package/src/accesslayer/share-link/reserve.ts +684 -0
  309. package/src/accesslayer/share-link/revoke.ts +197 -0
  310. package/src/accesslayer/share-link/transaction-execution.ts +72 -0
  311. package/src/accesslayer/share-link/transaction.ts +126 -0
  312. package/src/accesslayer/share-link/types.ts +601 -0
  313. package/src/app.ts +6 -0
  314. package/src/cache/atomic-counter.test-helpers.ts +27 -0
  315. package/src/cache/atomic-counter.test.ts +6 -0
  316. package/src/cache/index.ts +25 -13
  317. package/src/config/environment.ts +40 -0
  318. package/src/constants/auth-grant.ts +5 -0
  319. package/src/credential-refresh.ts +5 -1
  320. package/src/docker-entry.ts +65 -0
  321. package/src/helpers/activity.helpers.ts +4 -0
  322. package/src/helpers/app-counter.helpers.ts +1 -1
  323. package/src/helpers/boost.helpers.ts +4 -31
  324. package/src/helpers/contact-method.helpers.test.ts +43 -0
  325. package/src/helpers/contact-method.helpers.ts +3 -4
  326. package/src/helpers/credential-refresh-email-content.helpers.test.ts +222 -0
  327. package/src/helpers/credential-refresh-email-content.helpers.ts +155 -0
  328. package/src/helpers/credential-refresh-email.helpers.ts +201 -0
  329. package/src/helpers/credential-refresh-proof.helpers.test.ts +103 -0
  330. package/src/helpers/credential-refresh-proof.helpers.ts +43 -0
  331. package/src/helpers/credential-refresh.helpers.ts +375 -99
  332. package/src/helpers/credential.helpers.ts +4 -1
  333. package/src/helpers/credentialSubject.helpers.test.ts +114 -0
  334. package/src/helpers/credentialSubject.helpers.ts +40 -0
  335. package/src/helpers/finalize-inbox.helpers.ts +29 -14
  336. package/src/helpers/inbox-batch-http.helpers.test.ts +188 -0
  337. package/src/helpers/inbox-batch-http.helpers.ts +150 -0
  338. package/src/helpers/inbox-batch-status.helpers.ts +11 -0
  339. package/src/helpers/inbox-batch.helpers.test.ts +668 -0
  340. package/src/helpers/inbox-batch.helpers.ts +374 -0
  341. package/src/helpers/inbox-issuance-error.helpers.ts +25 -0
  342. package/src/helpers/inbox-queue.helpers.test.ts +253 -0
  343. package/src/helpers/inbox-queue.helpers.ts +372 -0
  344. package/src/helpers/inbox-refresh.helpers.ts +432 -0
  345. package/src/helpers/inbox.helpers.ts +275 -36
  346. package/src/helpers/notificationMessages.ts +18 -1
  347. package/src/helpers/notifications.helpers.ts +4 -2
  348. package/src/helpers/presentation-metadata.ts +41 -0
  349. package/src/helpers/presentation.helpers.ts +27 -20
  350. package/src/helpers/profile.helpers.ts +7 -0
  351. package/src/helpers/rateLimit.helpers.ts +2 -5
  352. package/src/helpers/refresh-send-intent.helpers.ts +273 -0
  353. package/src/helpers/share-content-client/adapters.ts +55 -0
  354. package/src/helpers/share-content-client/canonical.ts +25 -0
  355. package/src/helpers/share-content-client/client.ts +440 -0
  356. package/src/helpers/share-content-client/config.ts +189 -0
  357. package/src/helpers/share-content-client/index.ts +27 -0
  358. package/src/helpers/share-content-client/responses.ts +273 -0
  359. package/src/helpers/share-content-client/types.ts +253 -0
  360. package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
  361. package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
  362. package/src/helpers/share-link-coordinator/coordinator.ts +714 -0
  363. package/src/helpers/share-link-coordinator/index.ts +8 -0
  364. package/src/helpers/share-link-coordinator/recovery-runner.ts +428 -0
  365. package/src/helpers/share-link-coordinator/runtime.ts +88 -0
  366. package/src/helpers/share-link-coordinator/stat.ts +30 -0
  367. package/src/helpers/share-link-coordinator/types.ts +353 -0
  368. package/src/helpers/share-link-lifecycle/index.ts +4 -0
  369. package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
  370. package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
  371. package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
  372. package/src/helpers/share-link-lifecycle/types.ts +58 -0
  373. package/src/helpers/share-link-maintenance/budget.ts +63 -0
  374. package/src/helpers/share-link-maintenance/config.ts +312 -0
  375. package/src/helpers/share-link-maintenance/index.ts +24 -0
  376. package/src/helpers/share-link-maintenance/runner.ts +223 -0
  377. package/src/helpers/share-link-maintenance/runtime.ts +334 -0
  378. package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
  379. package/src/helpers/share-link-maintenance/telemetry.ts +70 -0
  380. package/src/helpers/share-link-maintenance/types.ts +89 -0
  381. package/src/helpers/share-link-owner/config.ts +48 -0
  382. package/src/helpers/share-link-owner/diagnostics.ts +89 -0
  383. package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
  384. package/src/helpers/share-link-owner-projection.ts +52 -0
  385. package/src/helpers/share-link-passcode-abuse.ts +36 -0
  386. package/src/helpers/share-link-passcode.ts +47 -0
  387. package/src/helpers/share-link-policy/index.ts +7 -0
  388. package/src/helpers/share-link-policy/production.ts +88 -0
  389. package/src/helpers/share-link-policy/resolver.ts +108 -0
  390. package/src/helpers/share-link-policy/types.ts +48 -0
  391. package/src/helpers/share-link-public/config.ts +41 -0
  392. package/src/helpers/share-link-view-notification.ts +18 -0
  393. package/src/helpers/shim.ts +32 -31
  394. package/src/inbox-queue-local.ts +28 -0
  395. package/src/models/AuthGrant.ts +1 -0
  396. package/src/models/CredentialActivity.ts +3 -5
  397. package/src/models/CredentialRefresh.ts +4 -2
  398. package/src/models/InboxCredential.ts +2 -0
  399. package/src/models/Presentation.ts +2 -2
  400. package/src/models/Profile.ts +3 -2
  401. package/src/models/ShareLink.ts +130 -0
  402. package/src/models/ShareViewReceipt.ts +69 -0
  403. package/src/models/credential-refresh-constraints.test.ts +8 -3
  404. package/src/models/credential-refresh-constraints.ts +5 -0
  405. package/src/models/index.ts +2 -0
  406. package/src/models/share-link-constraints.ts +93 -0
  407. package/src/openapi.ts +8 -0
  408. package/src/public-share-link-http.ts +49 -0
  409. package/src/routes/act-as.test.ts +239 -0
  410. package/src/routes/app-store.ts +4 -13
  411. package/src/routes/auth-grants.ts +13 -11
  412. package/src/routes/boosts.ts +916 -103
  413. package/src/routes/contracts.ts +2 -11
  414. package/src/routes/credential-refreshes.test.ts +8 -2
  415. package/src/routes/credential-refreshes.ts +4 -1
  416. package/src/routes/credentials.ts +2 -1
  417. package/src/routes/inbox.ts +95 -49
  418. package/src/routes/index.ts +216 -46
  419. package/src/routes/presentations.ts +19 -3
  420. package/src/routes/profile-manager.ts +10 -2
  421. package/src/routes/profiles.ts +17 -6
  422. package/src/routes/public-share-links.ts +814 -0
  423. package/src/routes/share-links.ts +640 -0
  424. package/src/routes/skills.ts +1 -1
  425. package/src/routes/workflows.ts +43 -24
  426. package/src/services/delivery/adapters/postmark.adapter.test.ts +54 -0
  427. package/src/services/delivery/adapters/postmark.adapter.ts +16 -1
  428. package/src/services/registry/registry.factory.ts +2 -1
  429. package/src/types/activity.ts +2 -0
  430. package/src/types/credential-refresh.ts +21 -2
  431. package/src/types/inbox-batch.ts +46 -0
  432. package/test/atomic-counter.e2e.ts +12 -0
  433. package/test/auth-grant.spec.ts +21 -21
  434. package/test/credential-refresh-email.spec.ts +663 -0
  435. package/test/credential-refresh-publication.spec.ts +2 -0
  436. package/test/dids.spec.ts +10 -1
  437. package/test/guardian-gated-routes.spec.ts +13 -0
  438. package/test/helpers/inbox-e2e-environment.ts +6 -0
  439. package/test/helpers/share-link-fixtures.ts +94 -0
  440. package/test/inbox-batch.spec.ts +1338 -0
  441. package/test/inbox-refresh.spec.ts +534 -0
  442. package/test/managed-recipient-inbox.spec.ts +130 -0
  443. package/test/notificationMessages.spec.ts +13 -0
  444. package/test/openapi.spec.ts +61 -1
  445. package/test/presentations.spec.ts +54 -6
  446. package/test/profiles.spec.ts +67 -1
  447. package/test/refresh-send.spec.ts +1799 -0
  448. package/test/registry.spec.ts +56 -11
  449. package/test/share-content-client.contract.spec.ts +365 -0
  450. package/test/share-content-client.unit.spec.ts +462 -0
  451. package/test/share-link-abuse.unit.spec.ts +65 -0
  452. package/test/share-link-coordinator.neo4j.spec.ts +684 -0
  453. package/test/share-link-coordinator.unit.spec.ts +990 -0
  454. package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
  455. package/test/share-link-lifecycle.neo4j.spec.ts +1268 -0
  456. package/test/share-link-lifecycle.unit.spec.ts +205 -0
  457. package/test/share-link-list-cursor.unit.spec.ts +86 -0
  458. package/test/share-link-list.neo4j.spec.ts +324 -0
  459. package/test/share-link-maintenance.neo4j.spec.ts +355 -0
  460. package/test/share-link-maintenance.unit.spec.ts +1113 -0
  461. package/test/share-link-owner-policy.unit.spec.ts +210 -0
  462. package/test/share-link-owner-route.unit.spec.ts +752 -0
  463. package/test/share-link-owner-runtime.unit.spec.ts +102 -0
  464. package/test/share-link-passcode.unit.spec.ts +29 -0
  465. package/test/share-link-policy.unit.spec.ts +135 -0
  466. package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
  467. package/test/share-link-production-policy.unit.spec.ts +77 -0
  468. package/test/share-link-public-config.unit.spec.ts +64 -0
  469. package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
  470. package/test/share-link-public-route.unit.spec.ts +800 -0
  471. package/test/share-link-receipt.neo4j.spec.ts +943 -0
  472. package/test/share-link-recovery.neo4j.spec.ts +935 -0
  473. package/test/share-link-recovery.unit.spec.ts +726 -0
  474. package/vitest.config.ts +10 -0
  475. package/vitest.inbox-e2e.config.ts +5 -1
  476. package/vitest.integration.config.ts +2 -0
  477. package/vitest.share-links-neo4j.config.ts +19 -0
  478. package/vitest.share-links-unit.config.ts +28 -0
@@ -0,0 +1,221 @@
1
+ import { CredentialRefreshRecordValidator } from 'types/credential-refresh';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { TRPCError } from '@trpc/server';
4
+ import { neogma } from '@instance';
5
+ import type { InboxCredentialType, PublishCredentialRefreshResult } from '@learncard/types';
6
+ import { inflateObject } from '@helpers/objects.helpers';
7
+
8
+ export const getInboxRefreshIssue = async (
9
+ issueKey: string
10
+ ): Promise<{
11
+ inbox: InboxCredentialType;
12
+ requestDigest: string;
13
+ claimUrl?: string;
14
+ } | null> => {
15
+ const result = await neogma.queryRunner.run(
16
+ 'MATCH (ic:InboxCredential {refreshIssueKey: $issueKey}) RETURN ic LIMIT 1',
17
+ { issueKey }
18
+ );
19
+ const props = result.records[0]?.get('ic')?.properties;
20
+ return props
21
+ ? {
22
+ inbox: inflateObject(props),
23
+ requestDigest: props.refreshRequestDigest,
24
+ claimUrl: props.refreshClaimUrl,
25
+ }
26
+ : null;
27
+ };
28
+
29
+ /** Creates the inbox row and unbound aggregate together; a retry cannot allocate a second root. */
30
+ export const createPendingInboxRefresh = async (params: {
31
+ inbox: Record<string, unknown>;
32
+ aggregate: Record<string, unknown>;
33
+ issueKey: string;
34
+ requestDigest: string;
35
+ }): Promise<InboxCredentialType> => {
36
+ const result = await neogma.queryRunner.run(
37
+ `MATCH (issuer:Profile {profileId: $issuerProfileId})
38
+ MERGE (ic:InboxCredential {refreshIssueKey: $issueKey})
39
+ ON CREATE SET ic += $inbox, ic.refreshRequestDigest = $requestDigest
40
+ WITH issuer, ic WHERE ic.refreshRequestDigest = $requestDigest
41
+ MERGE (refresh:CredentialRefresh {refreshId: ic.refreshId})
42
+ ON CREATE SET refresh += $aggregate
43
+ MERGE (issuer)-[:ISSUED_REFRESH]->(refresh)
44
+ MERGE (refresh)-[:INBOX]->(ic)
45
+ MERGE (p:InboxRefreshPublication {refreshVersionKey: ic.refreshId + ':1'})
46
+ ON CREATE SET p.id = ic.refreshId, p.refreshId = ic.refreshId, p.version = 1,
47
+ p.publishedAt = ic.createdAt, p.signingMode = 'signing-authority'
48
+ RETURN ic`,
49
+ { ...params, issuerProfileId: params.aggregate.issuerProfileId }
50
+ );
51
+ const props = result.records[0]?.get('ic')?.properties;
52
+ if (!props)
53
+ throw new TRPCError({
54
+ code: 'CONFLICT',
55
+ message: 'This idempotencyKey was used for a different inbox issuance.',
56
+ });
57
+ return inflateObject<InboxCredentialType>(props);
58
+ };
59
+
60
+ /** Metadata only. Pending publication bodies live exclusively in the expiring inbox escrow. */
61
+ export const getPendingInboxPublication = async (
62
+ refreshId: string,
63
+ idempotencyKey?: string
64
+ ): Promise<PublishCredentialRefreshResult | null> => {
65
+ if (!idempotencyKey) return null;
66
+ const result = await neogma.queryRunner.run(
67
+ `MATCH (p:InboxRefreshPublication {refreshIdempotencyKey: $key}) RETURN p LIMIT 1`,
68
+ { key: `${refreshId}:${idempotencyKey}` }
69
+ );
70
+ const props = result.records[0]?.get('p')?.properties;
71
+ return props
72
+ ? {
73
+ refreshId,
74
+ version: Number(props.version),
75
+ publishedAt: props.publishedAt,
76
+ notification: 'not-applicable',
77
+ }
78
+ : null;
79
+ };
80
+
81
+ /** Publication and claim both lock the inbox row, then compare the version they prepared. */
82
+ export const advancePendingInboxRefresh = async (params: {
83
+ refreshId: string;
84
+ expectedVersion: number;
85
+ credential: string;
86
+ credentialName?: string;
87
+ materialDigest: string;
88
+ idempotencyKey?: string;
89
+ updateSummary?: string;
90
+ }): Promise<PublishCredentialRefreshResult | null> => {
91
+ const { refreshId, expectedVersion, idempotencyKey } = params;
92
+ const replay = await getPendingInboxPublication(refreshId, idempotencyKey);
93
+ if (replay) return replay;
94
+ const version = expectedVersion + 1;
95
+ const publishedAt = new Date().toISOString();
96
+ const result = await neogma.queryRunner.run(
97
+ `MATCH (refresh:CredentialRefresh {refreshId: $refreshId})-[:INBOX]->(ic:InboxCredential)
98
+ SET ic._escrowLock = true REMOVE ic._escrowLock
99
+ WITH refresh, ic
100
+ WHERE refresh.state = 'pending_holder' AND refresh.currentVersion = $expectedVersion
101
+ AND ic.currentStatus = 'PENDING' AND datetime(ic.expiresAt) > datetime()
102
+ AND ($key IS NULL OR NOT EXISTS { MATCH (:InboxRefreshPublication {refreshIdempotencyKey: $key}) })
103
+ CREATE (p:InboxRefreshPublication $publication)
104
+ SET ic.credential = $credential, ic.credentialName = $credentialName,
105
+ refresh.currentVersion = $version, refresh.materialDigest = $materialDigest,
106
+ refresh.credentialDisplayName = coalesce($credentialName, refresh.credentialDisplayName),
107
+ refresh.lastPublishedAt = $publishedAt, refresh.updatedAt = $publishedAt,
108
+ refresh.updateSummary = $updateSummary
109
+ RETURN p`,
110
+ {
111
+ ...params,
112
+ version,
113
+ publishedAt,
114
+ credentialName: params.credentialName ?? null,
115
+ updateSummary: params.updateSummary ?? null,
116
+ key: idempotencyKey ? `${refreshId}:${idempotencyKey}` : null,
117
+ publication: Object.fromEntries(
118
+ Object.entries({
119
+ id: randomUUID(),
120
+ refreshId,
121
+ version,
122
+ refreshVersionKey: `${refreshId}:${version}`,
123
+ publishedAt,
124
+ signingMode: 'signing-authority',
125
+ updateSummary: params.updateSummary,
126
+ refreshIdempotencyKey: idempotencyKey
127
+ ? `${refreshId}:${idempotencyKey}`
128
+ : undefined,
129
+ }).filter(([, value]) => value !== undefined)
130
+ ),
131
+ }
132
+ );
133
+ if (!result.records.length) return getPendingInboxPublication(refreshId, idempotencyKey);
134
+ return { refreshId, version, publishedAt, notification: 'not-applicable' };
135
+ };
136
+
137
+ /** Single commit: holder binding, canonical delivery, current version, and escrow erasure. */
138
+ export const bindPendingInboxRefresh = async (params: {
139
+ inboxId: string;
140
+ refreshId: string;
141
+ expectedVersion: number;
142
+ holderDid: string;
143
+ holderProfileId?: string;
144
+ issuerProfileId: string;
145
+ root: Record<string, unknown>;
146
+ aggregate: Record<string, unknown>;
147
+ deliveryCredential: string;
148
+ deliveryExpiresAt: string;
149
+ accepted: boolean;
150
+ boostId?: string;
151
+ }): Promise<boolean> => {
152
+ const now = new Date().toISOString();
153
+ const result = await neogma.queryRunner.run(
154
+ `MATCH (refresh:CredentialRefresh {refreshId: $refreshId})-[:INBOX]->(ic:InboxCredential {id: $inboxId})
155
+ SET ic._escrowLock = true REMOVE ic._escrowLock
156
+ WITH refresh, ic
157
+ WHERE refresh.state = 'pending_holder' AND refresh.currentVersion = $expectedVersion
158
+ AND ic.currentStatus = 'PENDING' AND datetime(ic.expiresAt) > datetime()
159
+ AND (ic.guardianStatus IS NULL OR ic.guardianStatus = 'GUARDIAN_APPROVED')
160
+ MATCH (issuer:Profile {profileId: $issuerProfileId})
161
+ OPTIONAL MATCH (holder:Profile {profileId: $holderProfileId})
162
+ OPTIONAL MATCH (boost:Boost {id: $boostId})
163
+ CREATE (root:Credential $root)
164
+ CREATE (refresh)-[:ROOT]->(root)
165
+ CREATE (refresh)-[:HEAD]->(root)
166
+ CREATE (issuer)-[:CREDENTIAL_SENT {to: $deliveryTarget, date: $now, activityId: ic.activityId, integrationId: ic.integrationId}]->(root)
167
+ FOREACH (_ IN CASE WHEN holder IS NULL THEN [] ELSE [1] END |
168
+ CREATE (holder)-[:HELD_REFRESH]->(refresh))
169
+ FOREACH (_ IN CASE WHEN holder IS NOT NULL AND $accepted THEN [1] ELSE [] END |
170
+ CREATE (root)-[:CREDENTIAL_RECEIVED {date: $now}]->(holder))
171
+ FOREACH (_ IN CASE WHEN boost IS NULL THEN [] ELSE [1] END |
172
+ CREATE (root)-[:INSTANCE_OF]->(boost))
173
+ SET refresh += $aggregate,
174
+ refresh.holderDid = $holderDid, refresh.holderProfileId = $holderProfileId,
175
+ refresh.state = CASE WHEN $accepted THEN 'active' ELSE 'awaiting_claim' END,
176
+ refresh.updatedAt = $now,
177
+ ic.currentStatus = 'ISSUED', ic.isAccepted = $accepted, ic.finalizedAt = $now,
178
+ ic.credential = null, ic.credentialName = null, ic.achievementType = null,
179
+ ic.deliveryCredential = $deliveryCredential, ic.deliveryRecipientDid = $holderDid,
180
+ ic.deliveryExpiresAt = $deliveryExpiresAt
181
+ RETURN root.id AS id`,
182
+ {
183
+ ...params,
184
+ now,
185
+ holderProfileId: params.holderProfileId ?? null,
186
+ boostId: params.boostId ?? null,
187
+ deliveryTarget: params.holderProfileId ?? params.holderDid,
188
+ }
189
+ );
190
+ return result.records.length > 0;
191
+ };
192
+
193
+ /** A read snapshot under the same inbox lock used by publish/claim, preventing mixed revisions. */
194
+ export const getInboxRefreshSnapshot = async (inboxId: string) => {
195
+ const result = await neogma.queryRunner.run(
196
+ `MATCH (refresh:CredentialRefresh)-[:INBOX]->(ic:InboxCredential {id: $inboxId})
197
+ SET ic._escrowLock = true REMOVE ic._escrowLock
198
+ RETURN ic, refresh LIMIT 1`,
199
+ { inboxId }
200
+ );
201
+ const row = result.records[0];
202
+ if (!row) return null;
203
+ const props = row.get('refresh').properties;
204
+ return {
205
+ inbox: inflateObject<InboxCredentialType>(row.get('ic').properties),
206
+ aggregate: CredentialRefreshRecordValidator.parse({
207
+ ...props,
208
+ currentVersion: Number(props.currentVersion),
209
+ }),
210
+ };
211
+ };
212
+
213
+ export const recordInboxRefreshClaimUrl = async (id: string, claimUrl: string): Promise<string> => {
214
+ const result = await neogma.queryRunner.run(
215
+ `MATCH (ic:InboxCredential {id: $id})
216
+ SET ic.refreshClaimUrl = coalesce(ic.refreshClaimUrl, $claimUrl)
217
+ RETURN ic.refreshClaimUrl AS claimUrl`,
218
+ { id, claimUrl }
219
+ );
220
+ return result.records[0]!.get('claimUrl');
221
+ };
@@ -10,7 +10,7 @@ import {
10
10
 
11
11
  export const updateInboxCredential = async (
12
12
  id: string,
13
- updates: Partial<Omit<InboxCredentialType, 'id' | 'createdAt' | 'credential'>>
13
+ updates: Partial<Omit<InboxCredentialType, 'id' | 'createdAt' | 'credential' | 'refresh'>>
14
14
  ): Promise<InboxCredentialType | null> => {
15
15
  const result = await new QueryBuilder(new BindParam({ id, updates: flattenObject(updates) }))
16
16
  .match({ model: InboxCredential, identifier: 'inboxCredential' })
@@ -14,6 +14,12 @@ import { PresentationType } from 'types/presentation';
14
14
  import { ProfileType } from 'types/profile';
15
15
  import { convertQueryResultToPropertiesObjectArray } from '@helpers/neo4j.helpers';
16
16
  import { getIdFromUri } from '@helpers/uri.helpers';
17
+ import { inflateObject } from '@helpers/objects.helpers';
18
+
19
+ const inflateRelationshipProperties = (
20
+ properties: Record<string, unknown>
21
+ ): Record<string, unknown> & { metadata?: Record<string, unknown> } =>
22
+ inflateObject(properties) as Record<string, unknown> & { metadata?: Record<string, unknown> };
17
23
 
18
24
  export const getPresentationById = async (id: string): Promise<PresentationInstance | null> => {
19
25
  return Presentation.findOne({ where: { id } });
@@ -56,8 +62,8 @@ export const getReceivedPresentationsForProfile = async (
56
62
  const query =
57
63
  from && from.length > 0
58
64
  ? matchQuery.where(
59
- new Where({ source: { profileId: { [Op.in]: from } } }, matchQuery.getBindParam())
60
- )
65
+ new Where({ source: { profileId: { [Op.in]: from } } }, matchQuery.getBindParam())
66
+ )
61
67
  : matchQuery;
62
68
 
63
69
  const results = convertQueryResultToPropertiesObjectArray<{
@@ -66,13 +72,22 @@ export const getReceivedPresentationsForProfile = async (
66
72
  received: PresentationRelationships['presentationReceived']['RelationshipProperties'];
67
73
  }>(await query.return('sent, presentation, received').limit(limit).run());
68
74
 
69
- return results.map(({ sent, presentation, received }) => ({
70
- uri: getPresentationUri(presentation.id, domain),
71
- to: sent.to,
72
- from: received.from,
73
- sent: sent.date,
74
- received: received.date,
75
- }));
75
+ return results.map(({ sent, presentation, received }) => {
76
+ const sentProps = inflateRelationshipProperties(sent as unknown as Record<string, unknown>);
77
+ const receivedProps = inflateRelationshipProperties(
78
+ received as unknown as Record<string, unknown>
79
+ );
80
+
81
+ return {
82
+ uri: getPresentationUri(presentation.id, domain),
83
+ to: sentProps.to as string,
84
+ from: receivedProps.from as string,
85
+ sent: sentProps.date as string,
86
+ received: receivedProps.date as string,
87
+ metadata: (receivedProps.metadata ?? sentProps.metadata) as
88
+ Record<string, unknown> | undefined,
89
+ };
90
+ });
76
91
  };
77
92
 
78
93
  export const getSentPresentationsForProfile = async (
@@ -101,8 +116,8 @@ export const getSentPresentationsForProfile = async (
101
116
  const whereQuery =
102
117
  to && to.length > 0
103
118
  ? matchQuery.where(
104
- new Where({ sent: { to: { [Op.in]: to } } }, matchQuery.getBindParam())
105
- )
119
+ new Where({ sent: { to: { [Op.in]: to } } }, matchQuery.getBindParam())
120
+ )
106
121
  : matchQuery;
107
122
 
108
123
  const query = whereQuery.match({
@@ -124,13 +139,22 @@ export const getSentPresentationsForProfile = async (
124
139
  received?: PresentationRelationships['presentationReceived']['RelationshipProperties'];
125
140
  }>(await query.return('source, sent, presentation, received').limit(limit).run());
126
141
 
127
- return results.map(({ source, sent, presentation, received }) => ({
128
- uri: getPresentationUri(presentation.id, domain),
129
- to: sent.to,
130
- from: source.profileId,
131
- sent: sent.date,
132
- received: received?.date,
133
- }));
142
+ return results.map(({ source, sent, presentation, received }) => {
143
+ const sentProps = inflateRelationshipProperties(sent as unknown as Record<string, unknown>);
144
+ const receivedProps = received
145
+ ? inflateRelationshipProperties(received as unknown as Record<string, unknown>)
146
+ : undefined;
147
+
148
+ return {
149
+ uri: getPresentationUri(presentation.id, domain),
150
+ to: sentProps.to as string,
151
+ from: source.profileId,
152
+ sent: sentProps.date as string,
153
+ received: receivedProps?.date as string | undefined,
154
+ metadata: (sentProps.metadata ?? receivedProps?.metadata) as
155
+ Record<string, unknown> | undefined,
156
+ };
157
+ });
134
158
  };
135
159
 
136
160
  export const getIncomingPresentationsForProfile = async (
@@ -168,7 +192,8 @@ export const getIncomingPresentationsForProfile = async (
168
192
  })
169
193
  // Don't return presentations that have been accepted
170
194
  .where(
171
- `NOT (presentation)-[:PRESENTATION_RECEIVED]->()${whereFrom ? `AND ${whereFrom.getStatement('text')}` : ''
195
+ `NOT (presentation)-[:PRESENTATION_RECEIVED]->()${
196
+ whereFrom ? `AND ${whereFrom.getStatement('text')}` : ''
172
197
  }`
173
198
  )
174
199
  .return('source, relationship, presentation')
@@ -176,10 +201,17 @@ export const getIncomingPresentationsForProfile = async (
176
201
  .run()
177
202
  );
178
203
 
179
- return results.map(({ source, relationship, presentation }) => ({
180
- uri: getPresentationUri(presentation.id, domain),
181
- to: relationship.to,
182
- from: source.profileId,
183
- sent: relationship.date,
184
- }));
204
+ return results.map(({ source, relationship, presentation }) => {
205
+ const relationshipProps = inflateRelationshipProperties(
206
+ relationship as unknown as Record<string, unknown>
207
+ );
208
+
209
+ return {
210
+ uri: getPresentationUri(presentation.id, domain),
211
+ to: relationshipProps.to as string,
212
+ from: source.profileId,
213
+ sent: relationshipProps.date as string,
214
+ metadata: relationshipProps.metadata,
215
+ };
216
+ });
185
217
  };
@@ -1,26 +1,69 @@
1
- import { PresentationInstance, Profile } from '@models';
1
+ import { BindParam, QueryBuilder } from 'neogma';
2
+
3
+ import { Presentation, PresentationInstance, Profile } from '@models';
4
+ import { flattenObject } from '@helpers/objects.helpers';
2
5
  import { ProfileType } from 'types/profile';
3
6
 
4
7
  export const createSentPresentationRelationship = async (
5
8
  from: ProfileType,
6
9
  to: ProfileType,
7
- presentation: PresentationInstance
10
+ presentation: PresentationInstance,
11
+ metadata?: Record<string, unknown>
8
12
  ): Promise<void> => {
9
- await Profile.relateTo({
10
- alias: 'presentationSent',
11
- where: { source: { profileId: from.profileId }, target: { id: presentation.id } },
12
- properties: { to: to.profileId, date: new Date().toISOString() },
13
+ const properties = flattenObject({
14
+ to: to.profileId,
15
+ date: new Date().toISOString(),
16
+ ...(metadata ? { metadata } : {}),
13
17
  });
18
+
19
+ await new QueryBuilder(new BindParam({ params: properties }))
20
+ .match({
21
+ related: [
22
+ { model: Profile, where: { profileId: from.profileId }, identifier: 'profile' },
23
+ ],
24
+ })
25
+ .match({
26
+ related: [
27
+ { model: Presentation, where: { id: presentation.id }, identifier: 'presentation' },
28
+ ],
29
+ })
30
+ .create(
31
+ `(profile)-[relationship:${
32
+ Profile.getRelationshipByAlias('presentationSent').name
33
+ }]->(presentation)`
34
+ )
35
+ .set('relationship = $params')
36
+ .run();
14
37
  };
15
38
 
16
39
  export const createReceivedPresentationRelationship = async (
17
40
  to: ProfileType,
18
41
  from: ProfileType,
19
- presentation: PresentationInstance
42
+ presentation: PresentationInstance,
43
+ metadata?: Record<string, unknown>
20
44
  ): Promise<void> => {
21
- await presentation.relateTo({
22
- alias: 'presentationReceived',
23
- where: { profileId: to.profileId },
24
- properties: { from: from.profileId, date: new Date().toISOString() },
45
+ const properties = flattenObject({
46
+ from: from.profileId,
47
+ date: new Date().toISOString(),
48
+ ...(metadata ? { metadata } : {}),
25
49
  });
50
+
51
+ await new QueryBuilder(new BindParam({ params: properties }))
52
+ .match({
53
+ related: [
54
+ { model: Presentation, where: { id: presentation.id }, identifier: 'presentation' },
55
+ ],
56
+ })
57
+ .match({
58
+ related: [
59
+ { model: Profile, where: { profileId: to.profileId }, identifier: 'profile' },
60
+ ],
61
+ })
62
+ .create(
63
+ `(presentation)-[relationship:${
64
+ Presentation.getRelationshipByAlias('presentationReceived').name
65
+ }]->(profile)`
66
+ )
67
+ .set('relationship = $params')
68
+ .run();
26
69
  };
@@ -2,15 +2,26 @@ import { inflateObject } from '@helpers/objects.helpers';
2
2
  import { Presentation, PresentationInstance, Profile, ProfileRelationships } from '@models';
3
3
  import { ProfileType } from 'types/profile';
4
4
 
5
+ const relationshipProperties = (value: unknown): Record<string, unknown> => {
6
+ if (value && typeof value === 'object' && 'dataValues' in value) {
7
+ const { dataValues } = value as { dataValues?: unknown };
8
+ if (dataValues && typeof dataValues === 'object') {
9
+ return dataValues as Record<string, unknown>;
10
+ }
11
+ }
12
+
13
+ return value as Record<string, unknown>;
14
+ };
15
+
5
16
  export const getPresentationSentToProfile = async (
6
17
  id: string,
7
18
  to: ProfileType
8
19
  ): Promise<
9
20
  | {
10
- source: ProfileType;
11
- relationship: ProfileRelationships['presentationSent']['RelationshipProperties'];
12
- target: PresentationInstance;
13
- }
21
+ source: ProfileType;
22
+ relationship: ProfileRelationships['presentationSent']['RelationshipProperties'];
23
+ target: PresentationInstance;
24
+ }
14
25
  | undefined
15
26
  > => {
16
27
  const data = (
@@ -22,7 +33,11 @@ export const getPresentationSentToProfile = async (
22
33
 
23
34
  if (!data) return undefined;
24
35
 
25
- return { ...data, source: inflateObject(data.source.dataValues as any) };
36
+ return {
37
+ ...data,
38
+ source: inflateObject(data.source.dataValues as unknown as Record<string, unknown>),
39
+ relationship: inflateObject(relationshipProperties(data.relationship)),
40
+ };
26
41
  };
27
42
 
28
43
  export const getPresentationOwner = async (
@@ -39,7 +54,7 @@ export const getPresentationOwner = async (
39
54
 
40
55
  if (!owner) return undefined;
41
56
 
42
- return inflateObject<ProfileType>(owner.dataValues as any);
57
+ return inflateObject(owner.dataValues as unknown as Record<string, unknown>) as ProfileType;
43
58
  };
44
59
 
45
60
  export const getPresentationReceivedByProfile = async (
@@ -0,0 +1,100 @@
1
+ import { ensureShareLinkConstraints } from '../../models/share-link-constraints';
2
+ import { failShareLink } from './errors';
3
+ import {
4
+ deleteReservation,
5
+ enqueueCleanupJob,
6
+ lockShare,
7
+ markOperationAbandoned,
8
+ readReservation,
9
+ toShareLinkRecord,
10
+ toShareLinkReservationRecord,
11
+ } from './helpers';
12
+ import { withShareLinkTransaction } from './transaction';
13
+ import type { AbandonReservationInput, AbandonReservationResult } from './types';
14
+
15
+ /**
16
+ * Irreversibly abandons a reservation.
17
+ *
18
+ * The operation transitions to cleanup-only: its exact immutable object is queued
19
+ * for tracked cleanup and the reservation is deleted, so a late finalize for the
20
+ * same operation is rejected. A stale worker can never abandon a newer reservation
21
+ * because the operation id (and, when supplied, generation/lease owner) must match.
22
+ *
23
+ * When the reservation is already gone but the share records this operation as the
24
+ * committed one, abandon reports `already_finalized` instead of pretending to have
25
+ * cleaned up live content.
26
+ */
27
+ export const abandonReservation = async (
28
+ input: AbandonReservationInput
29
+ ): Promise<AbandonReservationResult> => {
30
+ await ensureShareLinkConstraints();
31
+
32
+ const now = input.now ?? new Date();
33
+ const nowIso = now.toISOString();
34
+
35
+ return withShareLinkTransaction(async tx => {
36
+ const lockedProps = await lockShare(tx, input.shareId);
37
+
38
+ if (!lockedProps) {
39
+ failShareLink('NOT_FOUND', 'share not found');
40
+ }
41
+
42
+ const share = toShareLinkRecord(lockedProps);
43
+
44
+ if (share.namespace !== input.namespace || share.ownerProfileId !== input.ownerProfileId) {
45
+ failShareLink('NOT_FOUND', 'share not found');
46
+ }
47
+
48
+ const reservationProps = await readReservation(tx, input.shareId);
49
+
50
+ if (!reservationProps) {
51
+ if (share.lastOperationId === input.operationId) {
52
+ return { outcome: 'already_finalized', share };
53
+ }
54
+
55
+ return { outcome: 'already_absent' };
56
+ }
57
+
58
+ const reservation = toShareLinkReservationRecord(reservationProps);
59
+
60
+ if (reservation.operationId !== input.operationId) {
61
+ return { outcome: 'already_absent' };
62
+ }
63
+
64
+ if (input.generation !== undefined && reservation.generation !== input.generation) {
65
+ return { outcome: 'already_absent' };
66
+ }
67
+
68
+ if (input.leaseOwner !== undefined && reservation.leaseOwner !== input.leaseOwner) {
69
+ return { outcome: 'already_absent' };
70
+ }
71
+
72
+ let cleanupQueuedFor: string | null = null;
73
+
74
+ if (reservation.objectRef) {
75
+ await enqueueCleanupJob(tx, {
76
+ objectRef: reservation.objectRef,
77
+ operationId: reservation.operationId,
78
+ namespace: reservation.namespace,
79
+ ownerProfileId: reservation.ownerProfileId,
80
+ shareId: reservation.shareId,
81
+ contentVersion: reservation.contentVersion ?? reservation.baseContentVersion,
82
+ reason: 'abandoned',
83
+ now: nowIso,
84
+ });
85
+
86
+ cleanupQueuedFor = reservation.objectRef;
87
+ }
88
+
89
+ await markOperationAbandoned(tx, {
90
+ namespace: reservation.namespace,
91
+ ownerProfileId: reservation.ownerProfileId,
92
+ opKind: reservation.opKind,
93
+ clientRequestId: reservation.clientRequestId,
94
+ now: nowIso,
95
+ });
96
+ await deleteReservation(tx, input.shareId);
97
+
98
+ return { outcome: 'abandoned', cleanupQueuedFor };
99
+ });
100
+ };