@hraness/ghostget 0.17.2

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 (500) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1359 -0
  4. package/bunfig.toml +2 -0
  5. package/dist/apple-photos-client.js +704 -0
  6. package/dist/beeper-client.js +4602 -0
  7. package/dist/client.js +1605 -0
  8. package/dist/index-26yq8q16.js +31 -0
  9. package/dist/index-9t13b4k7.js +508 -0
  10. package/dist/index-axwm9b3v.js +5 -0
  11. package/dist/index-gwk7rbyj.js +30 -0
  12. package/dist/index.js +701 -0
  13. package/dist/messaging.js +1130 -0
  14. package/dist/omni-client.js +1406 -0
  15. package/dist/whatsapp-client.js +355 -0
  16. package/docs/imessage-direct-provider.md +106 -0
  17. package/docs/rental-listings.md +94 -0
  18. package/package.json +600 -0
  19. package/skills/ghostget/SKILL.md +251 -0
  20. package/skills/ghostget/agents/openai.yaml +4 -0
  21. package/skills/ghostget/references/adapter-contract.md +181 -0
  22. package/skills/ghostget/references/apple-photos.md +115 -0
  23. package/skills/ghostget/references/article-drafts.md +235 -0
  24. package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
  25. package/skills/ghostget/references/cross-posting-video.md +139 -0
  26. package/skills/ghostget/references/cross-posting.md +82 -0
  27. package/skills/ghostget/references/derivation.md +390 -0
  28. package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
  29. package/skills/ghostget/references/install.md +66 -0
  30. package/skills/ghostget/references/linkedin-adapter.md +442 -0
  31. package/skills/ghostget/references/messaging.md +168 -0
  32. package/skills/ghostget/references/meta-comet-contract.md +153 -0
  33. package/skills/ghostget/references/platform-patterns.md +122 -0
  34. package/skills/ghostget/references/provider-plugins.md +496 -0
  35. package/skills/ghostget/references/reddit-flair.md +78 -0
  36. package/skills/ghostget/references/safety-and-state.md +214 -0
  37. package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
  38. package/skills/ghostget/references/social-platform-routing.md +66 -0
  39. package/skills/ghostget/references/social-profile-stats.md +195 -0
  40. package/skills/ghostget/references/social-video-platform-routing.md +89 -0
  41. package/skills/ghostget/references/whatsapp-adapter.md +101 -0
  42. package/skills/ghostget/references/x-adapter.md +332 -0
  43. package/skills/ghostget/references/x-ai-disclosure.md +71 -0
  44. package/src/apple-photos-cli.ts +639 -0
  45. package/src/apple-photos-client-types.ts +168 -0
  46. package/src/apple-photos-client.ts +201 -0
  47. package/src/apple-photos-contact-evidence.ts +838 -0
  48. package/src/apple-photos-local-source.ts +1131 -0
  49. package/src/args.ts +2053 -0
  50. package/src/article-draft-document.ts +417 -0
  51. package/src/article-draft-embeds.ts +123 -0
  52. package/src/article-draft-images.ts +214 -0
  53. package/src/assets/adapter-template/wrench-adapter.json +40 -0
  54. package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
  55. package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
  56. package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
  57. package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
  58. package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
  59. package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
  60. package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
  61. package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
  62. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
  63. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
  64. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
  65. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
  66. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
  67. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
  68. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
  69. package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
  70. package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
  71. package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
  72. package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
  73. package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
  74. package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
  75. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
  76. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
  77. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
  78. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
  79. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
  80. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
  81. package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
  82. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
  83. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
  84. package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
  85. package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
  86. package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
  87. package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
  88. package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
  89. package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
  90. package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
  91. package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
  92. package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
  93. package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
  94. package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
  95. package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
  96. package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
  97. package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
  98. package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
  99. package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
  100. package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
  101. package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
  102. package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
  103. package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
  104. package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
  105. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
  106. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
  107. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
  108. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
  109. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
  110. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
  111. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
  112. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
  113. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
  114. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
  115. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
  116. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
  117. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
  118. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
  119. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
  120. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
  121. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
  122. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
  123. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
  124. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
  125. package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
  126. package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
  127. package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
  128. package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
  129. package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
  130. package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
  131. package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
  132. package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
  133. package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
  134. package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
  135. package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
  136. package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
  137. package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
  138. package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
  139. package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
  140. package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
  141. package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
  142. package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
  143. package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
  144. package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
  145. package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
  146. package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
  147. package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
  148. package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
  149. package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
  150. package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
  151. package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
  152. package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
  153. package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
  154. package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
  155. package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
  156. package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
  157. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
  158. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
  159. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
  160. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
  161. package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
  162. package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
  163. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
  164. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
  165. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
  166. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
  167. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
  168. package/src/assets/adapters/x/wrench-adapter.json +749 -0
  169. package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
  170. package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
  171. package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
  172. package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
  173. package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
  174. package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
  175. package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
  176. package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
  177. package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
  178. package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
  179. package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
  180. package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
  181. package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
  182. package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
  183. package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
  184. package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
  185. package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
  186. package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
  187. package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
  188. package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
  189. package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
  190. package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
  191. package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
  192. package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
  193. package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
  194. package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
  195. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  196. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  197. package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
  198. package/src/assets.d.ts +4 -0
  199. package/src/auth.ts +1157 -0
  200. package/src/beeper-client-types.ts +169 -0
  201. package/src/beeper-client.ts +250 -0
  202. package/src/beeper-contact-interactions-cli.ts +180 -0
  203. package/src/beeper-contact-interactions.ts +1612 -0
  204. package/src/beeper-message-bundle-v1.ts +257 -0
  205. package/src/beeper-message-like-me-cli.ts +84 -0
  206. package/src/beeper-message-like-me-export.ts +2274 -0
  207. package/src/beeper-message-like-me-recovery.ts +931 -0
  208. package/src/beeper-message-like-me-source.ts +4034 -0
  209. package/src/browser-admission.ts +928 -0
  210. package/src/browser-snapshots.ts +260 -0
  211. package/src/browser.ts +3873 -0
  212. package/src/canonical-json.ts +60 -0
  213. package/src/catalog-cli.ts +638 -0
  214. package/src/cli.ts +208 -0
  215. package/src/client-types.ts +301 -0
  216. package/src/client.ts +2726 -0
  217. package/src/confirmed-write-failure.ts +44 -0
  218. package/src/confirmed-write-model.ts +72 -0
  219. package/src/confirmed-write-platform.ts +1055 -0
  220. package/src/confirmed-write-program.ts +224 -0
  221. package/src/confirmed-write-runtime.ts +16 -0
  222. package/src/cursor-token.ts +228 -0
  223. package/src/derivation-dnr-readiness.ts +367 -0
  224. package/src/derivation-file-chooser.ts +384 -0
  225. package/src/derivation-network-boundary.ts +835 -0
  226. package/src/derivation-network-guard.ts +782 -0
  227. package/src/derivation-network-proxy-helper.ts +440 -0
  228. package/src/derivation-network-proxy.ts +385 -0
  229. package/src/derive-command-helper.ts +693 -0
  230. package/src/derive-fixtures.ts +343 -0
  231. package/src/derive-review.ts +980 -0
  232. package/src/derive.ts +4754 -0
  233. package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
  234. package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
  235. package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
  236. package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
  237. package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
  238. package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
  239. package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
  240. package/src/ghostget.ts +3672 -0
  241. package/src/gmail-capture.ts +1244 -0
  242. package/src/har-internal.ts +1242 -0
  243. package/src/har.ts +743 -0
  244. package/src/index.ts +218 -0
  245. package/src/invocation-read-platform.ts +55 -0
  246. package/src/invocation-read-program.ts +93 -0
  247. package/src/invocation-read-runtime.ts +11 -0
  248. package/src/linked-device-lifecycle-admission.ts +450 -0
  249. package/src/linked-device-lifecycle-journal.ts +1560 -0
  250. package/src/linked-device-lifecycle-runtime.ts +1602 -0
  251. package/src/local-cli-admission.ts +135 -0
  252. package/src/local-cli-contracts.ts +271 -0
  253. package/src/local-cli-execution.ts +98 -0
  254. package/src/local-cli-surface-contract.ts +1510 -0
  255. package/src/local-cli-tool-identity.ts +336 -0
  256. package/src/media/archive.ts +2325 -0
  257. package/src/media/args.ts +303 -0
  258. package/src/media/cli.ts +287 -0
  259. package/src/media/doctor.ts +475 -0
  260. package/src/media/ffmpeg.ts +1330 -0
  261. package/src/media/http-capture.ts +711 -0
  262. package/src/media/http-probe.ts +593 -0
  263. package/src/media/http.ts +387 -0
  264. package/src/media/index.ts +10 -0
  265. package/src/media/local-transcription.ts +613 -0
  266. package/src/media/lock.ts +321 -0
  267. package/src/media/manifest.ts +1712 -0
  268. package/src/media/metadata.ts +906 -0
  269. package/src/media/process.ts +723 -0
  270. package/src/media/revision.ts +166 -0
  271. package/src/media/runtime-closure.ts +1063 -0
  272. package/src/media/source-router.ts +187 -0
  273. package/src/media/transcriber-config.ts +1122 -0
  274. package/src/media/transcript-persistence-model.ts +15 -0
  275. package/src/media/transcript-persistence-platform.ts +89 -0
  276. package/src/media/transcript-persistence-program.ts +39 -0
  277. package/src/media/transcript-persistence-runtime.ts +24 -0
  278. package/src/media/transcript.ts +616 -0
  279. package/src/media/utf8-order.ts +30 -0
  280. package/src/media/whisper-cpp.ts +464 -0
  281. package/src/media/whisper-language.ts +58 -0
  282. package/src/media/yt-dlp.ts +439 -0
  283. package/src/message-like-me-agentic-messaging.ts +882 -0
  284. package/src/messaging-action-store.ts +793 -0
  285. package/src/messaging-runtime.ts +2136 -0
  286. package/src/messaging-store.ts +539 -0
  287. package/src/messaging-types.ts +1663 -0
  288. package/src/messaging.ts +334 -0
  289. package/src/model.ts +2671 -0
  290. package/src/oauth-google.ts +888 -0
  291. package/src/omni-client-types.ts +306 -0
  292. package/src/omni-client.ts +2225 -0
  293. package/src/omni-limits.ts +34 -0
  294. package/src/omni-model.ts +1614 -0
  295. package/src/omni-request.ts +379 -0
  296. package/src/omni-runtime.ts +1632 -0
  297. package/src/operation-deadline.ts +189 -0
  298. package/src/path-helper.ts +1656 -0
  299. package/src/pinned-https.ts +194 -0
  300. package/src/plan-assets.ts +832 -0
  301. package/src/platform-catalog.ts +1832 -0
  302. package/src/plugins/beeper-linked-device/plugin.ts +310 -0
  303. package/src/plugins/bluesky-web/plugin.ts +178 -0
  304. package/src/plugins/clasificados-web/plugin.ts +66 -0
  305. package/src/plugins/github-web/plugin.ts +80 -0
  306. package/src/plugins/gmail-official/plugin.ts +110 -0
  307. package/src/plugins/hacker-news-web/plugin.ts +52 -0
  308. package/src/plugins/imessage-direct/plugin.ts +195 -0
  309. package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
  310. package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
  311. package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
  312. package/src/plugins/linkedin-official/plugin.ts +84 -0
  313. package/src/plugins/linkedin-web/plugin.ts +412 -0
  314. package/src/plugins/meta-web/plugin.ts +317 -0
  315. package/src/plugins/reddit-web/flair.ts +492 -0
  316. package/src/plugins/reddit-web/plugin.ts +477 -0
  317. package/src/plugins/substack-web/plugin.ts +124 -0
  318. package/src/plugins/tiktok-web/plugin.ts +67 -0
  319. package/src/plugins/twitch-web/plugin.ts +54 -0
  320. package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
  321. package/src/plugins/x-official/plugin.ts +64 -0
  322. package/src/plugins/x-web/plugin.ts +488 -0
  323. package/src/plugins/youtube-web/plugin.ts +101 -0
  324. package/src/portable-run-recovery.ts +466 -0
  325. package/src/process-identity.ts +501 -0
  326. package/src/profile-clone-helper.ts +104 -0
  327. package/src/provider-catalog-views.ts +51 -0
  328. package/src/provider-context.ts +69 -0
  329. package/src/provider-contract-definitions-x.ts +45 -0
  330. package/src/provider-contract-definitions.ts +948 -0
  331. package/src/provider-contract-input-gmail.ts +176 -0
  332. package/src/provider-contract-input-linkedin.ts +77 -0
  333. package/src/provider-contract-input-x.ts +304 -0
  334. package/src/provider-contract-planning.ts +23 -0
  335. package/src/provider-contract-semantic-identity.ts +54 -0
  336. package/src/provider-contracts.ts +251 -0
  337. package/src/provider-http.ts +723 -0
  338. package/src/provider-plugin-auth.ts +36 -0
  339. package/src/provider-plugin-builtins.ts +229 -0
  340. package/src/provider-plugin-cleanup-barrier.ts +229 -0
  341. package/src/provider-plugin-cleanup-execution.ts +79 -0
  342. package/src/provider-plugin-cleanup-resource.ts +470 -0
  343. package/src/provider-plugin-contract-identity.ts +707 -0
  344. package/src/provider-plugin-host.ts +1972 -0
  345. package/src/provider-plugin-identifiers.ts +60 -0
  346. package/src/provider-plugin-invocation-lease.ts +889 -0
  347. package/src/provider-plugin-lifecycle-kernel.ts +566 -0
  348. package/src/provider-plugin-lifecycle.ts +1455 -0
  349. package/src/provider-plugin-module-analysis.ts +336 -0
  350. package/src/provider-plugin-package.ts +2530 -0
  351. package/src/provider-plugin-portable-authority.ts +106 -0
  352. package/src/provider-plugin-portable-catalog.ts +94 -0
  353. package/src/provider-plugin-portable-identity.ts +315 -0
  354. package/src/provider-plugin-portable-runtime.ts +2057 -0
  355. package/src/provider-plugin-protocol.ts +1625 -0
  356. package/src/provider-plugin-registry.ts +3342 -0
  357. package/src/provider-plugin-store.ts +2155 -0
  358. package/src/provider-plugin-test-fixture.ts +5 -0
  359. package/src/provider-plugin.ts +4907 -0
  360. package/src/provider-plugins.generated.ts +43 -0
  361. package/src/provider-plugins.ts +8 -0
  362. package/src/provider-subject.ts +41 -0
  363. package/src/provider.ts +315 -0
  364. package/src/providers/beeper-local-runtime.ts +5426 -0
  365. package/src/providers/beeper-local.ts +2739 -0
  366. package/src/providers/beeper-messaging.ts +462 -0
  367. package/src/providers/beeper-omni.ts +1159 -0
  368. package/src/providers/bluesky-web-runtime.ts +2895 -0
  369. package/src/providers/bluesky-web.ts +1614 -0
  370. package/src/providers/clasificados-web-runtime.ts +255 -0
  371. package/src/providers/clasificados-web.ts +503 -0
  372. package/src/providers/contact-projection.ts +301 -0
  373. package/src/providers/github-read-model.ts +94 -0
  374. package/src/providers/github-read-platform.ts +165 -0
  375. package/src/providers/github-read-program.ts +97 -0
  376. package/src/providers/github-web-runtime.ts +320 -0
  377. package/src/providers/github-web.ts +403 -0
  378. package/src/providers/gmail-api.ts +2834 -0
  379. package/src/providers/gmail-omni.ts +830 -0
  380. package/src/providers/gmail.ts +1130 -0
  381. package/src/providers/hacker-news-web-runtime.ts +220 -0
  382. package/src/providers/hacker-news-web.ts +809 -0
  383. package/src/providers/imessage-direct-install.ts +354 -0
  384. package/src/providers/imessage-direct-messaging.ts +532 -0
  385. package/src/providers/imessage-direct-omni.ts +400 -0
  386. package/src/providers/imessage-direct-runtime.ts +1498 -0
  387. package/src/providers/imessage-direct.ts +351 -0
  388. package/src/providers/instagram-video-foundations.ts +913 -0
  389. package/src/providers/instagram-web-profile-browser.ts +600 -0
  390. package/src/providers/iso-bmff.ts +338 -0
  391. package/src/providers/linkedin-company-platform.ts +42 -0
  392. package/src/providers/linkedin-company-program.ts +88 -0
  393. package/src/providers/linkedin-contact-failure.ts +84 -0
  394. package/src/providers/linkedin-contact-platform.ts +45 -0
  395. package/src/providers/linkedin-contact-program.ts +91 -0
  396. package/src/providers/linkedin-profile-activity-failure.ts +76 -0
  397. package/src/providers/linkedin-profile-activity-platform.ts +41 -0
  398. package/src/providers/linkedin-profile-activity-program.ts +67 -0
  399. package/src/providers/linkedin-read-failure.ts +52 -0
  400. package/src/providers/linkedin-self-platform.ts +53 -0
  401. package/src/providers/linkedin-self-program.ts +122 -0
  402. package/src/providers/linkedin-web-article-browser.ts +794 -0
  403. package/src/providers/linkedin-web-bootstrap.ts +146 -0
  404. package/src/providers/linkedin-web-contact.ts +830 -0
  405. package/src/providers/linkedin-web-feed-browser.ts +832 -0
  406. package/src/providers/linkedin-web-feed.ts +864 -0
  407. package/src/providers/linkedin-web-post-browser.ts +653 -0
  408. package/src/providers/linkedin-web-profile-browser.ts +683 -0
  409. package/src/providers/linkedin-web-runtime.ts +2388 -0
  410. package/src/providers/linkedin-web.ts +3305 -0
  411. package/src/providers/linkedin.ts +1885 -0
  412. package/src/providers/meta-bootstrap.ts +1043 -0
  413. package/src/providers/meta-facebook-group.ts +690 -0
  414. package/src/providers/meta-marketplace-relay.ts +747 -0
  415. package/src/providers/meta-omni.ts +373 -0
  416. package/src/providers/meta-relay-bundle.ts +1049 -0
  417. package/src/providers/meta-web-descriptors.ts +1740 -0
  418. package/src/providers/meta-web-runtime.ts +3388 -0
  419. package/src/providers/meta-web.ts +2931 -0
  420. package/src/providers/read-failure.ts +139 -0
  421. package/src/providers/reddit-omni.ts +462 -0
  422. package/src/providers/reddit-web-runtime.ts +1929 -0
  423. package/src/providers/reddit-web.ts +1611 -0
  424. package/src/providers/rental-listings.ts +460 -0
  425. package/src/providers/substack-omni.ts +406 -0
  426. package/src/providers/substack-video-mp4.ts +33 -0
  427. package/src/providers/substack-web-runtime.ts +2812 -0
  428. package/src/providers/substack-web.ts +1502 -0
  429. package/src/providers/tiktok-video-mp4.ts +33 -0
  430. package/src/providers/tiktok-web-runtime.ts +770 -0
  431. package/src/providers/tiktok-web.ts +2786 -0
  432. package/src/providers/twitch-web-runtime.ts +226 -0
  433. package/src/providers/twitch-web.ts +274 -0
  434. package/src/providers/whatsapp-account-identity.ts +55 -0
  435. package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
  436. package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
  437. package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
  438. package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
  439. package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
  440. package/src/providers/whatsapp-messaging.ts +132 -0
  441. package/src/providers/whatsapp-omni.ts +586 -0
  442. package/src/providers/whatsapp-web-runtime.ts +3108 -0
  443. package/src/providers/whatsapp-web.ts +1037 -0
  444. package/src/providers/x-image-provenance.ts +523 -0
  445. package/src/providers/x-made-with-ai.ts +106 -0
  446. package/src/providers/x-transaction-id.ts +413 -0
  447. package/src/providers/x-web-runtime.ts +3485 -0
  448. package/src/providers/x-web.ts +2332 -0
  449. package/src/providers/x.ts +1889 -0
  450. package/src/providers/youtube-web-runtime.ts +1400 -0
  451. package/src/providers/youtube-web.ts +1221 -0
  452. package/src/read-admission-runtime.ts +91 -0
  453. package/src/read-client.ts +521 -0
  454. package/src/read-effect-platform.ts +7 -0
  455. package/src/read-effect-runtime.ts +14 -0
  456. package/src/read-effect.ts +52 -0
  457. package/src/read-projection-admission.ts +866 -0
  458. package/src/read-projections.ts +3151 -0
  459. package/src/recovery.ts +1492 -0
  460. package/src/reviewed-template.ts +146 -0
  461. package/src/run-journal.ts +1272 -0
  462. package/src/runtime.ts +5116 -0
  463. package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
  464. package/src/scripts/install-whatsapp-protocol.sh +232 -0
  465. package/src/scripts/recover-install-stages.ts +1491 -0
  466. package/src/scripts/resolve-state-home.ts +30 -0
  467. package/src/scripts/scaffold-web-provider.ts +973 -0
  468. package/src/scripts/sync-bundled-adapters.ts +695 -0
  469. package/src/session-secrets.ts +1183 -0
  470. package/src/state-helper.bunfig.toml +2 -0
  471. package/src/state-helper.ts +2914 -0
  472. package/src/storage.ts +3842 -0
  473. package/src/transport-policy.ts +2 -0
  474. package/src/usage.ts +182 -0
  475. package/src/version.ts +2 -0
  476. package/src/web-session-authentication-policy.ts +136 -0
  477. package/src/web-session-cleanup-admission.ts +2344 -0
  478. package/src/web-session-cleanup-join.ts +99 -0
  479. package/src/web-session-client.ts +996 -0
  480. package/src/web-session-contract-definitions.ts +820 -0
  481. package/src/web-session-contract-planning.ts +42 -0
  482. package/src/web-session-contracts.ts +228 -0
  483. package/src/web-session-cookies.ts +500 -0
  484. package/src/web-session-execution.ts +385 -0
  485. package/src/web-session-http.ts +373 -0
  486. package/src/web-session-read-errors.ts +19 -0
  487. package/src/web-session-read-runtime.ts +124 -0
  488. package/src/web-session-recovery.ts +818 -0
  489. package/src/web-session-sites.ts +58 -0
  490. package/src/web-session-template.ts +994 -0
  491. package/src/web-session.ts +93 -0
  492. package/src/whatsapp-client-binding.ts +21 -0
  493. package/src/whatsapp-client-types.ts +75 -0
  494. package/src/whatsapp-client.ts +395 -0
  495. package/src/whatsapp-export-coordinate.ts +17 -0
  496. package/src/whatsapp-message-bundle-v2.ts +195 -0
  497. package/src/whatsapp-message-like-me-cli.ts +195 -0
  498. package/src/whatsapp-message-like-me-export.ts +1072 -0
  499. package/src/whatsapp-message-like-me-source.ts +1012 -0
  500. package/tsconfig.json +67 -0
@@ -0,0 +1,2225 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { types as nodeTypes } from "node:util";
5
+
6
+ import { canonicalJson, sha256 } from "./canonical-json";
7
+ import {
8
+ boundedOmniText,
9
+ OMNI_MAX_CURSOR_CHARACTERS as MAX_CURSOR_CHARACTERS,
10
+ OMNI_MAX_REASON_BYTES as MAX_ERROR_BYTES,
11
+ OMNI_MAX_REQUEST_BYTES as MAX_REQUEST_BYTES,
12
+ OMNI_MAX_RESPONSE_BYTES as MAX_OUTPUT_BYTES,
13
+ OMNI_MAX_SOURCES as MAX_SOURCES,
14
+ OMNI_MAX_VIEW_ENTITIES as MAX_VIEW_ENTITIES,
15
+ } from "./omni-limits";
16
+ import type {
17
+ OmniAttachmentV1,
18
+ OmniConversationV1,
19
+ OmniCoverageSourceStatusV1,
20
+ OmniEntityKindV1,
21
+ OmniEntityV1,
22
+ OmniExactFreshnessV1,
23
+ OmniExactSourceStatusV1,
24
+ OmniMessageV1,
25
+ OmniNormalizationSourceStatusV1,
26
+ OmniNotificationV1,
27
+ OmniParticipantV1,
28
+ OmniViewCacheResult,
29
+ OmniViewIdentity,
30
+ OmniViewLiveResult,
31
+ OmniViewMergedResult,
32
+ OmniViewRequest,
33
+ OmniViewSourceRequest,
34
+ OmniViewSourceStatusV1,
35
+ OmniViewV1,
36
+ ReadOmniViewOptions,
37
+ RevalidatedOmniView,
38
+ RevalidatedOmniViewCurrent,
39
+ RevalidateOmniViewOptions,
40
+ } from "./omni-client-types";
41
+
42
+ type JsonRecord = Record<string, unknown>;
43
+ type JsonSnapshot =
44
+ | null
45
+ | boolean
46
+ | number
47
+ | string
48
+ | readonly JsonSnapshot[]
49
+ | { readonly [key: string]: JsonSnapshot };
50
+
51
+ const MAX_JSON_DEPTH = 64;
52
+ const MAX_JSON_NODES = 100_000;
53
+ const MAX_PARTICIPANTS = 256;
54
+ const MAX_ATTACHMENTS = 64;
55
+ const SYNCHRONOUS_COMMAND_TIMEOUT_MS = 20_000;
56
+ const ASYNC_CONTROL_COMMAND_TIMEOUT_MS = 20_000;
57
+ const CHILD_GRACEFUL_TERMINATION_MS = 40_000;
58
+ const CHILD_FORCEFUL_TERMINATION_MS = 5_000;
59
+ const abortSignalAbortedGetter = (() => {
60
+ const descriptor = Object.getOwnPropertyDescriptor(
61
+ AbortSignal.prototype,
62
+ "aborted",
63
+ );
64
+ const getter = descriptor === undefined
65
+ ? undefined
66
+ : Reflect.get(descriptor, "get") as unknown;
67
+ return typeof getter !== "function"
68
+ ? undefined
69
+ : (value: unknown): unknown => Reflect.apply(getter, value, []);
70
+ })();
71
+
72
+ type PreparedRequest = {
73
+ readonly json: string;
74
+ readonly invocationDigest: string;
75
+ readonly requestDigest: string;
76
+ readonly sources: readonly {
77
+ readonly adapterId: string;
78
+ readonly operationId: "messaging.list" | "messaging.read";
79
+ readonly authId: string;
80
+ readonly requestInputHash: string;
81
+ }[];
82
+ readonly filter: {
83
+ readonly kinds: readonly OmniEntityV1["kind"][] | null;
84
+ readonly conversationId: string | null;
85
+ readonly unread: boolean | null;
86
+ };
87
+ readonly pageLimit: number;
88
+ };
89
+
90
+ type PreparedOptions = {
91
+ readonly cwd: string;
92
+ readonly environment: Readonly<Record<string, string>>;
93
+ readonly headed: boolean;
94
+ readonly signal?: AbortSignal;
95
+ };
96
+
97
+ type CommandMode = "cache" | "identity" | "live";
98
+
99
+ type PreparedCommand = {
100
+ readonly arguments: readonly string[];
101
+ readonly input: string;
102
+ readonly request: PreparedRequest;
103
+ };
104
+
105
+ type ParsedEnvelope =
106
+ | {
107
+ readonly source: "omni-identity";
108
+ readonly identity: OmniViewIdentity;
109
+ readonly view: null;
110
+ }
111
+ | {
112
+ readonly source: "omni-cache";
113
+ readonly identity: OmniViewIdentity;
114
+ readonly view: OmniViewV1;
115
+ }
116
+ | {
117
+ readonly source: "omni-live";
118
+ readonly identity: OmniViewIdentity;
119
+ readonly view: OmniViewV1;
120
+ }
121
+ | {
122
+ readonly source: "omni-exact-cache";
123
+ readonly identity: OmniViewIdentity;
124
+ readonly view: OmniViewV1;
125
+ };
126
+
127
+ type CachedBeforeSource =
128
+ | { readonly status: "provided"; readonly value: OmniViewCacheResult | null }
129
+ | { readonly status: "lookup" };
130
+
131
+ function fail(label: string, message: string): never {
132
+ throw new Error(`${label} ${message}`);
133
+ }
134
+
135
+ function record(value: unknown, label: string): JsonRecord {
136
+ if (
137
+ nodeTypes.isProxy(value)
138
+ || typeof value !== "object"
139
+ || value === null
140
+ || Array.isArray(value)
141
+ || (
142
+ Object.getPrototypeOf(value) !== Object.prototype
143
+ && Object.getPrototypeOf(value) !== null
144
+ )
145
+ ) return fail(label, "must be a plain non-proxy object");
146
+ const descriptors = Object.getOwnPropertyDescriptors(value);
147
+ const keys = Reflect.ownKeys(descriptors);
148
+ if (keys.some((key) => typeof key !== "string")) {
149
+ return fail(label, "must not contain symbol properties");
150
+ }
151
+ for (const key of keys as string[]) {
152
+ const descriptor = descriptors[key];
153
+ if (
154
+ descriptor === undefined
155
+ || !descriptor.enumerable
156
+ || !("value" in descriptor)
157
+ ) return fail(`${label}.${key}`, "must be an enumerable data property");
158
+ }
159
+ return value as JsonRecord;
160
+ }
161
+
162
+ function exactKeys(
163
+ value: JsonRecord,
164
+ required: readonly string[],
165
+ optional: readonly string[],
166
+ label: string,
167
+ ): void {
168
+ const actual = Object.keys(value);
169
+ const allowed = new Set([...required, ...optional]);
170
+ const unknown = actual.find((key) => !allowed.has(key));
171
+ if (unknown !== undefined) return fail(`${label}.${unknown}`, "is not reviewed");
172
+ const missing = required.find((key) => !Object.hasOwn(value, key));
173
+ if (missing !== undefined) return fail(`${label}.${missing}`, "is required");
174
+ }
175
+
176
+ function denseArray(
177
+ value: unknown,
178
+ label: string,
179
+ maximum: number,
180
+ ): readonly unknown[] {
181
+ if (
182
+ nodeTypes.isProxy(value)
183
+ || !Array.isArray(value)
184
+ || Object.getPrototypeOf(value) !== Array.prototype
185
+ || value.length > maximum
186
+ ) return fail(label, `must be a plain array of at most ${maximum} items`);
187
+ const descriptors = Object.getOwnPropertyDescriptors(value);
188
+ const keys = Reflect.ownKeys(descriptors);
189
+ const expected = new Set<PropertyKey>([
190
+ "length",
191
+ ...Array.from({ length: value.length }, (_unused, index) => String(index)),
192
+ ]);
193
+ if (keys.length !== expected.size || keys.some((key) => !expected.has(key))) {
194
+ return fail(label, "must be a dense array without named properties");
195
+ }
196
+ for (let index = 0; index < value.length; index += 1) {
197
+ const descriptor = descriptors[String(index)];
198
+ if (
199
+ descriptor === undefined
200
+ || !descriptor.enumerable
201
+ || !("value" in descriptor)
202
+ ) return fail(`${label}[${index}]`, "must be an enumerable data property");
203
+ }
204
+ return value;
205
+ }
206
+
207
+ function text(
208
+ value: unknown,
209
+ label: string,
210
+ maximumBytes: number,
211
+ allowEmpty = false,
212
+ ): string {
213
+ if (
214
+ typeof value !== "string"
215
+ || Buffer.byteLength(value, "utf8") > maximumBytes
216
+ || (!allowEmpty && value.length === 0)
217
+ || [...value].some((character) => {
218
+ const code = character.codePointAt(0) ?? -1;
219
+ return code === 0
220
+ || code === 8
221
+ || code === 11
222
+ || code === 12
223
+ || (code >= 14 && code <= 31)
224
+ || code === 127;
225
+ })
226
+ ) return fail(label, "must be bounded text");
227
+ return value;
228
+ }
229
+
230
+ function nullableText(
231
+ value: unknown,
232
+ label: string,
233
+ maximumBytes: number,
234
+ allowEmpty = false,
235
+ ): string | null {
236
+ return value === null
237
+ ? null
238
+ : text(value, label, maximumBytes, allowEmpty);
239
+ }
240
+
241
+ function identifier(value: unknown, label: string): string {
242
+ const result = text(value, label, 64);
243
+ if (!/^[a-z][a-z0-9-]{0,63}$/u.test(result)) {
244
+ return fail(label, "must be lowercase kebab-case");
245
+ }
246
+ return result;
247
+ }
248
+
249
+ function digest(value: unknown, label: string): string {
250
+ const result = text(value, label, 64);
251
+ if (!/^[a-f0-9]{64}$/u.test(result)) return fail(label, "must be a SHA-256 digest");
252
+ return result;
253
+ }
254
+
255
+ function localCursor(value: unknown, label: string): string {
256
+ const result = text(value, label, MAX_CURSOR_CHARACTERS);
257
+ if (!result.startsWith("smn1.")) {
258
+ return fail(label, "must be an authenticated local omni cursor");
259
+ }
260
+ const encoded = result.slice("smn1.".length);
261
+ if (encoded === "" || /[^A-Za-z0-9_-]/u.test(encoded)) {
262
+ return fail(label, "must be an authenticated local omni cursor");
263
+ }
264
+ const envelope = Buffer.from(encoded, "base64url");
265
+ if (
266
+ envelope.toString("base64url") !== encoded
267
+ || envelope.byteLength <= 12 + 16
268
+ ) {
269
+ return fail(label, "must be an authenticated local omni cursor");
270
+ }
271
+ return result;
272
+ }
273
+
274
+ function timestamp(value: unknown, label: string): string {
275
+ const result = text(value, label, 64);
276
+ const parsed = new Date(result);
277
+ if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== result) {
278
+ return fail(label, "must be a canonical ISO timestamp");
279
+ }
280
+ return result;
281
+ }
282
+
283
+ function nullableTimestamp(value: unknown, label: string): string | null {
284
+ return value === null ? null : timestamp(value, label);
285
+ }
286
+
287
+ function integer(
288
+ value: unknown,
289
+ label: string,
290
+ minimum: number,
291
+ maximum: number,
292
+ ): number {
293
+ if (
294
+ typeof value !== "number"
295
+ || !Number.isSafeInteger(value)
296
+ || value < minimum
297
+ || value > maximum
298
+ ) return fail(label, `must be an integer from ${minimum} through ${maximum}`);
299
+ return value;
300
+ }
301
+
302
+ function nullableInteger(
303
+ value: unknown,
304
+ label: string,
305
+ minimum: number,
306
+ maximum: number,
307
+ ): number | null {
308
+ return value === null ? null : integer(value, label, minimum, maximum);
309
+ }
310
+
311
+ function nullableBoolean(value: unknown, label: string): boolean | null {
312
+ if (value === null || typeof value === "boolean") return value;
313
+ return fail(label, "must be boolean or null");
314
+ }
315
+
316
+ function boolean(value: unknown, label: string): boolean {
317
+ if (typeof value === "boolean") return value;
318
+ return fail(label, "must be boolean");
319
+ }
320
+
321
+ function oneOf<const T extends string>(
322
+ value: unknown,
323
+ values: readonly T[],
324
+ label: string,
325
+ ): T {
326
+ if (typeof value !== "string" || !values.includes(value as T)) {
327
+ return fail(label, `must be one of ${values.join(", ")}`);
328
+ }
329
+ return value as T;
330
+ }
331
+
332
+ /** Clone caller values without invoking accessors or proxy traps. */
333
+ function snapshotJson(value: unknown, label: string): JsonSnapshot {
334
+ let nodes = 0;
335
+ const ancestors = new WeakSet<object>();
336
+ const completed = new WeakMap<object, JsonSnapshot>();
337
+ const visit = (candidate: unknown, depth: number): JsonSnapshot => {
338
+ nodes += 1;
339
+ if (nodes > MAX_JSON_NODES || depth > MAX_JSON_DEPTH) {
340
+ return fail(label, "exceeds its structural bound");
341
+ }
342
+ if (
343
+ candidate === null
344
+ || typeof candidate === "boolean"
345
+ || typeof candidate === "string"
346
+ ) return candidate;
347
+ if (typeof candidate === "number") {
348
+ if (!Number.isFinite(candidate)) return fail(label, "must contain only JSON data");
349
+ return candidate;
350
+ }
351
+ if (typeof candidate !== "object") return fail(label, "must contain only JSON data");
352
+ if (nodeTypes.isProxy(candidate)) return fail(label, "must not contain proxies");
353
+ const prior = completed.get(candidate);
354
+ if (prior !== undefined) return prior;
355
+ if (ancestors.has(candidate)) return fail(label, "must not be circular");
356
+ ancestors.add(candidate);
357
+ try {
358
+ if (Array.isArray(candidate)) {
359
+ const values = denseArray(candidate, label, MAX_JSON_NODES);
360
+ const cloned = Object.freeze(values.map((entry) =>
361
+ visit(entry, depth + 1)));
362
+ completed.set(candidate, cloned);
363
+ return cloned;
364
+ }
365
+ const source = record(candidate, label);
366
+ const cloned: JsonRecord = {};
367
+ for (const key of Object.keys(source).sort((left, right) =>
368
+ left.localeCompare(right))) {
369
+ Object.defineProperty(cloned, key, {
370
+ value: visit(source[key], depth + 1),
371
+ enumerable: true,
372
+ configurable: false,
373
+ writable: false,
374
+ });
375
+ }
376
+ const result = Object.freeze(cloned) as JsonSnapshot;
377
+ completed.set(candidate, result);
378
+ return result;
379
+ } finally {
380
+ ancestors.delete(candidate);
381
+ }
382
+ };
383
+ return visit(value, 0);
384
+ }
385
+
386
+ function parseRequestSource(value: unknown, index: number): OmniViewSourceRequest {
387
+ const label = `Ghostget omni request.sources[${index}]`;
388
+ const source = record(value, label);
389
+ exactKeys(source, ["adapterId", "operationId", "authId"], ["input"], label);
390
+ const operationId = oneOf(
391
+ source.operationId,
392
+ ["messaging.list", "messaging.read"] as const,
393
+ `${label}.operationId`,
394
+ );
395
+ const inputValue = Object.hasOwn(source, "input") ? source.input : {};
396
+ const input = record(inputValue, `${label}.input`);
397
+ return Object.freeze({
398
+ adapterId: identifier(source.adapterId, `${label}.adapterId`),
399
+ operationId,
400
+ authId: identifier(source.authId, `${label}.authId`),
401
+ input,
402
+ });
403
+ }
404
+
405
+ function prepareRequest(requestValue: OmniViewRequest): PreparedRequest {
406
+ const snapshot = snapshotJson(requestValue, "Ghostget omni request");
407
+ const request = record(snapshot, "Ghostget omni request");
408
+ exactKeys(request, ["schemaVersion", "sources"], ["filter", "page"], "Ghostget omni request");
409
+ if (request.schemaVersion !== 1) return fail("Ghostget omni request.schemaVersion", "must be 1");
410
+ const sources = denseArray(request.sources, "Ghostget omni request.sources", MAX_SOURCES)
411
+ .map(parseRequestSource)
412
+ .sort((left, right) => canonicalJson(left).localeCompare(canonicalJson(right)));
413
+ if (sources.length === 0) return fail("Ghostget omni request.sources", "must not be empty");
414
+ const sourceKeys = sources.map((source) => canonicalJson(source));
415
+ if (new Set(sourceKeys).size !== sourceKeys.length) {
416
+ return fail("Ghostget omni request.sources", "must not repeat an exact source request");
417
+ }
418
+
419
+ let filter: OmniViewRequest["filter"];
420
+ if (request.filter !== undefined) {
421
+ const value = record(request.filter, "Ghostget omni request.filter");
422
+ exactKeys(value, [], ["kinds", "conversationId", "unread"], "Ghostget omni request.filter");
423
+ let kinds: readonly OmniEntityKindV1[] | undefined;
424
+ if (value.kinds !== undefined) {
425
+ const parsed = denseArray(value.kinds, "Ghostget omni request.filter.kinds", 3)
426
+ .map((kind, index) => oneOf(
427
+ kind,
428
+ ["conversation", "message", "notification"] as const,
429
+ `Ghostget omni request.filter.kinds[${index}]`,
430
+ ));
431
+ if (parsed.length === 0 || new Set(parsed).size !== parsed.length) {
432
+ return fail("Ghostget omni request.filter.kinds", "must be non-empty and unique");
433
+ }
434
+ kinds = Object.freeze([...parsed].sort());
435
+ }
436
+ const conversationId = value.conversationId === undefined
437
+ ? undefined
438
+ : digest(value.conversationId, "Ghostget omni request.filter.conversationId");
439
+ if (value.unread !== undefined && typeof value.unread !== "boolean") {
440
+ return fail("Ghostget omni request.filter.unread", "must be boolean");
441
+ }
442
+ filter = Object.freeze({
443
+ ...(kinds === undefined ? {} : { kinds }),
444
+ ...(conversationId === undefined ? {} : { conversationId }),
445
+ ...(value.unread === undefined ? {} : { unread: value.unread }),
446
+ });
447
+ }
448
+
449
+ let page: OmniViewRequest["page"];
450
+ if (request.page !== undefined) {
451
+ const value = record(request.page, "Ghostget omni request.page");
452
+ exactKeys(value, [], ["limit", "cursor"], "Ghostget omni request.page");
453
+ const limit = value.limit === undefined
454
+ ? 100
455
+ : integer(value.limit, "Ghostget omni request.page.limit", 1, 500);
456
+ const cursor = value.cursor === undefined
457
+ ? undefined
458
+ : localCursor(value.cursor, "Ghostget omni request.page.cursor");
459
+ page = Object.freeze({
460
+ limit,
461
+ ...(cursor === undefined ? {} : { cursor }),
462
+ });
463
+ }
464
+
465
+ const normalized = Object.freeze({
466
+ schemaVersion: 1 as const,
467
+ sources: Object.freeze(sources),
468
+ ...(filter === undefined ? {} : { filter }),
469
+ ...(page === undefined ? {} : { page }),
470
+ });
471
+ const json = canonicalJson(normalized);
472
+ if (Buffer.byteLength(json, "utf8") > MAX_REQUEST_BYTES) {
473
+ return fail("Ghostget omni request", "exceeds its byte bound");
474
+ }
475
+ const cursorIndependentRequest = Object.freeze({
476
+ schemaVersion: 1 as const,
477
+ sources: normalized.sources,
478
+ ...(filter === undefined ? {} : { filter }),
479
+ ...(page === undefined
480
+ ? {}
481
+ : { page: Object.freeze({ limit: page.limit ?? 100 }) }),
482
+ });
483
+ return Object.freeze({
484
+ json,
485
+ invocationDigest: sha256(json),
486
+ requestDigest: sha256(canonicalJson(cursorIndependentRequest)),
487
+ sources: Object.freeze(sources.map((source) => Object.freeze({
488
+ adapterId: source.adapterId,
489
+ operationId: source.operationId,
490
+ authId: source.authId,
491
+ requestInputHash: sha256(canonicalJson(source.input ?? {})),
492
+ }))),
493
+ filter: Object.freeze({
494
+ kinds: filter?.kinds ?? null,
495
+ conversationId: filter?.conversationId ?? null,
496
+ unread: filter?.unread ?? null,
497
+ }),
498
+ pageLimit: page?.limit ?? 100,
499
+ });
500
+ }
501
+
502
+ function environmentName(value: string): string {
503
+ if (value.length === 0 || value.includes("=") || value.includes("\0")) {
504
+ return fail("Ghostget omni client environment name", "is malformed");
505
+ }
506
+ return value;
507
+ }
508
+
509
+ function defineEnvironmentValue(
510
+ environment: Record<string, string>,
511
+ key: string,
512
+ value: string,
513
+ ): void {
514
+ Object.defineProperty(environment, key, {
515
+ value,
516
+ enumerable: true,
517
+ configurable: true,
518
+ writable: true,
519
+ });
520
+ }
521
+
522
+ function snapshotEnvironment(value: unknown): Readonly<Record<string, string>> {
523
+ const result = Object.create(null) as Record<string, string>;
524
+ for (const [key, environmentValue] of Object.entries(process.env)) {
525
+ if (typeof environmentValue === "string") {
526
+ defineEnvironmentValue(result, key, environmentValue);
527
+ }
528
+ }
529
+ if (value === undefined) return Object.freeze(result);
530
+ const overrides = record(value, "Ghostget omni client environment");
531
+ for (const [key, environmentValue] of Object.entries(overrides)) {
532
+ const name = environmentName(key);
533
+ if (environmentValue === undefined) {
534
+ delete result[name];
535
+ } else if (
536
+ typeof environmentValue !== "string"
537
+ || environmentValue.includes("\0")
538
+ ) {
539
+ return fail("Ghostget omni client environment value", "is malformed");
540
+ } else {
541
+ defineEnvironmentValue(result, name, environmentValue);
542
+ }
543
+ }
544
+ return Object.freeze(result);
545
+ }
546
+
547
+ function isBrandedAbortSignal(value: unknown): value is AbortSignal {
548
+ if (
549
+ nodeTypes.isProxy(value)
550
+ || typeof value !== "object"
551
+ || value === null
552
+ || abortSignalAbortedGetter === undefined
553
+ || Object.getPrototypeOf(value) !== AbortSignal.prototype
554
+ ) return false;
555
+ try {
556
+ return typeof abortSignalAbortedGetter(value) === "boolean";
557
+ } catch {
558
+ return false;
559
+ }
560
+ }
561
+
562
+ function snapshotOptions(
563
+ optionsValue: ReadOmniViewOptions,
564
+ revalidation: boolean,
565
+ ): PreparedOptions {
566
+ const options = record(optionsValue, "Ghostget omni client options");
567
+ exactKeys(
568
+ options,
569
+ [],
570
+ revalidation ? ["environment", "headed", "signal"] : ["environment"],
571
+ "Ghostget omni client options",
572
+ );
573
+ const headedValue = options.headed;
574
+ const signalValue = options.signal;
575
+ if (headedValue !== undefined && typeof headedValue !== "boolean") {
576
+ return fail("Ghostget omni client headed option", "is malformed");
577
+ }
578
+ if (signalValue !== undefined && !isBrandedAbortSignal(signalValue)) {
579
+ return fail("Ghostget omni client abort signal", "is malformed");
580
+ }
581
+ return Object.freeze({
582
+ cwd: process.cwd(),
583
+ environment: snapshotEnvironment(options.environment),
584
+ headed: headedValue === true,
585
+ ...(signalValue === undefined ? {} : { signal: signalValue }),
586
+ });
587
+ }
588
+
589
+ function signalAborted(signal: AbortSignal): boolean {
590
+ if (abortSignalAbortedGetter === undefined) return true;
591
+ return abortSignalAbortedGetter(signal) === true;
592
+ }
593
+
594
+ function throwIfAborted(signal: AbortSignal | undefined): void {
595
+ if (signal === undefined || !signalAborted(signal)) return;
596
+ throw abortError(signal);
597
+ }
598
+
599
+ function abortError(signal: AbortSignal): Error {
600
+ const reason = signal.reason as unknown;
601
+ if (reason instanceof Error) return reason;
602
+ const error = new Error("Ghostget omni revalidation was aborted");
603
+ error.name = "AbortError";
604
+ return error;
605
+ }
606
+
607
+ function cliSourcePath(): string {
608
+ const besideSource = fileURLToPath(new URL("./cli.ts", import.meta.url));
609
+ if (existsSync(besideSource)) return besideSource;
610
+ const packagedSource = fileURLToPath(new URL("../src/cli.ts", import.meta.url));
611
+ if (existsSync(packagedSource)) return packagedSource;
612
+ throw new Error("the installed Ghostget CLI source is unavailable");
613
+ }
614
+
615
+ function requireBunRuntime(): void {
616
+ if (typeof process.versions.bun !== "string") {
617
+ throw new Error("@hraness/ghostget/omni requires Bun to run the installed Ghostget CLI");
618
+ }
619
+ }
620
+
621
+ function preparedCommand(
622
+ request: PreparedRequest,
623
+ mode: CommandMode,
624
+ options: PreparedOptions,
625
+ ): PreparedCommand {
626
+ return Object.freeze({
627
+ arguments: Object.freeze([
628
+ cliSourcePath(),
629
+ "omni",
630
+ "read",
631
+ "--input",
632
+ "-",
633
+ ...(mode === "cache" ? ["--cache-only"] : []),
634
+ ...(mode === "identity" ? ["--identity-only"] : []),
635
+ ...(mode === "live" && options.headed ? ["--headed"] : []),
636
+ "--json",
637
+ ]),
638
+ input: request.json,
639
+ request,
640
+ });
641
+ }
642
+
643
+ function boundedMessage(value: string): string {
644
+ return boundedOmniText(value, MAX_ERROR_BYTES).trim();
645
+ }
646
+
647
+ function childText(value: unknown, label: string): string {
648
+ if (nodeTypes.isProxy(value)) return fail(label, "must not be a proxy");
649
+ if (typeof value === "string") return value;
650
+ if (Buffer.isBuffer(value)) return Buffer.from(value).toString("utf8");
651
+ return fail(label, "must be UTF-8 text");
652
+ }
653
+
654
+ function parseJsonOutput(textValue: string, label: string): unknown {
655
+ if (Buffer.byteLength(textValue, "utf8") > MAX_OUTPUT_BYTES) {
656
+ return fail(label, "exceeds its byte bound");
657
+ }
658
+ try {
659
+ return JSON.parse(textValue) as unknown;
660
+ } catch {
661
+ return fail(label, "is malformed JSON");
662
+ }
663
+ }
664
+
665
+ function participant(value: unknown, label: string): OmniParticipantV1 {
666
+ const source = record(value, label);
667
+ exactKeys(source, ["providerId", "displayName", "handle"], [], label);
668
+ return Object.freeze({
669
+ providerId: nullableText(source.providerId, `${label}.providerId`, 1_024),
670
+ displayName: nullableText(source.displayName, `${label}.displayName`, 2_048, true),
671
+ handle: nullableText(source.handle, `${label}.handle`, 512),
672
+ });
673
+ }
674
+
675
+ function attachment(value: unknown, label: string): OmniAttachmentV1 {
676
+ const source = record(value, label);
677
+ exactKeys(source, ["kind", "mimeType", "name", "sizeBytes"], [], label);
678
+ return Object.freeze({
679
+ kind: oneOf(
680
+ source.kind,
681
+ ["audio", "document", "image", "link", "sticker", "video", "unknown"] as const,
682
+ `${label}.kind`,
683
+ ),
684
+ mimeType: nullableText(source.mimeType, `${label}.mimeType`, 256),
685
+ name: nullableText(source.name, `${label}.name`, 2_048, true),
686
+ sizeBytes: nullableInteger(
687
+ source.sizeBytes,
688
+ `${label}.sizeBytes`,
689
+ 0,
690
+ Number.MAX_SAFE_INTEGER,
691
+ ),
692
+ });
693
+ }
694
+
695
+ function entityCommon(source: JsonRecord, label: string) {
696
+ const entitySource = record(source.source, `${label}.source`);
697
+ exactKeys(entitySource, ["surfaceId", "authId", "providerId"], [], `${label}.source`);
698
+ const providerId = text(source.providerId, `${label}.providerId`, 1_024);
699
+ const providerSourceId = text(
700
+ entitySource.providerId,
701
+ `${label}.source.providerId`,
702
+ 1_024,
703
+ );
704
+ if (providerSourceId !== providerId) {
705
+ return fail(`${label}.source.providerId`, "does not match providerId");
706
+ }
707
+ return Object.freeze({
708
+ id: digest(source.id, `${label}.id`),
709
+ providerId,
710
+ providerRevision: nullableText(
711
+ source.providerRevision,
712
+ `${label}.providerRevision`,
713
+ 1_024,
714
+ ),
715
+ orderedAt: nullableTimestamp(source.orderedAt, `${label}.orderedAt`),
716
+ source: Object.freeze({
717
+ surfaceId: text(entitySource.surfaceId, `${label}.source.surfaceId`, 64),
718
+ authId: text(entitySource.authId, `${label}.source.authId`, 64),
719
+ providerId: providerSourceId,
720
+ }),
721
+ conversationId: source.conversationId === null
722
+ ? null
723
+ : digest(source.conversationId, `${label}.conversationId`),
724
+ });
725
+ }
726
+
727
+ function verifyEntityRevision<T extends Omit<OmniEntityV1, "revision">>(
728
+ semantic: T,
729
+ revisionValue: unknown,
730
+ label: string,
731
+ ): T & { readonly revision: string } {
732
+ const revision = digest(revisionValue, `${label}.revision`);
733
+ if (sha256(canonicalJson(semantic)) !== revision) {
734
+ return fail(`${label}.revision`, "does not authenticate its semantic fields");
735
+ }
736
+ return Object.freeze({ ...semantic, revision });
737
+ }
738
+
739
+ function conversationEntity(
740
+ source: JsonRecord,
741
+ label: string,
742
+ ): OmniConversationV1 {
743
+ const hasConversationKind = Object.hasOwn(source, "conversationKind");
744
+ exactKeys(source, [
745
+ "kind", "providerId", "providerRevision", "orderedAt",
746
+ ...(hasConversationKind ? ["conversationKind"] : []), "detail", "title",
747
+ "summary", "participants", "unread", "unreadCount", "archived", "pending",
748
+ "id", "revision", "source", "conversationId",
749
+ ], [], label);
750
+ const common = entityCommon(source, label);
751
+ if (common.conversationId !== null) {
752
+ return fail(`${label}.conversationId`, "must be null for conversations");
753
+ }
754
+ const semantic = Object.freeze({
755
+ kind: "conversation" as const,
756
+ ...(hasConversationKind
757
+ ? {
758
+ conversationKind: oneOf(
759
+ source.conversationKind,
760
+ ["single", "group", "unknown"] as const,
761
+ `${label}.conversationKind`,
762
+ ),
763
+ }
764
+ : {}),
765
+ providerId: common.providerId,
766
+ providerRevision: common.providerRevision,
767
+ orderedAt: common.orderedAt,
768
+ detail: oneOf(source.detail, ["summary", "full"] as const, `${label}.detail`),
769
+ title: nullableText(source.title, `${label}.title`, 4_096, true),
770
+ summary: nullableText(source.summary, `${label}.summary`, 64 * 1024, true),
771
+ participants: Object.freeze(denseArray(
772
+ source.participants,
773
+ `${label}.participants`,
774
+ MAX_PARTICIPANTS,
775
+ ).map((entry, index) => participant(entry, `${label}.participants[${index}]`))),
776
+ unread: nullableBoolean(source.unread, `${label}.unread`),
777
+ unreadCount: nullableInteger(
778
+ source.unreadCount,
779
+ `${label}.unreadCount`,
780
+ 0,
781
+ Number.MAX_SAFE_INTEGER,
782
+ ),
783
+ archived: nullableBoolean(source.archived, `${label}.archived`),
784
+ pending: nullableBoolean(source.pending, `${label}.pending`),
785
+ id: common.id,
786
+ source: common.source,
787
+ conversationId: common.conversationId,
788
+ });
789
+ return verifyEntityRevision(semantic, source.revision, label);
790
+ }
791
+
792
+ function messageEntity(source: JsonRecord, label: string): OmniMessageV1 {
793
+ const hasBodyTruncated = Object.hasOwn(source, "bodyTruncated");
794
+ exactKeys(source, [
795
+ "kind", "providerId", "providerRevision", "orderedAt", "conversationProviderId",
796
+ "sender", "recipients", "direction", "subject", "body", "unread",
797
+ "replyToProviderId", "state", "attachments", "id", "revision", "source",
798
+ "conversationId",
799
+ ], hasBodyTruncated ? ["bodyTruncated"] : [], label);
800
+ const common = entityCommon(source, label);
801
+ const conversationProviderId = nullableText(
802
+ source.conversationProviderId,
803
+ `${label}.conversationProviderId`,
804
+ 1_024,
805
+ );
806
+ if ((conversationProviderId === null) !== (common.conversationId === null)) {
807
+ return fail(`${label}.conversationId`, "is inconsistent with conversationProviderId");
808
+ }
809
+ const body = nullableText(source.body, `${label}.body`, 256 * 1024, true);
810
+ const bodyTruncated = hasBodyTruncated
811
+ ? boolean(source.bodyTruncated, `${label}.bodyTruncated`)
812
+ : undefined;
813
+ if (body === null && bodyTruncated === true) {
814
+ return fail(`${label}.bodyTruncated`, "cannot be true when body is null");
815
+ }
816
+ const semantic = Object.freeze({
817
+ kind: "message" as const,
818
+ providerId: common.providerId,
819
+ providerRevision: common.providerRevision,
820
+ orderedAt: common.orderedAt,
821
+ conversationProviderId,
822
+ sender: source.sender === null ? null : participant(source.sender, `${label}.sender`),
823
+ recipients: Object.freeze(denseArray(
824
+ source.recipients,
825
+ `${label}.recipients`,
826
+ MAX_PARTICIPANTS,
827
+ ).map((entry, index) => participant(entry, `${label}.recipients[${index}]`))),
828
+ direction: oneOf(
829
+ source.direction,
830
+ ["incoming", "outgoing", "unknown"] as const,
831
+ `${label}.direction`,
832
+ ),
833
+ subject: nullableText(source.subject, `${label}.subject`, 8_192, true),
834
+ body,
835
+ ...(bodyTruncated === undefined ? {} : { bodyTruncated }),
836
+ unread: nullableBoolean(source.unread, `${label}.unread`),
837
+ replyToProviderId: nullableText(
838
+ source.replyToProviderId,
839
+ `${label}.replyToProviderId`,
840
+ 1_024,
841
+ ),
842
+ state: oneOf(
843
+ source.state,
844
+ ["active", "revoked", "deleted-for-me", "revoked-and-deleted-for-me"] as const,
845
+ `${label}.state`,
846
+ ),
847
+ attachments: Object.freeze(denseArray(
848
+ source.attachments,
849
+ `${label}.attachments`,
850
+ MAX_ATTACHMENTS,
851
+ ).map((entry, index) => attachment(entry, `${label}.attachments[${index}]`))),
852
+ id: common.id,
853
+ source: common.source,
854
+ conversationId: common.conversationId,
855
+ });
856
+ return verifyEntityRevision(semantic, source.revision, label);
857
+ }
858
+
859
+ function notificationEntity(
860
+ source: JsonRecord,
861
+ label: string,
862
+ ): OmniNotificationV1 {
863
+ exactKeys(source, [
864
+ "kind", "providerId", "providerRevision", "orderedAt", "actor", "subject",
865
+ "body", "unread", "context", "id", "revision", "source", "conversationId",
866
+ ], [], label);
867
+ const common = entityCommon(source, label);
868
+ if (common.conversationId !== null) {
869
+ return fail(`${label}.conversationId`, "must be null for notifications");
870
+ }
871
+ const semantic = Object.freeze({
872
+ kind: "notification" as const,
873
+ providerId: common.providerId,
874
+ providerRevision: common.providerRevision,
875
+ orderedAt: common.orderedAt,
876
+ actor: source.actor === null ? null : participant(source.actor, `${label}.actor`),
877
+ subject: nullableText(source.subject, `${label}.subject`, 8_192, true),
878
+ body: nullableText(source.body, `${label}.body`, 256 * 1024, true),
879
+ unread: nullableBoolean(source.unread, `${label}.unread`),
880
+ context: nullableText(source.context, `${label}.context`, 8_192),
881
+ id: common.id,
882
+ source: common.source,
883
+ conversationId: common.conversationId,
884
+ });
885
+ return verifyEntityRevision(semantic, source.revision, label);
886
+ }
887
+
888
+ function entity(value: unknown, index: number): OmniEntityV1 {
889
+ const label = `Ghostget omni view.entities[${index}]`;
890
+ const source = record(value, label);
891
+ const kind = oneOf(
892
+ source.kind,
893
+ ["conversation", "message", "notification"] as const,
894
+ `${label}.kind`,
895
+ );
896
+ if (kind === "conversation") return conversationEntity(source, label);
897
+ if (kind === "message") return messageEntity(source, label);
898
+ return notificationEntity(source, label);
899
+ }
900
+
901
+ function freshness(value: unknown, label: string): OmniExactFreshnessV1 {
902
+ const source = record(value, label);
903
+ exactKeys(source, ["state", "freshForMs"], [], label);
904
+ const state = oneOf(
905
+ source.state,
906
+ ["fresh", "stale", "unclassified"] as const,
907
+ `${label}.state`,
908
+ );
909
+ const freshForMs = source.freshForMs === null
910
+ ? null
911
+ : integer(source.freshForMs, `${label}.freshForMs`, 0, 365 * 24 * 60 * 60 * 1_000);
912
+ if ((state === "unclassified") !== (freshForMs === null)) {
913
+ return fail(label, "has inconsistent classification and window");
914
+ }
915
+ return Object.freeze({ state, freshForMs });
916
+ }
917
+
918
+ function coverage(value: unknown, label: string): OmniCoverageSourceStatusV1 {
919
+ const source = record(value, label);
920
+ const state = oneOf(
921
+ source.state,
922
+ ["unavailable", "observed"] as const,
923
+ `${label}.state`,
924
+ );
925
+ if (state === "unavailable") {
926
+ exactKeys(source, ["state", "reason"], [], label);
927
+ return Object.freeze({
928
+ state,
929
+ reason: text(source.reason, `${label}.reason`, 8_192),
930
+ });
931
+ }
932
+ exactKeys(source, ["state", "kind", "continuation", "reason"], [], label);
933
+ const kind = oneOf(
934
+ source.kind,
935
+ [
936
+ "complete",
937
+ "page",
938
+ "unknown",
939
+ "first-page-only",
940
+ "bounded-local",
941
+ "search-window",
942
+ "truncated",
943
+ ] as const,
944
+ `${label}.kind`,
945
+ );
946
+ const continuation = oneOf(
947
+ source.continuation,
948
+ ["none", "pending", "unavailable"] as const,
949
+ `${label}.continuation`,
950
+ );
951
+ if (kind === "complete" && continuation !== "none") {
952
+ return fail(label, "must not continue an authoritative complete set");
953
+ }
954
+ if (kind === "first-page-only" && continuation !== "unavailable") {
955
+ return fail(label, "must mark first-page-only continuation unavailable");
956
+ }
957
+ return Object.freeze({
958
+ state,
959
+ kind,
960
+ continuation,
961
+ reason: nullableText(source.reason, `${label}.reason`, 8_192, true),
962
+ });
963
+ }
964
+
965
+ function sourceStatus(value: unknown, index: number): OmniViewSourceStatusV1 {
966
+ const label = `Ghostget omni view.sources[${index}]`;
967
+ const source = record(value, label);
968
+ exactKeys(source, [
969
+ "adapterId", "operationId", "authId", "requestInputHash",
970
+ "projectionInputHash", "normalizationDataRevision", "surfaceId", "exact",
971
+ "normalization", "coverage",
972
+ ], [], label);
973
+ const exact = record(source.exact, `${label}.exact`);
974
+ exactKeys(
975
+ exact,
976
+ ["state", "key"],
977
+ ["dataRevision", "validatedAt", "ageMs", "freshness", "reason"],
978
+ `${label}.exact`,
979
+ );
980
+ const exactState = oneOf(
981
+ exact.state,
982
+ ["hit", "miss", "error"] as const,
983
+ `${label}.exact.state`,
984
+ );
985
+ const dataRevision = exact.dataRevision === undefined
986
+ ? undefined
987
+ : digest(exact.dataRevision, `${label}.exact.dataRevision`);
988
+ const validatedAt = exact.validatedAt === undefined
989
+ ? undefined
990
+ : timestamp(exact.validatedAt, `${label}.exact.validatedAt`);
991
+ const ageMs = exact.ageMs === undefined
992
+ ? undefined
993
+ : integer(exact.ageMs, `${label}.exact.ageMs`, 0, Number.MAX_SAFE_INTEGER);
994
+ const parsedFreshness = exact.freshness === undefined
995
+ ? undefined
996
+ : freshness(exact.freshness, `${label}.exact.freshness`);
997
+ const exactReason = exact.reason === undefined
998
+ ? undefined
999
+ : text(exact.reason, `${label}.exact.reason`, 8_192);
1000
+ if (
1001
+ exactState === "hit"
1002
+ && (
1003
+ dataRevision === undefined
1004
+ || validatedAt === undefined
1005
+ || ageMs === undefined
1006
+ || parsedFreshness === undefined
1007
+ || exactReason !== undefined
1008
+ )
1009
+ ) return fail(`${label}.exact`, "must fully describe a hit");
1010
+ if (
1011
+ exactState === "miss"
1012
+ && (
1013
+ dataRevision !== undefined
1014
+ || validatedAt !== undefined
1015
+ || ageMs !== undefined
1016
+ || parsedFreshness !== undefined
1017
+ || exactReason !== undefined
1018
+ )
1019
+ ) return fail(`${label}.exact`, "must not attach metadata to a miss");
1020
+ if (
1021
+ exactState === "error"
1022
+ && (
1023
+ dataRevision !== undefined
1024
+ || validatedAt !== undefined
1025
+ || ageMs !== undefined
1026
+ || parsedFreshness !== undefined
1027
+ || exactReason === undefined
1028
+ )
1029
+ ) return fail(`${label}.exact`, "must describe only the exact read error");
1030
+ if (
1031
+ exactState === "hit"
1032
+ && parsedFreshness!.state !== "unclassified"
1033
+ && (
1034
+ (parsedFreshness!.state === "fresh")
1035
+ !== (ageMs! <= parsedFreshness!.freshForMs!)
1036
+ )
1037
+ ) {
1038
+ return fail(
1039
+ `${label}.exact.freshness`,
1040
+ "does not match the returned cache age",
1041
+ );
1042
+ }
1043
+ const exactKey = digest(exact.key, `${label}.exact.key`);
1044
+ const exactStatus: OmniExactSourceStatusV1 = exactState === "hit"
1045
+ ? Object.freeze({
1046
+ state: exactState,
1047
+ key: exactKey,
1048
+ dataRevision: dataRevision!,
1049
+ validatedAt: validatedAt!,
1050
+ ageMs: ageMs!,
1051
+ freshness: parsedFreshness!,
1052
+ })
1053
+ : exactState === "error"
1054
+ ? Object.freeze({ state: exactState, key: exactKey, reason: exactReason! })
1055
+ : Object.freeze({ state: exactState, key: exactKey });
1056
+
1057
+ const normalization = record(source.normalization, `${label}.normalization`);
1058
+ exactKeys(
1059
+ normalization,
1060
+ ["state"],
1061
+ [
1062
+ "reason", "exactQueryKey", "exactDataRevision",
1063
+ "normalizedExactDataRevision", "failedExactDataRevision",
1064
+ "newerExactDataRevision", "lastGoodExactDataRevision", "lastGoodAt",
1065
+ ],
1066
+ `${label}.normalization`,
1067
+ );
1068
+ const normalizationState = oneOf(
1069
+ normalization.state,
1070
+ [
1071
+ "current",
1072
+ "missing",
1073
+ "unsupported",
1074
+ "retained-after-drift",
1075
+ "stale",
1076
+ "error",
1077
+ ] as const,
1078
+ `${label}.normalization.state`,
1079
+ );
1080
+ const reason = normalization.reason === undefined
1081
+ ? undefined
1082
+ : text(normalization.reason, `${label}.normalization.reason`, 8_192);
1083
+ const exactQueryKey = normalization.exactQueryKey === undefined
1084
+ ? undefined
1085
+ : digest(
1086
+ normalization.exactQueryKey,
1087
+ `${label}.normalization.exactQueryKey`,
1088
+ );
1089
+ const exactDataRevision = normalization.exactDataRevision === undefined
1090
+ ? undefined
1091
+ : normalization.exactDataRevision === null
1092
+ ? null
1093
+ : digest(
1094
+ normalization.exactDataRevision,
1095
+ `${label}.normalization.exactDataRevision`,
1096
+ );
1097
+ const normalizedExactDataRevision = normalization.normalizedExactDataRevision === undefined
1098
+ ? undefined
1099
+ : normalization.normalizedExactDataRevision === null
1100
+ ? null
1101
+ : digest(
1102
+ normalization.normalizedExactDataRevision,
1103
+ `${label}.normalization.normalizedExactDataRevision`,
1104
+ );
1105
+ const failedExactDataRevision = normalization.failedExactDataRevision === undefined
1106
+ ? undefined
1107
+ : digest(
1108
+ normalization.failedExactDataRevision,
1109
+ `${label}.normalization.failedExactDataRevision`,
1110
+ );
1111
+ const newerExactDataRevision = normalization.newerExactDataRevision === undefined
1112
+ ? undefined
1113
+ : normalization.newerExactDataRevision === null
1114
+ ? null
1115
+ : digest(
1116
+ normalization.newerExactDataRevision,
1117
+ `${label}.normalization.newerExactDataRevision`,
1118
+ );
1119
+ const lastGoodExactDataRevision = normalization.lastGoodExactDataRevision
1120
+ === undefined
1121
+ ? undefined
1122
+ : normalization.lastGoodExactDataRevision === null
1123
+ ? null
1124
+ : digest(
1125
+ normalization.lastGoodExactDataRevision,
1126
+ `${label}.normalization.lastGoodExactDataRevision`,
1127
+ );
1128
+ const lastGoodAt = normalization.lastGoodAt === undefined
1129
+ ? undefined
1130
+ : nullableTimestamp(
1131
+ normalization.lastGoodAt,
1132
+ `${label}.normalization.lastGoodAt`,
1133
+ );
1134
+ let normalizationStatus: OmniNormalizationSourceStatusV1;
1135
+ if (normalizationState === "missing") {
1136
+ if (
1137
+ reason !== undefined
1138
+ || exactQueryKey !== undefined
1139
+ || exactDataRevision !== undefined
1140
+ || normalizedExactDataRevision !== undefined
1141
+ || failedExactDataRevision !== undefined
1142
+ || newerExactDataRevision !== undefined
1143
+ || lastGoodExactDataRevision !== undefined
1144
+ || lastGoodAt !== undefined
1145
+ ) return fail(`${label}.normalization`, "must not attach metadata to a miss");
1146
+ normalizationStatus = Object.freeze({ state: normalizationState });
1147
+ } else if (normalizationState === "unsupported") {
1148
+ if (
1149
+ reason === undefined
1150
+ || exactQueryKey !== undefined
1151
+ || exactDataRevision !== undefined
1152
+ || normalizedExactDataRevision !== undefined
1153
+ || failedExactDataRevision !== undefined
1154
+ || newerExactDataRevision !== undefined
1155
+ || lastGoodExactDataRevision !== undefined
1156
+ || lastGoodAt !== undefined
1157
+ ) return fail(`${label}.normalization`, "must describe only why it is unsupported");
1158
+ normalizationStatus = Object.freeze({ state: normalizationState, reason });
1159
+ } else if (normalizationState === "current") {
1160
+ if (
1161
+ reason !== undefined
1162
+ || exactQueryKey === undefined
1163
+ || exactDataRevision === undefined
1164
+ || exactDataRevision === null
1165
+ || normalizedExactDataRevision !== undefined
1166
+ || failedExactDataRevision !== undefined
1167
+ || newerExactDataRevision !== undefined
1168
+ || lastGoodExactDataRevision !== undefined
1169
+ || lastGoodAt === undefined
1170
+ || lastGoodAt === null
1171
+ ) return fail(`${label}.normalization`, "must fully describe current normalized state");
1172
+ normalizationStatus = Object.freeze({
1173
+ state: normalizationState,
1174
+ exactQueryKey,
1175
+ exactDataRevision,
1176
+ lastGoodAt,
1177
+ });
1178
+ } else if (normalizationState === "retained-after-drift") {
1179
+ if (
1180
+ reason === undefined
1181
+ || exactQueryKey === undefined
1182
+ || exactDataRevision !== undefined
1183
+ || normalizedExactDataRevision !== undefined
1184
+ || failedExactDataRevision === undefined
1185
+ || newerExactDataRevision === undefined
1186
+ || lastGoodExactDataRevision === undefined
1187
+ || lastGoodAt === undefined
1188
+ ) return fail(`${label}.normalization`, "must fully describe retained drift state");
1189
+ if (
1190
+ (lastGoodExactDataRevision === null) !== (lastGoodAt === null)
1191
+ ) {
1192
+ return fail(
1193
+ `${label}.normalization`,
1194
+ "must pair last-good revision and observation time",
1195
+ );
1196
+ }
1197
+ if (
1198
+ newerExactDataRevision !== null
1199
+ && newerExactDataRevision === failedExactDataRevision
1200
+ ) {
1201
+ return fail(
1202
+ `${label}.normalization.newerExactDataRevision`,
1203
+ "must differ from the failed exact revision",
1204
+ );
1205
+ }
1206
+ normalizationStatus = Object.freeze({
1207
+ state: normalizationState,
1208
+ exactQueryKey,
1209
+ reason,
1210
+ failedExactDataRevision,
1211
+ newerExactDataRevision,
1212
+ lastGoodExactDataRevision,
1213
+ lastGoodAt,
1214
+ });
1215
+ } else if (normalizationState === "stale") {
1216
+ if (
1217
+ reason === undefined
1218
+ || exactQueryKey === undefined
1219
+ || exactDataRevision === undefined
1220
+ || normalizedExactDataRevision === undefined
1221
+ || failedExactDataRevision !== undefined
1222
+ || newerExactDataRevision !== undefined
1223
+ || lastGoodExactDataRevision !== undefined
1224
+ || lastGoodAt === undefined
1225
+ ) return fail(`${label}.normalization`, "must fully describe stale normalized state");
1226
+ if (
1227
+ (normalizedExactDataRevision === null) !== (lastGoodAt === null)
1228
+ ) {
1229
+ return fail(
1230
+ `${label}.normalization`,
1231
+ "must pair normalized revision and observation time",
1232
+ );
1233
+ }
1234
+ normalizationStatus = Object.freeze({
1235
+ state: normalizationState,
1236
+ exactQueryKey,
1237
+ exactDataRevision,
1238
+ normalizedExactDataRevision,
1239
+ lastGoodAt,
1240
+ reason,
1241
+ });
1242
+ } else {
1243
+ if (
1244
+ reason === undefined
1245
+ || exactQueryKey !== undefined
1246
+ || exactDataRevision !== undefined
1247
+ || normalizedExactDataRevision !== undefined
1248
+ || failedExactDataRevision !== undefined
1249
+ || newerExactDataRevision !== undefined
1250
+ || lastGoodExactDataRevision !== undefined
1251
+ || lastGoodAt === undefined
1252
+ ) return fail(`${label}.normalization`, "must describe only the normalization error");
1253
+ normalizationStatus = Object.freeze({
1254
+ state: normalizationState,
1255
+ reason,
1256
+ lastGoodAt,
1257
+ });
1258
+ }
1259
+ if (
1260
+ "exactQueryKey" in normalizationStatus
1261
+ && normalizationStatus.exactQueryKey === exactStatus.key
1262
+ ) {
1263
+ let normalizationRevision: string | null;
1264
+ let normalizationRevisionField:
1265
+ | "exactDataRevision"
1266
+ | "normalizedExactDataRevision"
1267
+ | "failedExactDataRevision"
1268
+ | "newerExactDataRevision";
1269
+ if (normalizationStatus.state === "current") {
1270
+ normalizationRevision = normalizationStatus.exactDataRevision;
1271
+ normalizationRevisionField = "exactDataRevision";
1272
+ } else if (normalizationStatus.state === "retained-after-drift") {
1273
+ normalizationRevision = normalizationStatus.newerExactDataRevision
1274
+ ?? normalizationStatus.failedExactDataRevision;
1275
+ normalizationRevisionField = normalizationStatus.newerExactDataRevision === null
1276
+ ? "failedExactDataRevision"
1277
+ : "newerExactDataRevision";
1278
+ } else if (normalizationStatus.exactDataRevision !== null) {
1279
+ normalizationRevision = normalizationStatus.exactDataRevision;
1280
+ normalizationRevisionField = "exactDataRevision";
1281
+ } else {
1282
+ normalizationRevision = normalizationStatus.normalizedExactDataRevision;
1283
+ normalizationRevisionField = "normalizedExactDataRevision";
1284
+ }
1285
+ if (
1286
+ exactStatus.state === "hit"
1287
+ && exactStatus.dataRevision !== normalizationRevision
1288
+ ) {
1289
+ return fail(
1290
+ `${label}.normalization.${normalizationRevisionField}`,
1291
+ "does not match the exact hit",
1292
+ );
1293
+ }
1294
+ if (exactStatus.state !== "hit" && normalizationStatus.state === "current") {
1295
+ return fail(`${label}.normalization`, "cannot be current without its exact hit");
1296
+ }
1297
+ if (
1298
+ exactStatus.state !== "hit"
1299
+ && normalizationStatus.state === "retained-after-drift"
1300
+ && normalizationStatus.newerExactDataRevision !== null
1301
+ ) {
1302
+ return fail(
1303
+ `${label}.normalization.newerExactDataRevision`,
1304
+ "cannot name a newer current revision without an exact hit",
1305
+ );
1306
+ }
1307
+ if (
1308
+ exactStatus.state !== "hit"
1309
+ && normalizationStatus.state === "stale"
1310
+ && normalizationStatus.exactDataRevision !== null
1311
+ ) {
1312
+ return fail(
1313
+ `${label}.normalization.exactDataRevision`,
1314
+ "cannot name a current revision without an exact hit",
1315
+ );
1316
+ }
1317
+ }
1318
+ if (
1319
+ normalizationStatus.state === "current"
1320
+ && normalizationStatus.exactQueryKey !== exactStatus.key
1321
+ ) {
1322
+ return fail(
1323
+ `${label}.normalization.exactQueryKey`,
1324
+ "must name the root exact query while normalization is current",
1325
+ );
1326
+ }
1327
+
1328
+ const normalizationDataRevision = source.normalizationDataRevision === null
1329
+ ? null
1330
+ : digest(
1331
+ source.normalizationDataRevision,
1332
+ `${label}.normalizationDataRevision`,
1333
+ );
1334
+ if (
1335
+ (
1336
+ normalizationStatus.state === "current"
1337
+ || normalizationStatus.state === "stale"
1338
+ || normalizationStatus.state === "retained-after-drift"
1339
+ )
1340
+ && normalizationDataRevision === null
1341
+ ) {
1342
+ return fail(
1343
+ `${label}.normalizationDataRevision`,
1344
+ `is required while normalization is ${normalizationStatus.state}`,
1345
+ );
1346
+ }
1347
+ if (
1348
+ normalizationStatus.state === "unsupported"
1349
+ && normalizationDataRevision !== null
1350
+ ) {
1351
+ return fail(
1352
+ `${label}.normalizationDataRevision`,
1353
+ "must be null while normalization is unsupported",
1354
+ );
1355
+ }
1356
+
1357
+ const parsedCoverage = coverage(source.coverage, `${label}.coverage`);
1358
+ if (
1359
+ normalizationStatus.state === "current"
1360
+ && parsedCoverage.state === "unavailable"
1361
+ ) {
1362
+ return fail(
1363
+ `${label}.coverage`,
1364
+ "cannot be unavailable for current normalized state",
1365
+ );
1366
+ }
1367
+ if (
1368
+ normalizationStatus.state === "current"
1369
+ && parsedCoverage.state === "observed"
1370
+ && parsedCoverage.continuation === "pending"
1371
+ ) {
1372
+ return fail(
1373
+ `${label}.coverage.continuation`,
1374
+ "cannot be pending for current normalized state",
1375
+ );
1376
+ }
1377
+ if (
1378
+ (
1379
+ normalizationStatus.state === "missing"
1380
+ || normalizationStatus.state === "unsupported"
1381
+ || normalizationStatus.state === "error"
1382
+ )
1383
+ && parsedCoverage.state === "observed"
1384
+ ) {
1385
+ return fail(
1386
+ `${label}.coverage`,
1387
+ `cannot be observed while normalization is ${normalizationStatus.state}`,
1388
+ );
1389
+ }
1390
+
1391
+ return Object.freeze({
1392
+ adapterId: text(source.adapterId, `${label}.adapterId`, 64),
1393
+ operationId: oneOf(
1394
+ source.operationId,
1395
+ ["messaging.list", "messaging.read"] as const,
1396
+ `${label}.operationId`,
1397
+ ),
1398
+ authId: text(source.authId, `${label}.authId`, 64),
1399
+ requestInputHash: digest(
1400
+ source.requestInputHash,
1401
+ `${label}.requestInputHash`,
1402
+ ),
1403
+ projectionInputHash: digest(
1404
+ source.projectionInputHash,
1405
+ `${label}.projectionInputHash`,
1406
+ ),
1407
+ normalizationDataRevision,
1408
+ surfaceId: text(source.surfaceId, `${label}.surfaceId`, 64),
1409
+ exact: exactStatus,
1410
+ normalization: normalizationStatus,
1411
+ coverage: parsedCoverage,
1412
+ });
1413
+ }
1414
+
1415
+ function sourceCoordinateKey(value: {
1416
+ readonly adapterId: string;
1417
+ readonly operationId: "messaging.list" | "messaging.read";
1418
+ readonly authId: string;
1419
+ readonly requestInputHash: string;
1420
+ }): string {
1421
+ return canonicalJson({
1422
+ adapterId: value.adapterId,
1423
+ operationId: value.operationId,
1424
+ authId: value.authId,
1425
+ requestInputHash: value.requestInputHash,
1426
+ });
1427
+ }
1428
+
1429
+ function compareEntities(left: OmniEntityV1, right: OmniEntityV1): number {
1430
+ const leftTime = left.orderedAt ?? "";
1431
+ const rightTime = right.orderedAt ?? "";
1432
+ return rightTime.localeCompare(leftTime) || left.id.localeCompare(right.id);
1433
+ }
1434
+
1435
+ function view(value: unknown, request: PreparedRequest): OmniViewV1 {
1436
+ const source = record(value, "Ghostget omni view");
1437
+ exactKeys(
1438
+ source,
1439
+ ["schemaVersion", "viewRevision", "entities", "nextCursor", "sources"],
1440
+ [],
1441
+ "Ghostget omni view",
1442
+ );
1443
+ if (source.schemaVersion !== 1) return fail("Ghostget omni view.schemaVersion", "must be 1");
1444
+ const entities = Object.freeze(denseArray(
1445
+ source.entities,
1446
+ "Ghostget omni view.entities",
1447
+ MAX_VIEW_ENTITIES,
1448
+ ).map(entity));
1449
+ if (new Set(entities.map((entry) => entry.id)).size !== entities.length) {
1450
+ return fail("Ghostget omni view.entities", "must not repeat entity identities");
1451
+ }
1452
+ for (let index = 1; index < entities.length; index += 1) {
1453
+ const prior = entities[index - 1];
1454
+ const current = entities[index];
1455
+ if (prior !== undefined && current !== undefined && compareEntities(prior, current) > 0) {
1456
+ return fail("Ghostget omni view.entities", "must use deterministic newest-first order");
1457
+ }
1458
+ }
1459
+ if (entities.length > request.pageLimit) {
1460
+ return fail("Ghostget omni view.entities", "exceeds the requested page limit");
1461
+ }
1462
+ for (const entry of entities) {
1463
+ if (
1464
+ request.filter.kinds !== null
1465
+ && !request.filter.kinds.includes(entry.kind)
1466
+ ) {
1467
+ return fail("Ghostget omni view.entities", "contains a kind excluded by the request");
1468
+ }
1469
+ if (
1470
+ request.filter.conversationId !== null
1471
+ && entry.conversationId !== request.filter.conversationId
1472
+ ) {
1473
+ return fail(
1474
+ "Ghostget omni view.entities",
1475
+ "contains an entity outside the requested conversation",
1476
+ );
1477
+ }
1478
+ if (request.filter.unread !== null) {
1479
+ const unread = entry.kind === "conversation"
1480
+ ? entry.unread ?? (entry.unreadCount === null ? null : entry.unreadCount > 0)
1481
+ : entry.unread;
1482
+ if (unread !== request.filter.unread) {
1483
+ return fail(
1484
+ "Ghostget omni view.entities",
1485
+ "contains an entity outside the requested unread state",
1486
+ );
1487
+ }
1488
+ }
1489
+ }
1490
+ const nextCursor = source.nextCursor === null
1491
+ ? null
1492
+ : localCursor(
1493
+ source.nextCursor,
1494
+ "Ghostget omni view.nextCursor",
1495
+ );
1496
+ if (nextCursor !== null && entities.length !== request.pageLimit) {
1497
+ return fail(
1498
+ "Ghostget omni view.nextCursor",
1499
+ "requires a full requested page",
1500
+ );
1501
+ }
1502
+ const sources = Object.freeze(denseArray(
1503
+ source.sources,
1504
+ "Ghostget omni view.sources",
1505
+ MAX_SOURCES,
1506
+ ).map(sourceStatus));
1507
+ if (sources.length === 0) return fail("Ghostget omni view.sources", "must not be empty");
1508
+ const sourceKeys = sources.map((entry) => canonicalJson({
1509
+ adapterId: entry.adapterId,
1510
+ operationId: entry.operationId,
1511
+ authId: entry.authId,
1512
+ requestInputHash: entry.requestInputHash,
1513
+ }));
1514
+ if (new Set(sourceKeys).size !== sourceKeys.length) {
1515
+ return fail("Ghostget omni view.sources", "must not repeat source identities");
1516
+ }
1517
+ const expectedSourceKeys = request.sources.map(sourceCoordinateKey);
1518
+ const actualSourceKeys = sources.map(sourceCoordinateKey);
1519
+ if (
1520
+ expectedSourceKeys.length !== actualSourceKeys.length
1521
+ || expectedSourceKeys.some((key, index) => key !== actualSourceKeys[index])
1522
+ ) {
1523
+ return fail(
1524
+ "Ghostget omni view.sources",
1525
+ "must exactly match the requested source coordinates and order",
1526
+ );
1527
+ }
1528
+ const returnedEntitySources = new Set(sources.map((entry) =>
1529
+ canonicalJson({ authId: entry.authId, surfaceId: entry.surfaceId })));
1530
+ for (const entry of entities) {
1531
+ const sourceKey = canonicalJson({
1532
+ authId: entry.source.authId,
1533
+ surfaceId: entry.source.surfaceId,
1534
+ });
1535
+ if (!returnedEntitySources.has(sourceKey)) {
1536
+ return fail(
1537
+ "Ghostget omni view.entities",
1538
+ "must belong to one returned requested source",
1539
+ );
1540
+ }
1541
+ }
1542
+ return Object.freeze({
1543
+ schemaVersion: 1,
1544
+ viewRevision: digest(source.viewRevision, "Ghostget omni view.viewRevision"),
1545
+ entities,
1546
+ nextCursor,
1547
+ sources,
1548
+ });
1549
+ }
1550
+
1551
+ function identity(value: unknown): OmniViewIdentity {
1552
+ const source = record(value, "Ghostget omni response identity");
1553
+ exactKeys(
1554
+ source,
1555
+ ["invocationDigest", "requestDigest", "sourceSetDigest"],
1556
+ [],
1557
+ "Ghostget omni response identity",
1558
+ );
1559
+ return Object.freeze({
1560
+ invocationDigest: digest(
1561
+ source.invocationDigest,
1562
+ "Ghostget omni response invocation digest",
1563
+ ),
1564
+ requestDigest: digest(
1565
+ source.requestDigest,
1566
+ "Ghostget omni response request digest",
1567
+ ),
1568
+ sourceSetDigest: digest(
1569
+ source.sourceSetDigest,
1570
+ "Ghostget omni response source-set digest",
1571
+ ),
1572
+ });
1573
+ }
1574
+
1575
+ function envelope(value: unknown, request: PreparedRequest): ParsedEnvelope {
1576
+ const source = record(value, "Ghostget omni response");
1577
+ exactKeys(
1578
+ source,
1579
+ ["ok", "schemaVersion", "source", "identity", "view"],
1580
+ [],
1581
+ "Ghostget omni response",
1582
+ );
1583
+ if (source.ok !== true) return fail("Ghostget omni response.ok", "must be true");
1584
+ if (source.schemaVersion !== 1) {
1585
+ return fail("Ghostget omni response.schemaVersion", "must be 1");
1586
+ }
1587
+ const parsedIdentity = identity(source.identity);
1588
+ if (parsedIdentity.invocationDigest !== request.invocationDigest) {
1589
+ return fail(
1590
+ "Ghostget omni response identity.invocationDigest",
1591
+ "does not match the exact canonical invocation",
1592
+ );
1593
+ }
1594
+ if (parsedIdentity.requestDigest !== request.requestDigest) {
1595
+ return fail(
1596
+ "Ghostget omni response identity.requestDigest",
1597
+ "does not match the cursor-independent canonical request",
1598
+ );
1599
+ }
1600
+ const parsedSource = oneOf(
1601
+ source.source,
1602
+ ["omni-cache", "omni-live", "omni-exact-cache", "omni-identity"] as const,
1603
+ "Ghostget omni response.source",
1604
+ );
1605
+ if (parsedSource === "omni-identity") {
1606
+ if (source.view !== null) return fail("Ghostget omni response.view", "must be null for identity-only output");
1607
+ return Object.freeze({
1608
+ source: parsedSource,
1609
+ identity: parsedIdentity,
1610
+ view: null,
1611
+ });
1612
+ }
1613
+ if (source.view === null) return fail("Ghostget omni response.view", "is required");
1614
+ return Object.freeze({
1615
+ source: parsedSource,
1616
+ identity: parsedIdentity,
1617
+ view: view(source.view, request),
1618
+ });
1619
+ }
1620
+
1621
+ function parseEnvelopeOutput(
1622
+ value: unknown,
1623
+ label: string,
1624
+ request: PreparedRequest,
1625
+ ): ParsedEnvelope {
1626
+ return envelope(parseJsonOutput(childText(value, label), label), request);
1627
+ }
1628
+
1629
+ function runSynchronousCommand(
1630
+ command: PreparedCommand,
1631
+ options: PreparedOptions,
1632
+ label: string,
1633
+ ): ParsedEnvelope {
1634
+ requireBunRuntime();
1635
+ const result = spawnSync(process.execPath, command.arguments, {
1636
+ cwd: options.cwd,
1637
+ env: options.environment,
1638
+ input: command.input,
1639
+ encoding: "utf8",
1640
+ maxBuffer: MAX_OUTPUT_BYTES,
1641
+ timeout: SYNCHRONOUS_COMMAND_TIMEOUT_MS,
1642
+ killSignal: "SIGKILL",
1643
+ });
1644
+ if (result.error !== undefined) throw result.error;
1645
+ const code = result.status ?? 3;
1646
+ const stderr = childText(result.stderr ?? "", `${label} stderr`);
1647
+ if (code !== 0) {
1648
+ throw new Error(boundedMessage(stderr) || `${label} exited ${code}`);
1649
+ }
1650
+ const stdout = childText(result.stdout ?? "", `${label} stdout`);
1651
+ if (stdout.trim().length === 0) {
1652
+ throw new Error(boundedMessage(stderr) || `${label} returned no output`);
1653
+ }
1654
+ return parseEnvelopeOutput(stdout, `${label} response`, command.request);
1655
+ }
1656
+
1657
+ function runAsynchronousCommand(
1658
+ command: PreparedCommand,
1659
+ options: PreparedOptions,
1660
+ label: string,
1661
+ timeoutMs?: number,
1662
+ ): Promise<ParsedEnvelope> {
1663
+ requireBunRuntime();
1664
+ throwIfAborted(options.signal);
1665
+ return new Promise((resolve, reject) => {
1666
+ const child = spawn(process.execPath, command.arguments, {
1667
+ cwd: options.cwd,
1668
+ env: options.environment,
1669
+ stdio: ["pipe", "pipe", "pipe"],
1670
+ });
1671
+ const stdout: Buffer[] = [];
1672
+ const stderr: Buffer[] = [];
1673
+ let stdoutBytes = 0;
1674
+ let stderrBytes = 0;
1675
+ let settled = false;
1676
+ let closed = false;
1677
+ let pendingError: Error | null = null;
1678
+ let terminationTimer: ReturnType<typeof setTimeout> | undefined;
1679
+ let forcedTerminationTimer: ReturnType<typeof setTimeout> | undefined;
1680
+ let controlTimer: ReturnType<typeof setTimeout> | undefined;
1681
+ const asError = (errorValue: unknown): Error => errorValue instanceof Error
1682
+ ? errorValue
1683
+ : new Error(String(errorValue));
1684
+ const removeAbortListener = (): void => {
1685
+ if (options.signal !== undefined) {
1686
+ options.signal.removeEventListener("abort", onAbort);
1687
+ }
1688
+ };
1689
+ const clearTimers = (): void => {
1690
+ if (terminationTimer !== undefined) clearTimeout(terminationTimer);
1691
+ if (forcedTerminationTimer !== undefined) clearTimeout(forcedTerminationTimer);
1692
+ if (controlTimer !== undefined) clearTimeout(controlTimer);
1693
+ };
1694
+ const settleFailure = (errorValue: unknown): void => {
1695
+ if (settled) return;
1696
+ settled = true;
1697
+ clearTimers();
1698
+ removeAbortListener();
1699
+ reject(asError(errorValue));
1700
+ };
1701
+ const requestTermination = (errorValue: unknown): void => {
1702
+ if (settled || closed) return;
1703
+ pendingError ??= asError(errorValue);
1704
+ if (terminationTimer !== undefined) return;
1705
+ try {
1706
+ child.kill("SIGTERM");
1707
+ } catch {
1708
+ // The close/error events below remain the process authority.
1709
+ }
1710
+ if (settled || closed) return;
1711
+ terminationTimer = setTimeout(() => {
1712
+ if (settled || closed) return;
1713
+ try {
1714
+ child.kill("SIGKILL");
1715
+ } catch {
1716
+ // A concurrent close is handled by the close event.
1717
+ }
1718
+ if (settled || closed) return;
1719
+ forcedTerminationTimer = setTimeout(() => {
1720
+ if (settled || closed) return;
1721
+ child.stdin.destroy();
1722
+ child.stdout.destroy();
1723
+ child.stderr.destroy();
1724
+ settleFailure(new Error(
1725
+ `${label} child did not close after SIGKILL`,
1726
+ { cause: pendingError },
1727
+ ));
1728
+ }, CHILD_FORCEFUL_TERMINATION_MS);
1729
+ forcedTerminationTimer.unref();
1730
+ }, CHILD_GRACEFUL_TERMINATION_MS);
1731
+ terminationTimer.unref();
1732
+ };
1733
+ function onAbort(): void {
1734
+ if (options.signal !== undefined) {
1735
+ requestTermination(abortError(options.signal));
1736
+ }
1737
+ }
1738
+ if (options.signal !== undefined) {
1739
+ options.signal.addEventListener("abort", onAbort, { once: true });
1740
+ }
1741
+ if (timeoutMs !== undefined) {
1742
+ controlTimer = setTimeout(() => {
1743
+ requestTermination(new Error(`${label} exceeded its ${timeoutMs}ms deadline`));
1744
+ }, timeoutMs);
1745
+ controlTimer.unref();
1746
+ }
1747
+ child.stdin.on("error", (error) => {
1748
+ if (pendingError === null) requestTermination(error);
1749
+ });
1750
+ child.stdout.on("error", requestTermination);
1751
+ child.stderr.on("error", requestTermination);
1752
+ child.stdout.on("data", (chunkValue: Buffer | string) => {
1753
+ if (pendingError !== null) return;
1754
+ const chunk = Buffer.isBuffer(chunkValue)
1755
+ ? chunkValue
1756
+ : Buffer.from(chunkValue);
1757
+ stdoutBytes += chunk.byteLength;
1758
+ if (stdoutBytes > MAX_OUTPUT_BYTES) {
1759
+ requestTermination(new Error(`${label} response exceeds its byte bound`));
1760
+ return;
1761
+ }
1762
+ stdout.push(chunk);
1763
+ });
1764
+ child.stderr.on("data", (chunkValue: Buffer | string) => {
1765
+ if (pendingError !== null) return;
1766
+ const chunk = Buffer.isBuffer(chunkValue)
1767
+ ? chunkValue
1768
+ : Buffer.from(chunkValue);
1769
+ const remainingBytes = Math.max(0, MAX_ERROR_BYTES - stderrBytes);
1770
+ stderrBytes += chunk.byteLength;
1771
+ if (remainingBytes > 0) stderr.push(chunk.subarray(0, remainingBytes));
1772
+ });
1773
+ child.on("error", (error) => {
1774
+ if (child.pid === undefined) {
1775
+ settleFailure(error);
1776
+ return;
1777
+ }
1778
+ requestTermination(error);
1779
+ });
1780
+ child.on("close", (code, signal) => {
1781
+ if (settled) return;
1782
+ closed = true;
1783
+ clearTimers();
1784
+ removeAbortListener();
1785
+ if (pendingError !== null) {
1786
+ settleFailure(pendingError);
1787
+ return;
1788
+ }
1789
+ const error = boundedMessage(Buffer.concat(stderr).toString("utf8"));
1790
+ if (code !== 0) {
1791
+ settleFailure(new Error(error || `${label} exited ${code ?? signal ?? "unknown"}`));
1792
+ return;
1793
+ }
1794
+ const output = Buffer.concat(stdout).toString("utf8");
1795
+ if (output.trim().length === 0) {
1796
+ settleFailure(new Error(error || `${label} returned no output`));
1797
+ return;
1798
+ }
1799
+ let parsed: ParsedEnvelope;
1800
+ try {
1801
+ parsed = parseEnvelopeOutput(output, `${label} response`, command.request);
1802
+ } catch (parseError) {
1803
+ settleFailure(parseError);
1804
+ return;
1805
+ }
1806
+ settled = true;
1807
+ resolve(parsed);
1808
+ });
1809
+ child.stdin.end(command.input);
1810
+ });
1811
+ }
1812
+
1813
+ function runLiveCommand(
1814
+ command: PreparedCommand,
1815
+ options: PreparedOptions,
1816
+ ): Promise<ParsedEnvelope> {
1817
+ return runAsynchronousCommand(command, options, "Ghostget omni live read");
1818
+ }
1819
+
1820
+ function runAsynchronousControlCommand(
1821
+ command: PreparedCommand,
1822
+ options: PreparedOptions,
1823
+ label: string,
1824
+ ): Promise<ParsedEnvelope> {
1825
+ return runAsynchronousCommand(
1826
+ command,
1827
+ options,
1828
+ label,
1829
+ ASYNC_CONTROL_COMMAND_TIMEOUT_MS,
1830
+ );
1831
+ }
1832
+
1833
+ function requireEnvelopeSource<const T extends ParsedEnvelope["source"]>(
1834
+ value: ParsedEnvelope,
1835
+ expected: T,
1836
+ label: string,
1837
+ ): Extract<ParsedEnvelope, { readonly source: T }> {
1838
+ if (value.source !== expected) return fail(label, `must have source ${expected}`);
1839
+ return value as Extract<ParsedEnvelope, { readonly source: T }>;
1840
+ }
1841
+
1842
+ function cacheResult(value: ParsedEnvelope): OmniViewCacheResult {
1843
+ const parsed = requireEnvelopeSource(value, "omni-cache", "Ghostget omni cache response");
1844
+ return Object.freeze({
1845
+ schemaVersion: 1,
1846
+ source: parsed.source,
1847
+ identity: parsed.identity,
1848
+ view: parsed.view,
1849
+ });
1850
+ }
1851
+
1852
+ function liveResult(value: ParsedEnvelope): OmniViewLiveResult {
1853
+ const parsed = requireEnvelopeSource(value, "omni-live", "Ghostget omni live response");
1854
+ return Object.freeze({
1855
+ schemaVersion: 1,
1856
+ source: parsed.source,
1857
+ identity: parsed.identity,
1858
+ view: parsed.view,
1859
+ });
1860
+ }
1861
+
1862
+ async function observeIdentity(
1863
+ request: PreparedRequest,
1864
+ options: PreparedOptions,
1865
+ ): Promise<OmniViewIdentity> {
1866
+ throwIfAborted(options.signal);
1867
+ return requireEnvelopeSource(
1868
+ await runAsynchronousControlCommand(
1869
+ preparedCommand(request, "identity", options),
1870
+ options,
1871
+ "Ghostget omni identity preflight",
1872
+ ),
1873
+ "omni-identity",
1874
+ "Ghostget omni identity response",
1875
+ ).identity;
1876
+ }
1877
+
1878
+ function readCachedPrepared(
1879
+ request: PreparedRequest,
1880
+ options: PreparedOptions,
1881
+ ): OmniViewCacheResult {
1882
+ return cacheResult(runSynchronousCommand(
1883
+ preparedCommand(request, "cache", options),
1884
+ options,
1885
+ "Ghostget omni cache read",
1886
+ ));
1887
+ }
1888
+
1889
+ async function readCachedPreparedAsynchronously(
1890
+ request: PreparedRequest,
1891
+ options: PreparedOptions,
1892
+ ): Promise<OmniViewCacheResult> {
1893
+ return cacheResult(await runAsynchronousControlCommand(
1894
+ preparedCommand(request, "cache", options),
1895
+ options,
1896
+ "Ghostget omni cache read",
1897
+ ));
1898
+ }
1899
+
1900
+ function identitiesMatch(left: OmniViewIdentity, right: OmniViewIdentity): boolean {
1901
+ return left.invocationDigest === right.invocationDigest
1902
+ && left.requestDigest === right.requestDigest
1903
+ && left.sourceSetDigest === right.sourceSetDigest;
1904
+ }
1905
+
1906
+ function assertIdentity(
1907
+ expected: OmniViewIdentity,
1908
+ actual: OmniViewIdentity,
1909
+ phase: string,
1910
+ ): void {
1911
+ if (!identitiesMatch(expected, actual)) {
1912
+ throw new Error(
1913
+ `Ghostget omni identity changed ${phase}; the live result was discarded`,
1914
+ );
1915
+ }
1916
+ }
1917
+
1918
+ function viewsHaveSameEntities(left: OmniViewV1, right: OmniViewV1): boolean {
1919
+ return left.entities.length === right.entities.length
1920
+ && left.entities.every((entity, index) => {
1921
+ const other = right.entities[index];
1922
+ return other !== undefined
1923
+ && entity.id === other.id
1924
+ && entity.revision === other.revision;
1925
+ });
1926
+ }
1927
+
1928
+ type PositiveSourceHeads = {
1929
+ readonly rootExact: string | null;
1930
+ readonly normalization: string | null;
1931
+ readonly continuationExact: string | null;
1932
+ };
1933
+
1934
+ function positiveSourceHeads(
1935
+ source: OmniViewSourceStatusV1,
1936
+ ): PositiveSourceHeads {
1937
+ const rootExact = source.exact.state === "hit"
1938
+ ? canonicalJson(Object.freeze({
1939
+ key: source.exact.key,
1940
+ dataRevision: source.exact.dataRevision,
1941
+ validatedAt: source.exact.validatedAt,
1942
+ }))
1943
+ : null;
1944
+ const continuationExact = source.normalization.state === "stale"
1945
+ && source.normalization.exactQueryKey !== source.exact.key
1946
+ && source.normalization.exactDataRevision !== null
1947
+ ? canonicalJson(Object.freeze({
1948
+ key: source.normalization.exactQueryKey,
1949
+ dataRevision: source.normalization.exactDataRevision,
1950
+ }))
1951
+ : source.normalization.state === "retained-after-drift"
1952
+ && source.normalization.exactQueryKey !== source.exact.key
1953
+ && source.normalization.newerExactDataRevision !== null
1954
+ ? canonicalJson(Object.freeze({
1955
+ key: source.normalization.exactQueryKey,
1956
+ dataRevision: source.normalization.newerExactDataRevision,
1957
+ }))
1958
+ : null;
1959
+ return Object.freeze({
1960
+ rootExact,
1961
+ normalization: source.normalizationDataRevision,
1962
+ continuationExact,
1963
+ });
1964
+ }
1965
+
1966
+ function hasPositiveAdvance(
1967
+ after: PositiveSourceHeads,
1968
+ before: PositiveSourceHeads | null,
1969
+ duringLive: PositiveSourceHeads,
1970
+ ): boolean {
1971
+ return (Object.keys(after) as (keyof PositiveSourceHeads)[]).some((key) => {
1972
+ const candidate = after[key];
1973
+ return candidate !== null
1974
+ && candidate !== duringLive[key]
1975
+ && (before === null || candidate !== before[key]);
1976
+ });
1977
+ }
1978
+
1979
+ function laterSourceCoordinates(
1980
+ cachedBefore: OmniViewCacheResult | null,
1981
+ cachedAfter: OmniViewCacheResult,
1982
+ live: OmniViewLiveResult,
1983
+ ): ReadonlySet<string> {
1984
+ const beforeEvidence = new Map((cachedBefore?.view.sources ?? []).map((source) => [
1985
+ sourceCoordinateKey(source),
1986
+ positiveSourceHeads(source),
1987
+ ]));
1988
+ const liveEvidence = new Map(live.view.sources.map((source) => [
1989
+ sourceCoordinateKey(source),
1990
+ positiveSourceHeads(source),
1991
+ ]));
1992
+ const result = new Set<string>();
1993
+ for (const source of cachedAfter.view.sources) {
1994
+ const coordinate = sourceCoordinateKey(source);
1995
+ const after = positiveSourceHeads(source);
1996
+ const before = beforeEvidence.get(coordinate) ?? null;
1997
+ const duringLive = liveEvidence.get(coordinate);
1998
+ if (
1999
+ duringLive !== undefined
2000
+ && hasPositiveAdvance(after, before, duringLive)
2001
+ ) result.add(coordinate);
2002
+ }
2003
+ return result;
2004
+ }
2005
+
2006
+ function viewHasLaterRevision(
2007
+ cachedBefore: OmniViewCacheResult | null,
2008
+ cachedAfter: OmniViewCacheResult,
2009
+ live: OmniViewLiveResult,
2010
+ ): boolean {
2011
+ return cachedAfter.view.viewRevision !== live.view.viewRevision
2012
+ && (
2013
+ cachedBefore === null
2014
+ || cachedAfter.view.viewRevision !== cachedBefore.view.viewRevision
2015
+ );
2016
+ }
2017
+
2018
+ function sourceHasUnresolvedLiveIssue(
2019
+ source: OmniViewSourceStatusV1,
2020
+ ): boolean {
2021
+ return source.exact.state === "error"
2022
+ || source.normalization.state !== "current";
2023
+ }
2024
+
2025
+ function selectCurrent(
2026
+ cachedBefore: OmniViewCacheResult | null,
2027
+ cachedAfter: OmniViewCacheResult | null,
2028
+ live: OmniViewLiveResult,
2029
+ ): RevalidatedOmniViewCurrent {
2030
+ if (cachedAfter === null) return live;
2031
+ const laterSources = laterSourceCoordinates(
2032
+ cachedBefore,
2033
+ cachedAfter,
2034
+ live,
2035
+ );
2036
+ let useCachedView = false;
2037
+ if (
2038
+ viewHasLaterRevision(cachedBefore, cachedAfter, live)
2039
+ || laterSources.size > 0
2040
+ ) {
2041
+ useCachedView = true;
2042
+ } else if (viewsHaveSameEntities(cachedAfter.view, live.view)) {
2043
+ return live;
2044
+ } else if (
2045
+ cachedBefore !== null
2046
+ && viewsHaveSameEntities(cachedAfter.view, cachedBefore.view)
2047
+ ) {
2048
+ return live;
2049
+ } else {
2050
+ useCachedView = true;
2051
+ }
2052
+ if (!useCachedView) return live;
2053
+
2054
+ const liveSources = new Map(live.view.sources.map((source) => [
2055
+ sourceCoordinateKey(source),
2056
+ source,
2057
+ ]));
2058
+ let merged = false;
2059
+ const sources = Object.freeze(cachedAfter.view.sources.map((cachedSource) => {
2060
+ const coordinate = sourceCoordinateKey(cachedSource);
2061
+ const liveSource = liveSources.get(coordinate);
2062
+ if (
2063
+ liveSource !== undefined
2064
+ && sourceHasUnresolvedLiveIssue(liveSource)
2065
+ ) {
2066
+ merged = true;
2067
+ return liveSource;
2068
+ }
2069
+ if (
2070
+ laterSources.has(coordinate)
2071
+ || liveSource === undefined
2072
+ || canonicalJson(liveSource) === canonicalJson(cachedSource)
2073
+ ) return cachedSource;
2074
+ merged = true;
2075
+ return liveSource;
2076
+ }));
2077
+ if (!merged) return cachedAfter;
2078
+ const result: OmniViewMergedResult = Object.freeze({
2079
+ schemaVersion: 1,
2080
+ source: "omni-merged",
2081
+ identity: cachedAfter.identity,
2082
+ view: Object.freeze({
2083
+ ...cachedAfter.view,
2084
+ sources,
2085
+ }),
2086
+ });
2087
+ return result;
2088
+ }
2089
+
2090
+ function scheduleRevalidation(
2091
+ request: PreparedRequest,
2092
+ options: PreparedOptions,
2093
+ cachedBeforeSource: CachedBeforeSource,
2094
+ ): Promise<RevalidatedOmniView> {
2095
+ // This callback cannot run until the public function has returned. SWR's
2096
+ // synchronous phase therefore performs exactly one cache-only command.
2097
+ return Promise.resolve().then(async () => {
2098
+ throwIfAborted(options.signal);
2099
+ const identityBefore = await observeIdentity(request, options);
2100
+ let cachedBefore = cachedBeforeSource.status === "provided"
2101
+ ? cachedBeforeSource.value
2102
+ : null;
2103
+ if (cachedBeforeSource.status === "lookup") {
2104
+ try {
2105
+ cachedBefore = await readCachedPreparedAsynchronously(request, options);
2106
+ } catch {
2107
+ cachedBefore = null;
2108
+ }
2109
+ }
2110
+ if (cachedBefore !== null) {
2111
+ assertIdentity(
2112
+ identityBefore,
2113
+ cachedBefore.identity,
2114
+ "before revalidation started",
2115
+ );
2116
+ }
2117
+
2118
+ throwIfAborted(options.signal);
2119
+ const live = liveResult(await runLiveCommand(
2120
+ preparedCommand(request, "live", options),
2121
+ options,
2122
+ ));
2123
+ throwIfAborted(options.signal);
2124
+ const identityAfter = await observeIdentity(request, options);
2125
+ assertIdentity(identityBefore, live.identity, "while revalidation was running");
2126
+ assertIdentity(identityBefore, identityAfter, "while revalidation was running");
2127
+
2128
+ let cachedAfter: OmniViewCacheResult | null = null;
2129
+ try {
2130
+ cachedAfter = await readCachedPreparedAsynchronously(request, options);
2131
+ } catch {
2132
+ cachedAfter = null;
2133
+ }
2134
+ if (cachedAfter !== null) {
2135
+ assertIdentity(
2136
+ identityBefore,
2137
+ cachedAfter.identity,
2138
+ "before the final cache observation",
2139
+ );
2140
+ }
2141
+ throwIfAborted(options.signal);
2142
+ return Object.freeze({
2143
+ cachedBefore,
2144
+ cachedAfter,
2145
+ live,
2146
+ current: selectCurrent(cachedBefore, cachedAfter, live),
2147
+ });
2148
+ });
2149
+ }
2150
+
2151
+ export function readCachedOmniView(
2152
+ request: OmniViewRequest,
2153
+ options: ReadOmniViewOptions = {},
2154
+ ): OmniViewCacheResult {
2155
+ const preparedOptions = snapshotOptions(options, false);
2156
+ const preparedRequest = prepareRequest(request);
2157
+ return readCachedPrepared(preparedRequest, preparedOptions);
2158
+ }
2159
+
2160
+ export function revalidateOmniView(
2161
+ request: OmniViewRequest,
2162
+ options: RevalidateOmniViewOptions = {},
2163
+ ): Promise<RevalidatedOmniView> {
2164
+ const preparedOptions = snapshotOptions(options, true);
2165
+ const preparedRequest = prepareRequest(request);
2166
+ return scheduleRevalidation(
2167
+ preparedRequest,
2168
+ preparedOptions,
2169
+ { status: "lookup" },
2170
+ );
2171
+ }
2172
+
2173
+ export function staleWhileRevalidateOmniView(
2174
+ request: OmniViewRequest,
2175
+ options: RevalidateOmniViewOptions = {},
2176
+ ): {
2177
+ readonly cached: OmniViewCacheResult | null;
2178
+ readonly revalidation: Promise<RevalidatedOmniView>;
2179
+ } {
2180
+ const preparedOptions = snapshotOptions(options, true);
2181
+ const preparedRequest = prepareRequest(request);
2182
+ let cached: OmniViewCacheResult | null = null;
2183
+ try {
2184
+ cached = readCachedPrepared(preparedRequest, preparedOptions);
2185
+ } catch {
2186
+ cached = null;
2187
+ }
2188
+ return Object.freeze({
2189
+ cached,
2190
+ revalidation: scheduleRevalidation(
2191
+ preparedRequest,
2192
+ preparedOptions,
2193
+ { status: "provided", value: cached },
2194
+ ),
2195
+ });
2196
+ }
2197
+
2198
+ export type {
2199
+ OmniAttachmentV1,
2200
+ OmniClientEnvironment,
2201
+ OmniConversationV1,
2202
+ OmniCoverageSourceStatusV1,
2203
+ OmniEntityKindV1,
2204
+ OmniEntityV1,
2205
+ OmniExactFreshnessV1,
2206
+ OmniExactSourceStatusV1,
2207
+ OmniJsonValue,
2208
+ OmniMessageV1,
2209
+ OmniNormalizationSourceStatusV1,
2210
+ OmniNotificationV1,
2211
+ OmniParticipantV1,
2212
+ OmniViewCacheResult,
2213
+ OmniViewExactCacheResult,
2214
+ OmniViewIdentity,
2215
+ OmniViewLiveResult,
2216
+ OmniViewMergedResult,
2217
+ OmniViewRequest,
2218
+ OmniViewSourceRequest,
2219
+ OmniViewSourceStatusV1,
2220
+ OmniViewV1,
2221
+ ReadOmniViewOptions,
2222
+ RevalidatedOmniView,
2223
+ RevalidatedOmniViewCurrent,
2224
+ RevalidateOmniViewOptions,
2225
+ } from "./omni-client-types";